1
0
mirror of https://github.com/esphome/esphome.git synced 2026-03-24 12:16:46 +01:00

Merge pull request #5758 from esphome/bump-2023.11.0

2023.11.0
This commit is contained in:
Jesse Hills 2023-11-15 16:11:18 +13:00 committed by GitHub
commit c536c976b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
411 changed files with 12883 additions and 1476 deletions

View File

@ -1,17 +1,13 @@
{
"name": "ESPHome Dev",
"image": "ghcr.io/esphome/esphome-lint:dev",
"postCreateCommand": [
"script/devcontainer-post-create"
],
"postCreateCommand": ["script/devcontainer-post-create"],
"containerEnv": {
"DEVCONTAINER": "1"
"DEVCONTAINER": "1",
"PIP_BREAK_SYSTEM_PACKAGES": "1",
"PIP_ROOT_USER_ACTION": "ignore"
},
"runArgs": [
"--privileged",
"-e",
"ESPHOME_DASHBOARD_USE_PING=1"
],
"runArgs": ["--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1"],
"appPort": 6052,
"customizations": {
"vscode": {
@ -24,7 +20,7 @@
// cpp
"ms-vscode.cpptools",
// editorconfig
"editorconfig.editorconfig",
"editorconfig.editorconfig"
],
"settings": {
"python.languageServer": "Pylance",

View File

@ -19,6 +19,8 @@
- [ ] ESP32 IDF
- [ ] ESP8266
- [ ] RP2040
- [ ] BK72xx
- [ ] RTL87xx
## Example entry for `config.yaml`:
<!--

View File

@ -40,7 +40,7 @@ jobs:
arch: [amd64, armv7, aarch64]
build_type: ["ha-addon", "docker", "lint"]
steps:
- uses: actions/checkout@v4.1.0
- uses: actions/checkout@v4.1.1
- name: Set up Python
uses: actions/setup-python@v4.7.1
with:

View File

@ -34,7 +34,7 @@ jobs:
cache-key: ${{ steps.cache-key.outputs.key }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
- name: Generate cache-key
id: cache-key
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
@ -66,7 +66,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -87,7 +87,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -108,7 +108,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -129,7 +129,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -150,7 +150,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -171,7 +171,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -191,7 +191,7 @@ jobs:
- common
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -216,11 +216,34 @@ jobs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
- name: Find all YAML test files
id: set-matrix
run: echo "matrix=$(ls tests/test*.yaml | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
validate-tests:
name: Validate YAML test ${{ matrix.file }}
runs-on: ubuntu-latest
needs:
- common
- compile-tests-list
strategy:
fail-fast: false
matrix:
file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run esphome config ${{ matrix.file }}
run: |
. venv/bin/activate
esphome config ${{ matrix.file }}
compile-tests:
name: Run YAML test ${{ matrix.file }}
runs-on: ubuntu-latest
@ -234,6 +257,7 @@ jobs:
- pytest
- pyupgrade
- compile-tests-list
- validate-tests
strategy:
fail-fast: false
max-parallel: 2
@ -241,7 +265,7 @@ jobs:
file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@ -296,7 +320,7 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:

24
.github/workflows/needs-docs.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: Needs Docs
on:
pull_request:
types: [labeled, unlabeled]
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- name: Check for needs-docs label
uses: actions/github-script@v6.4.1
with:
script: |
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number
});
const needsDocs = labels.find(label => label.name === 'needs-docs');
if (needsDocs) {
core.setFailed('Pull request needs docs');
}

View File

@ -19,7 +19,7 @@ jobs:
outputs:
tag: ${{ steps.tag.outputs.tag }}
steps:
- uses: actions/checkout@v4.1.0
- uses: actions/checkout@v4.1.1
- name: Get tag
id: tag
# yamllint disable rule:line-length
@ -43,7 +43,7 @@ jobs:
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.0
- uses: actions/checkout@v4.1.1
- name: Set up Python
uses: actions/setup-python@v4.7.1
with:
@ -88,7 +88,7 @@ jobs:
target: "lint"
baseimg: "docker"
steps:
- uses: actions/checkout@v4.1.0
- uses: actions/checkout@v4.1.1
- name: Set up Python
uses: actions/setup-python@v4.7.1
with:

View File

@ -13,10 +13,10 @@ jobs:
if: github.repository == 'esphome/esphome'
steps:
- name: Checkout
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
- name: Checkout Home Assistant
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
with:
repository: home-assistant/core
path: lib/home-assistant

View File

@ -17,6 +17,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.0
uses: actions/checkout@v4.1.1
- name: Run yamllint
uses: frenck/action-yamllint@v1.4.1

View File

@ -3,7 +3,7 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.9.1
rev: 23.10.1
hooks:
- id: black
args:
@ -11,7 +11,7 @@ repos:
- --quiet
files: ^((esphome|script|tests)/.+)?[^/]+\.py$
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
rev: 6.1.0
hooks:
- id: flake8
additional_dependencies:
@ -27,7 +27,7 @@ repos:
- --branch=release
- --branch=beta
- repo: https://github.com/asottile/pyupgrade
rev: v3.10.1
rev: v3.15.0
hooks:
- id: pyupgrade
args: [--py39-plus]

View File

@ -17,6 +17,9 @@ esphome/components/ac_dimmer/* @glmnet
esphome/components/adc/* @esphome/core
esphome/components/adc128s102/* @DeerMaximum
esphome/components/addressable_light/* @justfalter
esphome/components/ade7953/* @angelnu
esphome/components/ade7953_i2c/* @angelnu
esphome/components/ade7953_spi/* @angelnu
esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau
esphome/components/airthings_wave_mini/* @ncareau
@ -77,6 +80,7 @@ esphome/components/dashboard_import/* @esphome/core
esphome/components/debug/* @OttoWinter
esphome/components/delonghi/* @grob6000
esphome/components/dfplayer/* @glmnet
esphome/components/dfrobot_sen0395/* @niklasweber
esphome/components/dht/* @OttoWinter
esphome/components/display_menu_base/* @numo68
esphome/components/dps310/* @kbx81
@ -85,11 +89,12 @@ esphome/components/dsmr/* @glmnet @zuidwijk
esphome/components/duty_time/* @dudanov
esphome/components/ee895/* @Stock-M
esphome/components/ektf2232/* @jesserockz
esphome/components/emc2101/* @ellull
esphome/components/ens210/* @itn3rd77
esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @jesserockz
esphome/components/esp32_ble/* @Rapsssito @jesserockz
esphome/components/esp32_ble_client/* @jesserockz
esphome/components/esp32_ble_server/* @clydebarrow @jesserockz
esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz
esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_can/* @Sympatron
esphome/components/esp32_improv/* @jesserockz
@ -110,6 +115,7 @@ esphome/components/gp8403/* @jesserockz
esphome/components/gpio/* @esphome/core
esphome/components/gps/* @coogle
esphome/components/graph/* @synco
esphome/components/gree/* @orestismers
esphome/components/grove_tb6612fng/* @max246
esphome/components/growatt_solar/* @leeuwte
esphome/components/haier/* @paveldn
@ -121,6 +127,7 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/hm3301/* @freekode
esphome/components/homeassistant/* @OttoWinter
esphome/components/honeywellabp/* @RubyBailey
esphome/components/honeywellabp2_i2c/* @jpfaff
esphome/components/host/* @esphome/core
esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/hte501/* @Stock-M
@ -131,6 +138,7 @@ esphome/components/i2s_audio/* @jesserockz
esphome/components/i2s_audio/media_player/* @jesserockz
esphome/components/i2s_audio/microphone/* @jesserockz
esphome/components/i2s_audio/speaker/* @jesserockz
esphome/components/iaqcore/* @yozik04
esphome/components/ili9xxx/* @clydebarrow @nielsnl68
esphome/components/improv_base/* @esphome/core
esphome/components/improv_serial/* @esphome/core
@ -147,6 +155,7 @@ esphome/components/key_provider/* @ssieb
esphome/components/kuntze/* @ssieb
esphome/components/lcd_menu/* @numo68
esphome/components/ld2410/* @regevbr @sebcaps
esphome/components/ld2420/* @descipher
esphome/components/ledc/* @OttoWinter
esphome/components/libretiny/* @kuba2k2
esphome/components/libretiny_pwm/* @kuba2k2
@ -178,6 +187,7 @@ esphome/components/mcp9808/* @k7hpn
esphome/components/md5/* @esphome/core
esphome/components/mdns/* @esphome/core
esphome/components/media_player/* @jesserockz
esphome/components/micronova/* @jorre05
esphome/components/microphone/* @jesserockz
esphome/components/mics_4514/* @jesserockz
esphome/components/midea/* @dudanov
@ -207,11 +217,12 @@ esphome/components/nextion/sensor/* @senexcrenshaw
esphome/components/nextion/switch/* @senexcrenshaw
esphome/components/nextion/text_sensor/* @senexcrenshaw
esphome/components/nfc/* @jesserockz
esphome/components/noblex/* @AGalfra
esphome/components/number/* @esphome/core
esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/pca6416a/* @Mat931
esphome/components/pca9554/* @hwstar
esphome/components/pca9554/* @clydebarrow @hwstar
esphome/components/pcf85063/* @brogon
esphome/components/pcf8563/* @KoenBreeman
esphome/components/pid/* @OttoWinter
@ -229,11 +240,13 @@ esphome/components/pulse_meter/* @TrentHouliston @cstaahl @stevebaxter
esphome/components/pvvx_mithermometer/* @pasiz
esphome/components/qmp6988/* @andrewpc
esphome/components/qr_code/* @wjtje
esphome/components/qwiic_pir/* @kahrendt
esphome/components/radon_eye_ble/* @jeffeb3
esphome/components/radon_eye_rd200/* @jeffeb3
esphome/components/rc522/* @glmnet
esphome/components/rc522_i2c/* @glmnet
esphome/components/rc522_spi/* @glmnet
esphome/components/resistance_sampler/* @jesserockz
esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz
esphome/components/rgbct/* @jesserockz
@ -254,6 +267,7 @@ esphome/components/sen21231/* @shreyaskarnik
esphome/components/sen5x/* @martgras
esphome/components/sensirion_common/* @martgras
esphome/components/sensor/* @esphome/core
esphome/components/sfa30/* @ghsensdev
esphome/components/sgp40/* @SenexCrenshaw
esphome/components/sgp4x/* @SenexCrenshaw @martgras
esphome/components/shelly_dimmer/* @edge90 @rnauber
@ -299,6 +313,7 @@ esphome/components/tcl112/* @glmnet
esphome/components/tee501/* @Stock-M
esphome/components/teleinfo/* @0hax
esphome/components/template/alarm_control_panel/* @grahambrown11
esphome/components/text/* @mauritskorse
esphome/components/thermostat/* @kbx81
esphome/components/time/* @OttoWinter
esphome/components/tlc5947/* @rnauber
@ -322,6 +337,7 @@ esphome/components/tuya/sensor/* @jesserockz
esphome/components/tuya/switch/* @jesserockz
esphome/components/tuya/text_sensor/* @dentra
esphome/components/uart/* @esphome/core
esphome/components/uart/button/* @ssieb
esphome/components/ufire_ec/* @pvizeli
esphome/components/ufire_ise/* @pvizeli
esphome/components/ultrasonic/* @OttoWinter
@ -337,10 +353,12 @@ esphome/components/wiegand/* @ssieb
esphome/components/wireguard/* @droscy @lhoracek @thomas0bernard
esphome/components/wl_134/* @hobbypunk90
esphome/components/x9c/* @EtienneMD
esphome/components/xgzp68xx/* @gcormier
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
esphome/components/xl9535/* @mreditor97
esphome/components/xpt2046/* @nielsnl68 @numo68
esphome/components/zhlt01/* @cfeenstra1024
esphome/components/zio_ultrasonic/* @kahrendt

View File

@ -5,39 +5,52 @@
# One of "docker", "hassio"
ARG BASEIMGTYPE=docker
# https://github.com/hassio-addons/addon-debian-base/releases
FROM ghcr.io/hassio-addons/debian-base:6.2.3 AS base-hassio
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye
FROM debian:bullseye-20230208-slim AS base-docker
FROM ghcr.io/hassio-addons/debian-base:7.2.0 AS base-hassio
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bookworm
FROM debian:12.2-slim AS base-docker
FROM base-${BASEIMGTYPE} AS base
ARG TARGETARCH
ARG TARGETVARIANT
# Note that --break-system-packages is used below because
# https://peps.python.org/pep-0668/ added a safety check that prevents
# installing packages with the same name as a system package. This is
# not a problem for us because we are not concerned about overwriting
# system packages because we are running in an isolated container.
RUN \
apt-get update \
# Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \
python3=3.9.2-3 \
python3-pip=20.3.4-4+deb11u1 \
python3-setuptools=52.0.0-4 \
python3-cryptography=3.3.2-1 \
python3-venv=3.9.2-3 \
iputils-ping=3:20210202-1 \
git=1:2.30.2-1+deb11u2 \
curl=7.74.0-1.3+deb11u10 \
openssh-client=1:8.4p1-5+deb11u2 \
python3-cffi=1.14.5-1 \
libcairo2=1.16.0-5 \
python3-pip=23.0.1+dfsg-1 \
python3-setuptools=66.1.1-1 \
python3-venv=3.11.2-1+b1 \
python3-wheel=0.38.4-2 \
iputils-ping=3:20221126-1 \
git=1:2.39.2-1.1 \
curl=7.88.1-10+deb12u4 \
openssh-client=1:9.2p1-2+deb12u1 \
python3-cffi=1.15.1-5 \
libcairo2=1.16.0-7 \
patch=2.7.6-7; \
if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
apt-get install -y --no-install-recommends \
build-essential=12.9 \
python3-dev=3.9.2-3 \
zlib1g-dev=1:1.2.11.dfsg-2+deb11u2 \
libjpeg-dev=1:2.0.6-4 \
libfreetype-dev=2.10.4+dfsg-1+deb11u1; \
python3-dev=3.11.2-1+b1 \
zlib1g-dev=1:1.2.13.dfsg-1 \
libjpeg-dev=1:2.1.5-2 \
libfreetype-dev=2.12.1+dfsg-5 \
libssl-dev=3.0.11-1~deb12u2 \
libffi-dev=3.4.4-1 \
cargo=0.66.0+ds1-1 \
pkg-config=1.8.1-1 \
gcc-arm-linux-gnueabihf=4:12.2.0-3; \
fi; \
rm -rf \
/tmp/* \
@ -60,9 +73,12 @@ RUN \
RUN \
# Ubuntu python3-pip is missing wheel
pip3 install --no-cache-dir \
wheel==0.37.1 \
platformio==6.1.11 \
if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \
pip3 install \
--break-system-packages --no-cache-dir \
platformio==6.1.11 \
# Change some platformio settings
&& platformio settings set enable_telemetry No \
&& platformio settings set check_platformio_interval 1000000 \
@ -70,9 +86,15 @@ RUN \
# First install requirements to leverage caching when requirements don't change
# tmpfs is for https://github.com/rust-lang/cargo/issues/8719
COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini /
RUN \
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \
CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse CARGO_HOME=/root/.cargo \
pip3 install \
--break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
&& /platformio_install_deps.py /platformio.ini --libraries
@ -81,7 +103,11 @@ FROM base AS docker
# Copy esphome and install
COPY . /esphome
RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome
RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \
pip3 install \
--break-system-packages --no-cache-dir --no-use-pep517 -e /esphome
# Settings for dashboard
ENV USERNAME="" PASSWORD=""
@ -89,6 +115,10 @@ ENV USERNAME="" PASSWORD=""
# Expose the dashboard to Docker
EXPOSE 6052
# Run healthcheck (heartbeat)
HEALTHCHECK --interval=30s --timeout=30s \
CMD curl --fail http://localhost:6052/version -A "HealthCheck" || exit 1
COPY docker/docker_entrypoint.sh /entrypoint.sh
# The directory the user should mount their configuration files to
@ -110,7 +140,7 @@ RUN \
apt-get update \
# Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \
nginx-light=1.18.0-6.1+deb11u3 \
nginx-light=1.22.1-9 \
&& rm -rf \
/tmp/* \
/var/{cache,log}/* \
@ -123,7 +153,11 @@ COPY docker/ha-addon-rootfs/ /
# Copy esphome and install
COPY . /esphome
RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome
RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \
pip3 install \
--break-system-packages --no-cache-dir --no-use-pep517 -e /esphome
# Labels
LABEL \
@ -146,20 +180,24 @@ RUN \
apt-get update \
# Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \
clang-format-13=1:13.0.1-6~deb11u1 \
clang-tidy-11=1:11.0.1-2 \
clang-format-13=1:13.0.1-11+b2 \
clang-tidy-14=1:14.0.6-12 \
patch=2.7.6-7 \
software-properties-common=0.96.20.2-2.1 \
nano=5.4-2+deb11u2 \
software-properties-common=0.99.30-4 \
nano=7.2-1 \
build-essential=12.9 \
python3-dev=3.9.2-3 \
python3-dev=3.11.2-1+b1 \
&& rm -rf \
/tmp/* \
/var/{cache,log}/* \
/var/lib/apt/lists/*
COPY requirements_test.txt /
RUN pip3 install --no-cache-dir -r /requirements_test.txt
RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
export PIP_EXTRA_INDEX_URL="https://www.piwheels.org/simple"; \
fi; \
pip3 install \
--break-system-packages --no-cache-dir -r /requirements_test.txt
VOLUME ["/esphome"]
WORKDIR /esphome

View File

@ -1,3 +1,4 @@
# PYTHON_ARGCOMPLETE_OK
import argparse
import functools
import logging
@ -7,6 +8,8 @@ import sys
import time
from datetime import datetime
import argcomplete
from esphome import const, writer, yaml_util
import esphome.codegen as cg
from esphome.config import iter_components, read_config, strip_default_ids
@ -966,6 +969,7 @@ def parse_args(argv):
# Finally, run the new-style parser again with the possibly swapped arguments,
# and let it error out if the command is unparsable.
parser.set_defaults(deprecated_argv_suggestion=deprecated_argv_suggestion)
argcomplete.autocomplete(parser)
return parser.parse_args(arguments)

View File

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

View File

@ -1,53 +0,0 @@
#include "ade7953.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ade7953 {
static const char *const TAG = "ade7953";
void ADE7953::dump_config() {
ESP_LOGCONFIG(TAG, "ADE7953:");
LOG_PIN(" IRQ Pin: ", irq_pin_);
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage Sensor", this->voltage_sensor_);
LOG_SENSOR(" ", "Current A Sensor", this->current_a_sensor_);
LOG_SENSOR(" ", "Current B Sensor", this->current_b_sensor_);
LOG_SENSOR(" ", "Active Power A Sensor", this->active_power_a_sensor_);
LOG_SENSOR(" ", "Active Power B Sensor", this->active_power_b_sensor_);
}
#define ADE_PUBLISH_(name, val, factor) \
if (err == i2c::ERROR_OK && this->name##_sensor_) { \
float value = (val) / (factor); \
this->name##_sensor_->publish_state(value); \
}
#define ADE_PUBLISH(name, val, factor) ADE_PUBLISH_(name, val, factor)
void ADE7953::update() {
if (!this->is_setup_)
return;
uint32_t val;
i2c::ErrorCode err = ade_read_32_(0x0312, &val);
ADE_PUBLISH(active_power_a, (int32_t) val, 154.0f);
err = ade_read_32_(0x0313, &val);
ADE_PUBLISH(active_power_b, (int32_t) val, 154.0f);
err = ade_read_32_(0x031A, &val);
ADE_PUBLISH(current_a, (uint32_t) val, 100000.0f);
err = ade_read_32_(0x031B, &val);
ADE_PUBLISH(current_b, (uint32_t) val, 100000.0f);
err = ade_read_32_(0x031C, &val);
ADE_PUBLISH(voltage, (uint32_t) val, 26000.0f);
// auto apparent_power_a = this->ade_read_<int32_t>(0x0310);
// auto apparent_power_b = this->ade_read_<int32_t>(0x0311);
// auto reactive_power_a = this->ade_read_<int32_t>(0x0314);
// auto reactive_power_b = this->ade_read_<int32_t>(0x0315);
// auto power_factor_a = this->ade_read_<int16_t>(0x010A);
// auto power_factor_b = this->ade_read_<int16_t>(0x010B);
}
} // namespace ade7953
} // namespace esphome

View File

@ -1,97 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include <vector>
namespace esphome {
namespace ade7953 {
class ADE7953 : public i2c::I2CDevice, public PollingComponent {
public:
void set_irq_pin(InternalGPIOPin *irq_pin) { irq_pin_ = irq_pin; }
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_current_a_sensor(sensor::Sensor *current_a_sensor) { current_a_sensor_ = current_a_sensor; }
void set_current_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; }
void set_active_power_a_sensor(sensor::Sensor *active_power_a_sensor) {
active_power_a_sensor_ = active_power_a_sensor;
}
void set_active_power_b_sensor(sensor::Sensor *active_power_b_sensor) {
active_power_b_sensor_ = active_power_b_sensor;
}
void setup() override {
if (this->irq_pin_ != nullptr) {
this->irq_pin_->setup();
}
this->set_timeout(100, [this]() {
this->ade_write_8_(0x0010, 0x04);
this->ade_write_8_(0x00FE, 0xAD);
this->ade_write_16_(0x0120, 0x0030);
this->is_setup_ = true;
});
}
void dump_config() override;
void update() override;
protected:
i2c::ErrorCode ade_write_8_(uint16_t reg, uint8_t value) {
std::vector<uint8_t> data;
data.push_back(reg >> 8);
data.push_back(reg >> 0);
data.push_back(value);
return write(data.data(), data.size());
}
i2c::ErrorCode ade_write_16_(uint16_t reg, uint16_t value) {
std::vector<uint8_t> data;
data.push_back(reg >> 8);
data.push_back(reg >> 0);
data.push_back(value >> 8);
data.push_back(value >> 0);
return write(data.data(), data.size());
}
i2c::ErrorCode ade_write_32_(uint16_t reg, uint32_t value) {
std::vector<uint8_t> data;
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 write(data.data(), data.size());
}
i2c::ErrorCode ade_read_32_(uint16_t reg, uint32_t *value) {
uint8_t reg_data[2];
reg_data[0] = reg >> 8;
reg_data[1] = reg >> 0;
i2c::ErrorCode err = write(reg_data, 2);
if (err != i2c::ERROR_OK)
return err;
uint8_t recv[4];
err = read(recv, 4);
if (err != i2c::ERROR_OK)
return err;
*value = 0;
*value |= ((uint32_t) recv[0]) << 24;
*value |= ((uint32_t) recv[1]) << 16;
*value |= ((uint32_t) recv[2]) << 8;
*value |= ((uint32_t) recv[3]);
return i2c::ERROR_OK;
}
InternalGPIOPin *irq_pin_{nullptr};
bool is_setup_{false};
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_a_sensor_{nullptr};
sensor::Sensor *current_b_sensor_{nullptr};
sensor::Sensor *active_power_a_sensor_{nullptr};
sensor::Sensor *active_power_b_sensor_{nullptr};
};
} // namespace ade7953
} // namespace esphome

View File

@ -1,90 +1,5 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, i2c
from esphome import pins
from esphome.const import (
CONF_ID,
CONF_IRQ_PIN,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
UNIT_AMPERE,
UNIT_VOLT,
UNIT_WATT,
CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid(
"The ade7953 sensor component has been renamed to ade7953_i2c."
)
DEPENDENCIES = ["i2c"]
ade7953_ns = cg.esphome_ns.namespace("ade7953")
ADE7953 = ade7953_ns.class_("ADE7953", cg.PollingComponent, i2c.I2CDevice)
CONF_CURRENT_A = "current_a"
CONF_CURRENT_B = "current_b"
CONF_ACTIVE_POWER_A = "active_power_a"
CONF_ACTIVE_POWER_B = "active_power_b"
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ADE7953),
cv.Optional(CONF_IRQ_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x38))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if irq_pin_config := config.get(CONF_IRQ_PIN):
irq_pin = await cg.gpio_pin_expression(irq_pin_config)
cg.add(var.set_irq_pin(irq_pin))
for key in [
CONF_VOLTAGE,
CONF_CURRENT_A,
CONF_CURRENT_B,
CONF_ACTIVE_POWER_A,
CONF_ACTIVE_POWER_B,
]:
if key not in config:
continue
conf = config[key]
sens = await sensor.new_sensor(conf)
cg.add(getattr(var, f"set_{key}_sensor")(sens))

View File

@ -0,0 +1,196 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome import pins
from esphome.const import (
CONF_IRQ_PIN,
CONF_VOLTAGE,
CONF_FREQUENCY,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_POWER,
DEVICE_CLASS_REACTIVE_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_FREQUENCY,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
UNIT_HERTZ,
UNIT_AMPERE,
UNIT_VOLT_AMPS,
UNIT_WATT,
UNIT_VOLT_AMPS_REACTIVE,
UNIT_PERCENT,
)
CONF_CURRENT_A = "current_a"
CONF_CURRENT_B = "current_b"
CONF_ACTIVE_POWER_A = "active_power_a"
CONF_ACTIVE_POWER_B = "active_power_b"
CONF_APPARENT_POWER_A = "apparent_power_a"
CONF_APPARENT_POWER_B = "apparent_power_b"
CONF_REACTIVE_POWER_A = "reactive_power_a"
CONF_REACTIVE_POWER_B = "reactive_power_b"
CONF_POWER_FACTOR_A = "power_factor_a"
CONF_POWER_FACTOR_B = "power_factor_b"
CONF_VOLTAGE_PGA_GAIN = "voltage_pga_gain"
CONF_CURRENT_PGA_GAIN_A = "current_pga_gain_a"
CONF_CURRENT_PGA_GAIN_B = "current_pga_gain_b"
CONF_VOLTAGE_GAIN = "voltage_gain"
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"
PGA_GAINS = {
"1x": 0b000,
"2x": 0b001,
"4x": 0b010,
"8x": 0b011,
"16x": 0b100,
"22x": 0b101,
}
ade7953_base_ns = cg.esphome_ns.namespace("ade7953_base")
ADE7953 = ade7953_base_ns.class_("ADE7953", cg.PollingComponent)
ADE7953_CONFIG_SCHEMA = cv.Schema(
{
cv.Optional(CONF_IRQ_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
unit_of_measurement=UNIT_HERTZ,
accuracy_decimals=2,
device_class=DEVICE_CLASS_FREQUENCY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(
unit_of_measurement=UNIT_WATT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_APPARENT_POWER_A): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT_AMPS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_APPARENT_POWER_B): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT_AMPS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_APPARENT_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_REACTIVE_POWER_A): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_REACTIVE_POWER_B): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_REACTIVE_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER_FACTOR_A): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER_FACTOR,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_POWER_FACTOR_B): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER_FACTOR,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(
CONF_VOLTAGE_PGA_GAIN,
default="1x",
): cv.one_of(*PGA_GAINS, lower=True),
cv.Optional(
CONF_CURRENT_PGA_GAIN_A,
default="1x",
): cv.one_of(*PGA_GAINS, lower=True),
cv.Optional(
CONF_CURRENT_PGA_GAIN_B,
default="1x",
): cv.one_of(*PGA_GAINS, lower=True),
cv.Optional(CONF_VOLTAGE_GAIN, default=0x400000): cv.hex_int_range(
min=0x100000, max=0x800000
),
cv.Optional(CONF_CURRENT_GAIN_A, default=0x400000): cv.hex_int_range(
min=0x100000, max=0x800000
),
cv.Optional(CONF_CURRENT_GAIN_B, default=0x400000): cv.hex_int_range(
min=0x100000, max=0x800000
),
cv.Optional(CONF_ACTIVE_POWER_GAIN_A, default=0x400000): cv.hex_int_range(
min=0x100000, max=0x800000
),
cv.Optional(CONF_ACTIVE_POWER_GAIN_B, default=0x400000): cv.hex_int_range(
min=0x100000, max=0x800000
),
}
).extend(cv.polling_component_schema("60s"))
async def register_ade7953(var, config):
await cg.register_component(var, config)
if irq_pin_config := config.get(CONF_IRQ_PIN):
irq_pin = await cg.gpio_pin_expression(irq_pin_config)
cg.add(var.set_irq_pin(irq_pin))
cg.add(var.set_pga_v(PGA_GAINS[config.get(CONF_VOLTAGE_PGA_GAIN)]))
cg.add(var.set_pga_ia(PGA_GAINS[config.get(CONF_CURRENT_PGA_GAIN_A)]))
cg.add(var.set_pga_ib(PGA_GAINS[config.get(CONF_CURRENT_PGA_GAIN_B)]))
cg.add(var.set_vgain(config.get(CONF_VOLTAGE_GAIN)))
cg.add(var.set_aigain(config.get(CONF_CURRENT_GAIN_A)))
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)))
for key in [
CONF_VOLTAGE,
CONF_FREQUENCY,
CONF_CURRENT_A,
CONF_CURRENT_B,
CONF_POWER_FACTOR_A,
CONF_POWER_FACTOR_B,
CONF_APPARENT_POWER_A,
CONF_APPARENT_POWER_B,
CONF_ACTIVE_POWER_A,
CONF_ACTIVE_POWER_B,
CONF_REACTIVE_POWER_A,
CONF_REACTIVE_POWER_B,
]:
if key not in config:
continue
conf = config[key]
sens = await sensor.new_sensor(conf)
cg.add(getattr(var, f"set_{key}_sensor")(sens))

View File

@ -0,0 +1,129 @@
#include "ade7953_base.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ade7953_base {
static const char *const TAG = "ade7953";
void ADE7953::setup() {
if (this->irq_pin_ != nullptr) {
this->irq_pin_->setup();
}
// The chip might take up to 100ms to initialise
this->set_timeout(100, [this]() {
// this->ade_write_8(0x0010, 0x04);
this->ade_write_8(0x00FE, 0xAD);
this->ade_write_16(0x0120, 0x0030);
// Set gains
this->ade_write_8(PGA_V_8, pga_v_);
this->ade_write_8(PGA_IA_8, pga_ia_);
this->ade_write_8(PGA_IB_8, pga_ib_);
this->ade_write_32(AVGAIN_32, vgain_);
this->ade_write_32(AIGAIN_32, aigain_);
this->ade_write_32(BIGAIN_32, bigain_);
this->ade_write_32(AWGAIN_32, awgain_);
this->ade_write_32(BWGAIN_32, bwgain_);
// Read back gains for debugging
this->ade_read_8(PGA_V_8, &pga_v_);
this->ade_read_8(PGA_IA_8, &pga_ia_);
this->ade_read_8(PGA_IB_8, &pga_ib_);
this->ade_read_32(AVGAIN_32, &vgain_);
this->ade_read_32(AIGAIN_32, &aigain_);
this->ade_read_32(BIGAIN_32, &bigain_);
this->ade_read_32(AWGAIN_32, &awgain_);
this->ade_read_32(BWGAIN_32, &bwgain_);
this->is_setup_ = true;
});
}
void ADE7953::dump_config() {
LOG_PIN(" IRQ Pin: ", irq_pin_);
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Voltage Sensor", this->voltage_sensor_);
LOG_SENSOR(" ", "Current A Sensor", this->current_a_sensor_);
LOG_SENSOR(" ", "Current B Sensor", this->current_b_sensor_);
LOG_SENSOR(" ", "Power Factor A Sensor", this->power_factor_a_sensor_);
LOG_SENSOR(" ", "Power Factor B Sensor", this->power_factor_b_sensor_);
LOG_SENSOR(" ", "Apparent Power A Sensor", this->apparent_power_a_sensor_);
LOG_SENSOR(" ", "Apparent Power B Sensor", this->apparent_power_b_sensor_);
LOG_SENSOR(" ", "Active Power A Sensor", this->active_power_a_sensor_);
LOG_SENSOR(" ", "Active Power B Sensor", this->active_power_b_sensor_);
LOG_SENSOR(" ", "Rective Power A Sensor", this->reactive_power_a_sensor_);
LOG_SENSOR(" ", "Reactive Power B Sensor", this->reactive_power_b_sensor_);
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_);
ESP_LOGCONFIG(TAG, " VGAIN_32: 0x%08jX", (uintmax_t) vgain_);
ESP_LOGCONFIG(TAG, " AIGAIN_32: 0x%08jX", (uintmax_t) aigain_);
ESP_LOGCONFIG(TAG, " BIGAIN_32: 0x%08jX", (uintmax_t) bigain_);
ESP_LOGCONFIG(TAG, " AWGAIN_32: 0x%08jX", (uintmax_t) awgain_);
ESP_LOGCONFIG(TAG, " BWGAIN_32: 0x%08jX", (uintmax_t) bwgain_);
}
#define ADE_PUBLISH_(name, val, factor) \
if (err == 0 && this->name##_sensor_) { \
float value = (val) / (factor); \
this->name##_sensor_->publish_state(value); \
}
#define ADE_PUBLISH(name, val, factor) ADE_PUBLISH_(name, val, factor)
void ADE7953::update() {
if (!this->is_setup_)
return;
bool err;
uint32_t interrupts_a = 0;
uint32_t interrupts_b = 0;
if (this->irq_pin_ != nullptr) {
// Read and reset interrupts
this->ade_read_32(0x032E, &interrupts_a);
this->ade_read_32(0x0331, &interrupts_b);
}
uint32_t val;
uint16_t val_16;
// Power factor
err = this->ade_read_16(0x010A, &val_16);
ADE_PUBLISH(power_factor_a, (int16_t) val_16, (0x7FFF / 100.0f));
err = this->ade_read_16(0x010B, &val_16);
ADE_PUBLISH(power_factor_b, (int16_t) val_16, (0x7FFF / 100.0f));
// 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);
// 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);
// 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);
// Current
err = this->ade_read_32(0x031A, &val);
ADE_PUBLISH(current_a, (uint32_t) val, 100000.0f);
err = this->ade_read_32(0x031B, &val);
ADE_PUBLISH(current_b, (uint32_t) val, 100000.0f);
// Voltage
err = this->ade_read_32(0x031C, &val);
ADE_PUBLISH(voltage, (uint32_t) val, 26000.0f);
// Frequency
err = this->ade_read_16(0x010E, &val_16);
ADE_PUBLISH(frequency, 223750.0f, 1 + val_16);
}
} // namespace ade7953_base
} // namespace esphome

View File

@ -0,0 +1,121 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/sensor/sensor.h"
#include <vector>
namespace esphome {
namespace ade7953_base {
static const uint8_t PGA_V_8 =
0x007; // PGA_V, (R/W) Default: 0x00, Unsigned, Voltage channel gain configuration (Bits[2:0])
static const uint8_t PGA_IA_8 =
0x008; // PGA_IA, (R/W) Default: 0x00, Unsigned, Current Channel A gain configuration (Bits[2:0])
static const uint8_t PGA_IB_8 =
0x009; // PGA_IB, (R/W) Default: 0x00, Unsigned, Current Channel B gain configuration (Bits[2:0])
static const uint32_t AIGAIN_32 =
0x380; // AIGAIN, (R/W) Default: 0x400000, Unsigned,Current channel gain (Current Channel A)(32 bit)
static const uint32_t AVGAIN_32 = 0x381; // AVGAIN, (R/W) Default: 0x400000, Unsigned,Voltage channel gain(32 bit)
static const uint32_t AWGAIN_32 =
0x382; // AWGAIN, (R/W) Default: 0x400000, Unsigned,Active power gain (Current Channel A)(32 bit)
static const uint32_t AVARGAIN_32 =
0x383; // AVARGAIN, (R/W) Default: 0x400000, Unsigned, Reactive power gain (Current Channel A)(32 bit)
static const uint32_t AVAGAIN_32 =
0x384; // AVAGAIN, (R/W) Default: 0x400000, Unsigned,Apparent power gain (Current Channel A)(32 bit)
static const uint32_t BIGAIN_32 =
0x38C; // BIGAIN, (R/W) Default: 0x400000, Unsigned,Current channel gain (Current Channel B)(32 bit)
static const uint32_t BVGAIN_32 = 0x38D; // BVGAIN, (R/W) Default: 0x400000, Unsigned,Voltage channel gain(32 bit)
static const uint32_t BWGAIN_32 =
0x38E; // BWGAIN, (R/W) Default: 0x400000, Unsigned,Active power gain (Current Channel B)(32 bit)
static const uint32_t BVARGAIN_32 =
0x38F; // BVARGAIN, (R/W) Default: 0x400000, Unsigned, Reactive power gain (Current Channel B)(32 bit)
static const uint32_t BVAGAIN_32 =
0x390; // BVAGAIN, (R/W) Default: 0x400000, Unsigned,Apparent power gain (Current Channel B)(32 bit)
class ADE7953 : public PollingComponent, public sensor::Sensor {
public:
void set_irq_pin(InternalGPIOPin *irq_pin) { irq_pin_ = irq_pin; }
// Set PGA input gains: 0 1x, 1 2x, 0b10 4x
void set_pga_v(uint8_t pga_v) { pga_v_ = pga_v; }
void set_pga_ia(uint8_t pga_ia) { pga_ia_ = pga_ia; }
void set_pga_ib(uint8_t pga_ib) { pga_ib_ = pga_ib; }
// Set input gains
void set_vgain(uint32_t vgain) { vgain_ = vgain; }
void set_aigain(uint32_t aigain) { aigain_ = aigain; }
void set_bigain(uint32_t bigain) { bigain_ = bigain; }
void set_awgain(uint32_t awgain) { awgain_ = awgain; }
void set_bwgain(uint32_t bwgain) { bwgain_ = bwgain; }
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; }
void set_power_factor_a_sensor(sensor::Sensor *power_factor_a) { power_factor_a_sensor_ = power_factor_a; }
void set_power_factor_b_sensor(sensor::Sensor *power_factor_b) { power_factor_b_sensor_ = power_factor_b; }
void set_current_a_sensor(sensor::Sensor *current_a_sensor) { current_a_sensor_ = current_a_sensor; }
void set_current_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; }
void set_apparent_power_a_sensor(sensor::Sensor *apparent_power_a) { apparent_power_a_sensor_ = apparent_power_a; }
void set_apparent_power_b_sensor(sensor::Sensor *apparent_power_b) { apparent_power_b_sensor_ = apparent_power_b; }
void set_active_power_a_sensor(sensor::Sensor *active_power_a_sensor) {
active_power_a_sensor_ = active_power_a_sensor;
}
void set_active_power_b_sensor(sensor::Sensor *active_power_b_sensor) {
active_power_b_sensor_ = active_power_b_sensor;
}
void set_reactive_power_a_sensor(sensor::Sensor *reactive_power_a) { reactive_power_a_sensor_ = reactive_power_a; }
void set_reactive_power_b_sensor(sensor::Sensor *reactive_power_b) { reactive_power_b_sensor_ = reactive_power_b; }
void setup() override;
void dump_config() override;
void update() override;
protected:
InternalGPIOPin *irq_pin_{nullptr};
bool is_setup_{false};
sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *frequency_sensor_{nullptr};
sensor::Sensor *current_a_sensor_{nullptr};
sensor::Sensor *current_b_sensor_{nullptr};
sensor::Sensor *apparent_power_a_sensor_{nullptr};
sensor::Sensor *apparent_power_b_sensor_{nullptr};
sensor::Sensor *active_power_a_sensor_{nullptr};
sensor::Sensor *active_power_b_sensor_{nullptr};
sensor::Sensor *reactive_power_a_sensor_{nullptr};
sensor::Sensor *reactive_power_b_sensor_{nullptr};
sensor::Sensor *power_factor_a_sensor_{nullptr};
sensor::Sensor *power_factor_b_sensor_{nullptr};
uint8_t pga_v_;
uint8_t pga_ia_;
uint8_t pga_ib_;
uint32_t vgain_;
uint32_t aigain_;
uint32_t bigain_;
uint32_t awgain_;
uint32_t bwgain_;
virtual bool ade_write_8(uint16_t reg, uint8_t value) = 0;
virtual bool ade_write_16(uint16_t reg, uint16_t value) = 0;
virtual bool ade_write_32(uint16_t reg, uint32_t value) = 0;
virtual bool ade_read_8(uint16_t reg, uint8_t *value) = 0;
virtual bool ade_read_16(uint16_t reg, uint16_t *value) = 0;
virtual bool ade_read_32(uint16_t reg, uint32_t *value) = 0;
};
} // namespace ade7953_base
} // namespace esphome

View File

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

View File

@ -0,0 +1,80 @@
#include "ade7953_i2c.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace ade7953_i2c {
static const char *const TAG = "ade7953";
void AdE7953I2c::dump_config() {
ESP_LOGCONFIG(TAG, "ADE7953_i2c:");
LOG_I2C_DEVICE(this);
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;
}
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;
}
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;
}
bool AdE7953I2c::ade_read_8(uint16_t reg, uint8_t *value) {
uint8_t reg_data[2];
reg_data[0] = reg >> 8;
reg_data[1] = reg >> 0;
i2c::ErrorCode err = this->write(reg_data, 2);
if (err != i2c::ERROR_OK)
return true;
err = this->read(value, 1);
return (err != i2c::ERROR_OK);
}
bool AdE7953I2c::ade_read_16(uint16_t reg, uint16_t *value) {
uint8_t reg_data[2];
reg_data[0] = reg >> 8;
reg_data[1] = reg >> 0;
i2c::ErrorCode err = this->write(reg_data, 2);
if (err != i2c::ERROR_OK)
return true;
uint8_t recv[2];
err = this->read(recv, 2);
if (err != i2c::ERROR_OK)
return true;
*value = encode_uint16(recv[0], recv[1]);
return false;
}
bool AdE7953I2c::ade_read_32(uint16_t reg, uint32_t *value) {
uint8_t reg_data[2];
reg_data[0] = reg >> 8;
reg_data[1] = reg >> 0;
i2c::ErrorCode err = this->write(reg_data, 2);
if (err != i2c::ERROR_OK)
return true;
uint8_t recv[4];
err = this->read(recv, 4);
if (err != i2c::ERROR_OK)
return true;
*value = encode_uint32(recv[0], recv[1], recv[2], recv[3]);
return false;
}
} // namespace ade7953_i2c
} // namespace esphome

View File

@ -0,0 +1,28 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/ade7953_base/ade7953_base.h"
#include <vector>
namespace esphome {
namespace ade7953_i2c {
class AdE7953I2c : public ade7953_base::ADE7953, public i2c::I2CDevice {
public:
void dump_config() override;
protected:
bool ade_write_8(uint16_t reg, uint8_t value) override;
bool ade_write_16(uint16_t reg, uint16_t value) override;
bool ade_write_32(uint16_t reg, uint32_t value) override;
bool ade_read_8(uint16_t reg, uint8_t *value) override;
bool ade_read_16(uint16_t reg, uint16_t *value) override;
bool ade_read_32(uint16_t reg, uint32_t *value) override;
};
} // namespace ade7953_i2c
} // namespace esphome

View File

@ -0,0 +1,27 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, ade7953_base
from esphome.const import CONF_ID
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["ade7953_base"]
ade7953_ns = cg.esphome_ns.namespace("ade7953_i2c")
ADE7953 = ade7953_ns.class_("AdE7953I2c", ade7953_base.ADE7953, i2c.I2CDevice)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ADE7953),
}
)
.extend(ade7953_base.ADE7953_CONFIG_SCHEMA)
.extend(i2c.i2c_device_schema(0x38))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await i2c.register_i2c_device(var, config)
await ade7953_base.register_ade7953(var, config)

View File

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

View File

@ -0,0 +1,81 @@
#include "ade7953_spi.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace ade7953_spi {
static const char *const TAG = "ade7953";
void AdE7953Spi::setup() {
this->spi_setup();
ade7953_base::ADE7953::setup();
}
void AdE7953Spi::dump_config() {
ESP_LOGCONFIG(TAG, "ADE7953_spi:");
LOG_PIN(" CS Pin: ", this->cs_);
ade7953_base::ADE7953::dump_config();
}
bool AdE7953Spi::ade_write_8(uint16_t reg, uint8_t value) {
this->enable();
this->write_byte16(reg);
this->transfer_byte(0);
this->transfer_byte(value);
this->disable();
return false;
}
bool AdE7953Spi::ade_write_16(uint16_t reg, uint16_t value) {
this->enable();
this->write_byte16(reg);
this->transfer_byte(0);
this->write_byte16(value);
this->disable();
return false;
}
bool AdE7953Spi::ade_write_32(uint16_t reg, uint32_t value) {
this->enable();
this->write_byte16(reg);
this->transfer_byte(0);
this->write_byte16(value >> 16);
this->write_byte16(value & 0xFFFF);
this->disable();
return false;
}
bool AdE7953Spi::ade_read_8(uint16_t reg, uint8_t *value) {
this->enable();
this->write_byte16(reg);
this->transfer_byte(0x80);
*value = this->read_byte();
this->disable();
return false;
}
bool AdE7953Spi::ade_read_16(uint16_t reg, uint16_t *value) {
this->enable();
this->write_byte16(reg);
this->transfer_byte(0x80);
uint8_t recv[2];
this->read_array(recv, 4);
*value = encode_uint16(recv[0], recv[1]);
this->disable();
return false;
}
bool AdE7953Spi::ade_read_32(uint16_t reg, uint32_t *value) {
this->enable();
this->write_byte16(reg);
this->transfer_byte(0x80);
uint8_t recv[4];
this->read_array(recv, 4);
*value = encode_uint32(recv[0], recv[1], recv[2], recv[3]);
this->disable();
return false;
}
} // namespace ade7953_spi
} // namespace esphome

View File

@ -0,0 +1,32 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/spi/spi.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/ade7953_base/ade7953_base.h"
#include <vector>
namespace esphome {
namespace ade7953_spi {
class AdE7953Spi : public ade7953_base::ADE7953,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_1MHZ> {
public:
void setup() override;
void dump_config() override;
protected:
bool ade_write_8(uint16_t reg, uint8_t value) override;
bool ade_write_16(uint16_t reg, uint16_t value) override;
bool ade_write_32(uint16_t reg, uint32_t value) override;
bool ade_read_8(uint16_t reg, uint8_t *value) override;
bool ade_read_16(uint16_t reg, uint16_t *value) override;
bool ade_read_32(uint16_t reg, uint32_t *value) override;
};
} // namespace ade7953_spi
} // namespace esphome

View File

@ -0,0 +1,27 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import spi, ade7953_base
from esphome.const import CONF_ID
DEPENDENCIES = ["spi"]
AUTO_LOAD = ["ade7953_base"]
ade7953_ns = cg.esphome_ns.namespace("ade7953_spi")
ADE7953 = ade7953_ns.class_("AdE7953Spi", ade7953_base.ADE7953, spi.SPIDevice)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ADE7953),
}
)
.extend(ade7953_base.ADE7953_CONFIG_SCHEMA)
.extend(spi.spi_device_schema())
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await spi.register_spi_device(var, config)
await ade7953_base.register_ade7953(var, config)

View File

@ -15,6 +15,7 @@
#include "aht10.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include <cinttypes>
namespace esphome {
namespace aht10 {
@ -72,7 +73,7 @@ void AHT10Component::update() {
delay_ms = AHT10_HUMIDITY_DELAY;
bool success = false;
for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis());
ESP_LOGVV(TAG, "Attempt %d at %6" PRIu32, i, millis());
delay(delay_ms);
if (this->read(data, 6) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
@ -96,7 +97,7 @@ void AHT10Component::update() {
}
} else {
// data is valid, we can break the loop
ESP_LOGVV(TAG, "Answer at %6u", millis());
ESP_LOGVV(TAG, "Answer at %6" PRIu32, millis());
success = true;
break;
}

View File

@ -39,6 +39,7 @@ service APIConnection {
rpc camera_image (CameraImageRequest) returns (void) {}
rpc climate_command (ClimateCommandRequest) returns (void) {}
rpc number_command (NumberCommandRequest) returns (void) {}
rpc text_command (TextCommandRequest) returns (void) {}
rpc select_command (SelectCommandRequest) returns (void) {}
rpc button_command (ButtonCommandRequest) returns (void) {}
rpc lock_command (LockCommandRequest) returns (void) {}
@ -216,6 +217,8 @@ message DeviceInfoResponse {
string friendly_name = 13;
uint32 voice_assistant_version = 14;
string suggested_area = 16;
}
message ListEntitiesRequest {
@ -1536,3 +1539,48 @@ message AlarmControlPanelCommandRequest {
AlarmControlPanelStateCommand command = 2;
string code = 3;
}
// ===================== TEXT =====================
enum TextMode {
TEXT_MODE_TEXT = 0;
TEXT_MODE_PASSWORD = 1;
}
message ListEntitiesTextResponse {
option (id) = 97;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_TEXT";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
uint32 min_length = 8;
uint32 max_length = 9;
string pattern = 10;
TextMode mode = 11;
}
message TextStateResponse {
option (id) = 98;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_TEXT";
option (no_delay) = true;
fixed32 key = 1;
string state = 2;
// If the Text does not have a valid state yet.
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
bool missing_state = 3;
}
message TextCommandRequest {
option (id) = 99;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_TEXT";
option (no_delay) = true;
fixed32 key = 1;
string state = 2;
}

View File

@ -1,6 +1,7 @@
#include "api_connection.h"
#include <cerrno>
#include <cinttypes>
#include <utility>
#include "esphome/components/network/util.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/hal.h"
@ -664,6 +665,44 @@ void APIConnection::number_command(const NumberCommandRequest &msg) {
}
#endif
#ifdef USE_TEXT
bool APIConnection::send_text_state(text::Text *text, std::string state) {
if (!this->state_subscription_)
return false;
TextStateResponse resp{};
resp.key = text->get_object_id_hash();
resp.state = std::move(state);
resp.missing_state = !text->has_state();
return this->send_text_state_response(resp);
}
bool APIConnection::send_text_info(text::Text *text) {
ListEntitiesTextResponse msg;
msg.key = text->get_object_id_hash();
msg.object_id = text->get_object_id();
msg.name = text->get_name();
msg.icon = text->get_icon();
msg.disabled_by_default = text->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(text->get_entity_category());
msg.mode = static_cast<enums::TextMode>(text->traits.get_mode());
msg.min_length = text->traits.get_min_length();
msg.max_length = text->traits.get_max_length();
msg.pattern = text->traits.get_pattern();
return this->send_list_entities_text_response(msg);
}
void APIConnection::text_command(const TextCommandRequest &msg) {
text::Text *text = App.get_text_by_key(msg.key);
if (text == nullptr)
return;
auto call = text->make_call();
call.set_value(msg.state);
call.perform();
}
#endif
#ifdef USE_SELECT
bool APIConnection::send_select_state(select::Select *select, std::string state) {
if (!this->state_subscription_)
@ -1063,6 +1102,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
resp.uses_password = this->parent_->uses_password();
resp.name = App.get_name();
resp.friendly_name = App.get_friendly_name();
resp.suggested_area = App.get_area();
resp.mac_address = get_mac_address_pretty();
resp.esphome_version = ESPHOME_VERSION;
resp.compilation_time = App.get_compilation_time();

View File

@ -72,6 +72,11 @@ class APIConnection : public APIServerConnection {
bool send_number_info(number::Number *number);
void number_command(const NumberCommandRequest &msg) override;
#endif
#ifdef USE_TEXT
bool send_text_state(text::Text *text, std::string state);
bool send_text_info(text::Text *text);
void text_command(const TextCommandRequest &msg) override;
#endif
#ifdef USE_SELECT
bool send_select_state(select::Select *select, std::string state);
bool send_select_info(select::Select *select);

View File

@ -512,6 +512,18 @@ const char *proto_enum_to_string<enums::AlarmControlPanelStateCommand>(enums::Al
}
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<> const char *proto_enum_to_string<enums::TextMode>(enums::TextMode value) {
switch (value) {
case enums::TEXT_MODE_TEXT:
return "TEXT_MODE_TEXT";
case enums::TEXT_MODE_PASSWORD:
return "TEXT_MODE_PASSWORD";
default:
return "UNKNOWN";
}
}
#endif
bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@ -749,6 +761,10 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
this->friendly_name = value.as_string();
return true;
}
case 16: {
this->suggested_area = value.as_string();
return true;
}
default:
return false;
}
@ -769,6 +785,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(12, this->manufacturer);
buffer.encode_string(13, this->friendly_name);
buffer.encode_uint32(14, this->voice_assistant_version);
buffer.encode_string(16, this->suggested_area);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoResponse::dump_to(std::string &out) const {
@ -837,6 +854,10 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
sprintf(buffer, "%" PRIu32, this->voice_assistant_version);
out.append(buffer);
out.append("\n");
out.append(" suggested_area: ");
out.append("'").append(this->suggested_area).append("'");
out.append("\n");
out.append("}");
}
#endif
@ -6795,6 +6816,227 @@ void AlarmControlPanelCommandRequest::dump_to(std::string &out) const {
out.append("}");
}
#endif
bool ListEntitiesTextResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->disabled_by_default = value.as_bool();
return true;
}
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 8: {
this->min_length = value.as_uint32();
return true;
}
case 9: {
this->max_length = value.as_uint32();
return true;
}
case 11: {
this->mode = value.as_enum<enums::TextMode>();
return true;
}
default:
return false;
}
}
bool ListEntitiesTextResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->object_id = value.as_string();
return true;
}
case 3: {
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
}
case 10: {
this->pattern = value.as_string();
return true;
}
default:
return false;
}
}
bool ListEntitiesTextResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_uint32(8, this->min_length);
buffer.encode_uint32(9, this->max_length);
buffer.encode_string(10, this->pattern);
buffer.encode_enum<enums::TextMode>(11, this->mode);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesTextResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesTextResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
out.append("\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" min_length: ");
sprintf(buffer, "%" PRIu32, this->min_length);
out.append(buffer);
out.append("\n");
out.append(" max_length: ");
sprintf(buffer, "%" PRIu32, this->max_length);
out.append(buffer);
out.append("\n");
out.append(" pattern: ");
out.append("'").append(this->pattern).append("'");
out.append("\n");
out.append(" mode: ");
out.append(proto_enum_to_string<enums::TextMode>(this->mode));
out.append("\n");
out.append("}");
}
#endif
bool TextStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 3: {
this->missing_state = value.as_bool();
return true;
}
default:
return false;
}
}
bool TextStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->state = value.as_string();
return true;
}
default:
return false;
}
}
bool TextStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void TextStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_string(2, this->state);
buffer.encode_bool(3, this->missing_state);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void TextStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("TextStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" state: ");
out.append("'").append(this->state).append("'");
out.append("\n");
out.append(" missing_state: ");
out.append(YESNO(this->missing_state));
out.append("\n");
out.append("}");
}
#endif
bool TextCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->state = value.as_string();
return true;
}
default:
return false;
}
}
bool TextCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void TextCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_string(2, this->state);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void TextCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("TextCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" state: ");
out.append("'").append(this->state).append("'");
out.append("\n");
out.append("}");
}
#endif
} // namespace api
} // namespace esphome

View File

@ -208,6 +208,10 @@ enum AlarmControlPanelStateCommand : uint32_t {
ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS = 5,
ALARM_CONTROL_PANEL_TRIGGER = 6,
};
enum TextMode : uint32_t {
TEXT_MODE_TEXT = 0,
TEXT_MODE_PASSWORD = 1,
};
} // namespace enums
@ -324,6 +328,7 @@ class DeviceInfoResponse : public ProtoMessage {
std::string manufacturer{};
std::string friendly_name{};
uint32_t voice_assistant_version{0};
std::string suggested_area{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@ -1778,6 +1783,57 @@ class AlarmControlPanelCommandRequest : public ProtoMessage {
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesTextResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
uint32_t min_length{0};
uint32_t max_length{0};
std::string pattern{};
enums::TextMode mode{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class TextStateResponse : public ProtoMessage {
public:
uint32_t key{0};
std::string state{};
bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class TextCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
std::string state{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
} // namespace api
} // namespace esphome

View File

@ -495,6 +495,24 @@ bool APIServerConnectionBase::send_alarm_control_panel_state_response(const Alar
#endif
#ifdef USE_ALARM_CONTROL_PANEL
#endif
#ifdef USE_TEXT
bool APIServerConnectionBase::send_list_entities_text_response(const ListEntitiesTextResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_text_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesTextResponse>(msg, 97);
}
#endif
#ifdef USE_TEXT
bool APIServerConnectionBase::send_text_state_response(const TextStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_text_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<TextStateResponse>(msg, 98);
}
#endif
#ifdef USE_TEXT
#endif
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
switch (msg_type) {
case 1: {
@ -913,6 +931,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_alarm_control_panel_command_request: %s", msg.dump().c_str());
#endif
this->on_alarm_control_panel_command_request(msg);
#endif
break;
}
case 99: {
#ifdef USE_TEXT
TextCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_text_command_request: %s", msg.dump().c_str());
#endif
this->on_text_command_request(msg);
#endif
break;
}
@ -1124,6 +1153,19 @@ void APIServerConnection::on_number_command_request(const NumberCommandRequest &
this->number_command(msg);
}
#endif
#ifdef USE_TEXT
void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->text_command(msg);
}
#endif
#ifdef USE_SELECT
void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) {
if (!this->is_connection_setup()) {

View File

@ -248,6 +248,15 @@ class APIServerConnectionBase : public ProtoService {
#endif
#ifdef USE_ALARM_CONTROL_PANEL
virtual void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
#endif
#ifdef USE_TEXT
bool send_list_entities_text_response(const ListEntitiesTextResponse &msg);
#endif
#ifdef USE_TEXT
bool send_text_state_response(const TextStateResponse &msg);
#endif
#ifdef USE_TEXT
virtual void on_text_command_request(const TextCommandRequest &value){};
#endif
protected:
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
@ -288,6 +297,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_NUMBER
virtual void number_command(const NumberCommandRequest &msg) = 0;
#endif
#ifdef USE_TEXT
virtual void text_command(const TextCommandRequest &msg) = 0;
#endif
#ifdef USE_SELECT
virtual void select_command(const SelectCommandRequest &msg) = 0;
#endif
@ -371,6 +383,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_NUMBER
void on_number_command_request(const NumberCommandRequest &msg) override;
#endif
#ifdef USE_TEXT
void on_text_command_request(const TextCommandRequest &msg) override;
#endif
#ifdef USE_SELECT
void on_select_command_request(const SelectCommandRequest &msg) override;
#endif

View File

@ -255,6 +255,15 @@ void APIServer::on_number_update(number::Number *obj, float state) {
}
#endif
#ifdef USE_TEXT
void APIServer::on_text_update(text::Text *obj, const std::string &state) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_text_state(obj, state);
}
#endif
#ifdef USE_SELECT
void APIServer::on_select_update(select::Select *obj, const std::string &state, size_t index) {
if (obj->is_internal())

View File

@ -66,6 +66,9 @@ class APIServer : public Component, public Controller {
#ifdef USE_NUMBER
void on_number_update(number::Number *obj, float state) override;
#endif
#ifdef USE_TEXT
void on_text_update(text::Text *obj, const std::string &state) override;
#endif
#ifdef USE_SELECT
void on_select_update(select::Select *obj, const std::string &state, size_t index) override;
#endif

View File

@ -1,71 +1,65 @@
from __future__ import annotations
import asyncio
import logging
from datetime import datetime
from typing import Optional
from typing import Any
from aioesphomeapi import APIClient, ReconnectLogic, APIConnectionError, LogLevel
import zeroconf
from aioesphomeapi import APIClient
from aioesphomeapi.api_pb2 import SubscribeLogsResponse
from aioesphomeapi.log_runner import async_run
from zeroconf.asyncio import AsyncZeroconf
from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__
from esphome.core import CORE
from esphome.const import CONF_KEY, CONF_PORT, CONF_PASSWORD, __version__
from esphome.util import safe_print
from . import CONF_ENCRYPTION
_LOGGER = logging.getLogger(__name__)
async def async_run_logs(config, address):
"""Run the logs command in the event loop."""
conf = config["api"]
port: int = int(conf[CONF_PORT])
password: str = conf[CONF_PASSWORD]
noise_psk: Optional[str] = None
noise_psk: str | None = None
if CONF_ENCRYPTION in conf:
noise_psk = conf[CONF_ENCRYPTION][CONF_KEY]
_LOGGER.info("Starting log output from %s using esphome API", address)
aiozc = AsyncZeroconf()
cli = APIClient(
address,
port,
password,
client_info=f"ESPHome Logs {__version__}",
noise_psk=noise_psk,
zeroconf_instance=aiozc.zeroconf,
)
first_connect = True
dashboard = CORE.dashboard
def on_log(msg):
time_ = datetime.now().time().strftime("[%H:%M:%S]")
text = msg.message.decode("utf8", "backslashreplace")
safe_print(time_ + text)
async def on_connect():
nonlocal first_connect
try:
await cli.subscribe_logs(
on_log,
log_level=LogLevel.LOG_LEVEL_VERY_VERBOSE,
dump_config=first_connect,
)
first_connect = False
except APIConnectionError:
cli.disconnect()
async def on_disconnect(expected_disconnect: bool) -> None:
_LOGGER.warning("Disconnected from API")
zc = zeroconf.Zeroconf()
reconnect = ReconnectLogic(
client=cli,
on_connect=on_connect,
on_disconnect=on_disconnect,
zeroconf_instance=zc,
)
await reconnect.start()
def on_log(msg: SubscribeLogsResponse) -> None:
"""Handle a new log message."""
time_ = datetime.now()
message: bytes = msg.message
text = message.decode("utf8", "backslashreplace")
if dashboard:
text = text.replace("\033", "\\033")
print(f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}]{text}")
stop = await async_run(cli, on_log, aio_zeroconf_instance=aiozc)
try:
while True:
await asyncio.sleep(60)
finally:
await aiozc.async_close()
await stop()
def run_logs(config: dict[str, Any], address: str) -> None:
"""Run the logs command."""
try:
asyncio.run(async_run_logs(config, address))
except KeyboardInterrupt:
await reconnect.stop()
zc.close()
def run_logs(config, address):
asyncio.run(async_run_logs(config, address))
pass

View File

@ -60,6 +60,10 @@ bool ListEntitiesIterator::on_climate(climate::Climate *climate) { return this->
bool ListEntitiesIterator::on_number(number::Number *number) { return this->client_->send_number_info(number); }
#endif
#ifdef USE_TEXT
bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); }
#endif
#ifdef USE_SELECT
bool ListEntitiesIterator::on_select(select::Select *select) { return this->client_->send_select_info(select); }
#endif

View File

@ -46,6 +46,9 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_NUMBER
bool on_number(number::Number *number) override;
#endif
#ifdef USE_TEXT
bool on_text(text::Text *text) override;
#endif
#ifdef USE_SELECT
bool on_select(select::Select *select) override;
#endif

View File

@ -42,6 +42,9 @@ bool InitialStateIterator::on_number(number::Number *number) {
return this->client_->send_number_state(number, number->state);
}
#endif
#ifdef USE_TEXT
bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text, text->state); }
#endif
#ifdef USE_SELECT
bool InitialStateIterator::on_select(select::Select *select) {
return this->client_->send_select_state(select, select->state);

View File

@ -43,6 +43,9 @@ class InitialStateIterator : public ComponentIterator {
#ifdef USE_NUMBER
bool on_number(number::Number *number) override;
#endif
#ifdef USE_TEXT
bool on_text(text::Text *text) override;
#endif
#ifdef USE_SELECT
bool on_select(select::Select *select) override;
#endif

View File

@ -5,7 +5,7 @@ namespace esphome {
namespace api {
template<> bool get_execute_arg_value<bool>(const ExecuteServiceArgument &arg) { return arg.bool_; }
template<> int get_execute_arg_value<int>(const ExecuteServiceArgument &arg) {
template<> int32_t get_execute_arg_value<int32_t>(const ExecuteServiceArgument &arg) {
if (arg.legacy_int != 0)
return arg.legacy_int;
return arg.int_;
@ -26,11 +26,13 @@ template<> std::vector<std::string> get_execute_arg_value<std::vector<std::strin
}
template<> enums::ServiceArgType to_service_arg_type<bool>() { return enums::SERVICE_ARG_TYPE_BOOL; }
template<> enums::ServiceArgType to_service_arg_type<int>() { return enums::SERVICE_ARG_TYPE_INT; }
template<> enums::ServiceArgType to_service_arg_type<int32_t>() { return enums::SERVICE_ARG_TYPE_INT; }
template<> enums::ServiceArgType to_service_arg_type<float>() { return enums::SERVICE_ARG_TYPE_FLOAT; }
template<> enums::ServiceArgType to_service_arg_type<std::string>() { return enums::SERVICE_ARG_TYPE_STRING; }
template<> enums::ServiceArgType to_service_arg_type<std::vector<bool>>() { return enums::SERVICE_ARG_TYPE_BOOL_ARRAY; }
template<> enums::ServiceArgType to_service_arg_type<std::vector<int>>() { return enums::SERVICE_ARG_TYPE_INT_ARRAY; }
template<> enums::ServiceArgType to_service_arg_type<std::vector<int32_t>>() {
return enums::SERVICE_ARG_TYPE_INT_ARRAY;
}
template<> enums::ServiceArgType to_service_arg_type<std::vector<float>>() {
return enums::SERVICE_ARG_TYPE_FLOAT_ARRAY;
}

View File

@ -1,6 +1,7 @@
#include "atm90e32.h"
#include "atm90e32_reg.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace atm90e32 {
@ -173,7 +174,7 @@ uint16_t ATM90E32Component::read16_(uint16_t a_register) {
this->disable();
output = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF);
ESP_LOGVV(TAG, "read16_ 0x%04X output 0x%04X", a_register, output);
ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
return output;
}
@ -182,8 +183,10 @@ int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
uint16_t val_l = this->read16_(addr_l);
int32_t val = (val_h << 16) | val_l;
ESP_LOGVV(TAG, "read32_ addr_h 0x%04X val_h 0x%04X addr_l 0x%04X val_l 0x%04X = %d", addr_h, val_h, addr_l, val_l,
val);
ESP_LOGVV(TAG,
"read32_ addr_h 0x%04" PRIX16 " val_h 0x%04" PRIX16 " addr_l 0x%04" PRIX16 " val_l 0x%04" PRIX16
" = %" PRId32,
addr_h, val_h, addr_l, val_l, val);
return val;
}
@ -192,7 +195,7 @@ 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%04X val 0x%04X", a_register, val);
ESP_LOGVV(TAG, "write16_ 0x%04" PRIX16 " val 0x%04" PRIX16, a_register, val);
this->enable();
delayMicroseconds(10);
this->write_byte(addrh);

View File

@ -22,7 +22,7 @@ void binary_sensor::MultiClickTrigger::on_state_(bool state) {
// Start matching
MultiClickTriggerEvent evt = this->timing_[0];
if (evt.state == state) {
ESP_LOGV(TAG, "START min=%u max=%u", evt.min_length, evt.max_length);
ESP_LOGV(TAG, "START min=%" PRIu32 " max=%" PRIu32, evt.min_length, evt.max_length);
ESP_LOGV(TAG, "Multi Click: Starting multi click action!");
this->at_index_ = 1;
if (this->timing_.size() == 1 && evt.max_length == 4294967294UL) {
@ -51,15 +51,15 @@ void binary_sensor::MultiClickTrigger::on_state_(bool state) {
MultiClickTriggerEvent evt = this->timing_[*this->at_index_];
if (evt.max_length != 4294967294UL) {
ESP_LOGV(TAG, "A i=%u min=%u max=%u", *this->at_index_, evt.min_length, evt.max_length); // NOLINT
ESP_LOGV(TAG, "A i=%u min=%" PRIu32 " max=%" PRIu32, *this->at_index_, evt.min_length, evt.max_length); // NOLINT
this->schedule_is_valid_(evt.min_length);
this->schedule_is_not_valid_(evt.max_length);
} else if (*this->at_index_ + 1 != this->timing_.size()) {
ESP_LOGV(TAG, "B i=%u min=%u", *this->at_index_, evt.min_length); // NOLINT
ESP_LOGV(TAG, "B i=%u min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
this->cancel_timeout("is_not_valid");
this->schedule_is_valid_(evt.min_length);
} else {
ESP_LOGV(TAG, "C i=%u min=%u", *this->at_index_, evt.min_length); // NOLINT
ESP_LOGV(TAG, "C i=%u min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
this->is_valid_ = false;
this->cancel_timeout("is_not_valid");
this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); });
@ -68,7 +68,8 @@ void binary_sensor::MultiClickTrigger::on_state_(bool state) {
*this->at_index_ = *this->at_index_ + 1;
}
void binary_sensor::MultiClickTrigger::schedule_cooldown_() {
ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %u ms...", this->invalid_cooldown_);
ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms...",
this->invalid_cooldown_);
this->is_in_cooldown_ = true;
this->set_timeout("cooldown", this->invalid_cooldown_, [this]() {
ESP_LOGV(TAG, "Multi Click: Cooldown ended, matching is now enabled again.");

View File

@ -1,5 +1,6 @@
#pragma once
#include <cinttypes>
#include <utility>
#include <vector>

View File

@ -1,5 +1,6 @@
#include "bl0939.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace bl0939 {
@ -80,7 +81,7 @@ void BL0939::setup() {
void BL0939::received_package_(const DataPacket *data) const {
// Bad header
if (data->frame_header != BL0939_PACKET_HEADER) {
ESP_LOGI("bl0939", "Invalid data. Header mismatch: %d", data->frame_header);
ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header);
return;
}
@ -120,8 +121,9 @@ void BL0939::received_package_(const DataPacket *data) const {
energy_sensor_sum_->publish_state(total_energy_consumption);
}
ESP_LOGV("bl0939", "BL0939: U %fV, I1 %fA, I2 %fA, P1 %fW, P2 %fW, CntA %d, CntB %d, ∫P1 %fkWh, ∫P2 %fkWh", v_rms,
ia_rms, ib_rms, a_watt, b_watt, cfa_cnt, cfb_cnt, a_energy_consumption, b_energy_consumption);
ESP_LOGV(TAG,
"BL0939: U %fV, I1 %fA, I2 %fA, P1 %fW, P2 %fW, CntA %" PRId32 ", CntB %" PRId32 ", ∫P1 %fkWh, ∫P2 %fkWh",
v_rms, ia_rms, ib_rms, a_watt, b_watt, cfa_cnt, cfb_cnt, a_energy_consumption, b_energy_consumption);
}
void BL0939::dump_config() { // NOLINT(readability-function-cognitive-complexity)

View File

@ -1,5 +1,6 @@
#include "bl0940.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace bl0940 {
@ -77,7 +78,7 @@ float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45;
if (sensor != nullptr) {
if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) {
ESP_LOGD("bl0940", "Invalid temperature change. Sensor: '%s', Old temperature: %f, New temperature: %f",
ESP_LOGD(TAG, "Invalid temperature change. Sensor: '%s', Old temperature: %f, New temperature: %f",
sensor->get_name().c_str(), sensor->get_state(), converted_temp);
return 0.0f;
}
@ -89,7 +90,7 @@ float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
void BL0940::received_package_(const DataPacket *data) const {
// Bad header
if (data->frame_header != BL0940_PACKET_HEADER) {
ESP_LOGI("bl0940", "Invalid data. Header mismatch: %d", data->frame_header);
ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header);
return;
}
@ -115,7 +116,7 @@ void BL0940::received_package_(const DataPacket *data) const {
energy_sensor_->publish_state(total_energy_consumption);
}
ESP_LOGV("bl0940", "BL0940: U %fV, I %fA, P %fW, Cnt %d, ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt,
ESP_LOGV(TAG, "BL0940: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt,
total_energy_consumption, tps1, tps2);
}

View File

@ -1,5 +1,6 @@
#include "bl0942.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace bl0942 {
@ -104,8 +105,8 @@ void BL0942::received_package_(DataPacket *data) {
frequency_sensor_->publish_state(frequency);
}
ESP_LOGV(TAG, "BL0942: U %fV, I %fA, P %fW, Cnt %d, ∫P %fkWh, frequency %f°Hz, status 0x%08X", v_rms, i_rms, watt,
cf_cnt, total_energy_consumption, frequency, data->status);
ESP_LOGV(TAG, "BL0942: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, frequency %fHz, status 0x%08X", v_rms, i_rms,
watt, cf_cnt, total_energy_consumption, frequency, data->status);
}
void BL0942::dump_config() { // NOLINT(readability-function-cognitive-complexity)

View File

@ -1,8 +1,8 @@
#include "ble_rssi_sensor.h"
#include "esphome/core/log.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
@ -37,6 +37,10 @@ void BLEClientRSSISensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
}
case ESP_GATTC_SEARCH_CMPL_EVT:
this->node_state = espbt::ClientState::ESTABLISHED;
if (this->should_update_) {
this->should_update_ = false;
this->get_rssi_();
}
break;
default:
break;
@ -50,6 +54,7 @@ void BLEClientRSSISensor::gap_event_handler(esp_gap_ble_cb_event_t event, esp_bl
if (param->read_rssi_cmpl.status == ESP_BT_STATUS_SUCCESS) {
int8_t rssi = param->read_rssi_cmpl.rssi;
ESP_LOGI(TAG, "ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT RSSI: %d", rssi);
this->status_clear_warning();
this->publish_state(rssi);
}
break;
@ -61,9 +66,12 @@ void BLEClientRSSISensor::gap_event_handler(esp_gap_ble_cb_event_t event, esp_bl
void BLEClientRSSISensor::update() {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
this->should_update_ = true;
return;
}
this->get_rssi_();
}
void BLEClientRSSISensor::get_rssi_() {
ESP_LOGV(TAG, "requesting rssi from %s", this->parent()->address_str().c_str());
auto status = esp_ble_gap_read_rssi(this->parent()->get_remote_bda());
if (status != ESP_OK) {

View File

@ -1,9 +1,9 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#ifdef USE_ESP32
#include <esp_gattc_api.h>
@ -24,6 +24,10 @@ class BLEClientRSSISensor : public sensor::Sensor, public PollingComponent, publ
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
protected:
void get_rssi_();
bool should_update_{false};
};
} // namespace ble_client

View File

@ -8,6 +8,7 @@
#include "bmp3xx.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include <cinttypes>
namespace esphome {
namespace bmp3xx {
@ -198,8 +199,9 @@ void BMP3XXComponent::update() {
return;
}
ESP_LOGVV(TAG, "measurement time %d", uint32_t(ceilf(meas_time)));
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
const uint32_t meas_timeout = uint32_t(ceilf(meas_time));
ESP_LOGVV(TAG, "measurement time %" PRIu32, meas_timeout);
this->set_timeout("data", meas_timeout, [this]() {
float temperature = 0.0f;
float pressure = 0.0f;
if (this->pressure_sensor_ != nullptr) {

View File

@ -16,9 +16,9 @@ void Canbus::setup() {
void Canbus::dump_config() {
if (this->use_extended_id_) {
ESP_LOGCONFIG(TAG, "config extended id=0x%08x", this->can_id_);
ESP_LOGCONFIG(TAG, "config extended id=0x%08" PRIx32, this->can_id_);
} else {
ESP_LOGCONFIG(TAG, "config standard id=0x%03x", this->can_id_);
ESP_LOGCONFIG(TAG, "config standard id=0x%03" PRIx32, this->can_id_);
}
}
@ -28,9 +28,11 @@ void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transm
uint8_t size = static_cast<uint8_t>(data.size());
if (use_extended_id) {
ESP_LOGD(TAG, "send extended id=0x%08x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
ESP_LOGD(TAG, "send extended id=0x%08" PRIx32 " rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request),
size);
} else {
ESP_LOGD(TAG, "send standard id=0x%03x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
ESP_LOGD(TAG, "send standard id=0x%03" PRIx32 " rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request),
size);
}
if (size > CAN_MAX_DATA_LENGTH)
size = CAN_MAX_DATA_LENGTH;
@ -49,9 +51,9 @@ void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transm
void Canbus::add_trigger(CanbusTrigger *trigger) {
if (trigger->use_extended_id_) {
ESP_LOGVV(TAG, "add trigger for extended canid=0x%08x", trigger->can_id_);
ESP_LOGVV(TAG, "add trigger for extended canid=0x%08" PRIx32, trigger->can_id_);
} else {
ESP_LOGVV(TAG, "add trigger for std canid=0x%03x", trigger->can_id_);
ESP_LOGVV(TAG, "add trigger for std canid=0x%03" PRIx32, trigger->can_id_);
}
this->triggers_.push_back(trigger);
};
@ -63,10 +65,10 @@ void Canbus::loop() {
while (this->read_message(&can_message) == canbus::ERROR_OK) {
message_counter++;
if (can_message.use_extended_id) {
ESP_LOGD(TAG, "received can message (#%d) extended can_id=0x%x size=%d", message_counter, can_message.can_id,
can_message.can_data_length_code);
ESP_LOGD(TAG, "received can message (#%d) extended can_id=0x%" PRIx32 " size=%d", message_counter,
can_message.can_id, can_message.can_data_length_code);
} else {
ESP_LOGD(TAG, "received can message (#%d) std can_id=0x%x size=%d", message_counter, can_message.can_id,
ESP_LOGD(TAG, "received can message (#%d) std can_id=0x%" PRIx32 " size=%d", message_counter, can_message.can_id,
can_message.can_data_length_code);
}

View File

@ -4,6 +4,7 @@
#include "esphome/core/component.h"
#include "esphome/core/optional.h"
#include <cinttypes>
#include <vector>
namespace esphome {

View File

@ -1,5 +1,6 @@
#include "cd74hc4067.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace cd74hc4067 {
@ -27,7 +28,7 @@ void CD74HC4067Component::dump_config() {
LOG_PIN(" S1 Pin: ", this->pin_s1_);
LOG_PIN(" S2 Pin: ", this->pin_s2_);
LOG_PIN(" S3 Pin: ", this->pin_s3_);
ESP_LOGCONFIG(TAG, "switch delay: %d", this->switch_delay_);
ESP_LOGCONFIG(TAG, "switch delay: %" PRIu32, this->switch_delay_);
}
void CD74HC4067Component::activate_pin(uint8_t pin) {

View File

@ -121,7 +121,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
}
}
ESP_LOGD(TAG, "Decoded 0x%02X", remote_state);
ESP_LOGD(TAG, "Decoded 0x%02" PRIX32, remote_state);
if ((remote_state & 0xFF00000) != 0x8800000)
return false;
@ -173,7 +173,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
}
void LgIrClimate::transmit_(uint32_t value) {
calc_checksum_(value);
ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02X", value);
ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02" PRIX32, value);
auto transmit = this->transmitter_->transmit();
auto *data = transmit.get_data();

View File

@ -2,6 +2,8 @@
#include "esphome/components/climate_ir/climate_ir.h"
#include <cinttypes>
namespace esphome {
namespace climate_ir_lg {

View File

@ -101,7 +101,7 @@ void CoolixClimate::transmit_state() {
}
}
}
ESP_LOGV(TAG, "Sending coolix code: 0x%06X", remote_state);
ESP_LOGV(TAG, "Sending coolix code: 0x%06" PRIX32, remote_state);
auto transmit = this->transmitter_->transmit();
auto *data = transmit.get_data();
@ -115,7 +115,7 @@ bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteRecei
return false;
// Decoded remote state y 3 bytes long code.
uint32_t remote_state = (*decoded).second;
ESP_LOGV(TAG, "Decoded 0x%06X", remote_state);
ESP_LOGV(TAG, "Decoded 0x%06" PRIX32, remote_state);
if ((remote_state & 0xFF0000) != 0xB20000)
return false;

View File

@ -2,6 +2,8 @@
#include "esphome/components/climate_ir/climate_ir.h"
#include <cinttypes>
namespace esphome {
namespace coolix {

View File

@ -0,0 +1,36 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text
from esphome.const import (
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_MODE,
CONF_SOURCE_ID,
)
from esphome.core.entity_helpers import inherit_property_from
from .. import copy_ns
CopyText = copy_ns.class_("CopyText", text.Text, cg.Component)
CONFIG_SCHEMA = text.TEXT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(CopyText),
cv.Required(CONF_SOURCE_ID): cv.use_id(text.Text),
}
).extend(cv.COMPONENT_SCHEMA)
FINAL_VALIDATE_SCHEMA = cv.All(
inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID),
inherit_property_from(CONF_MODE, CONF_SOURCE_ID),
)
async def to_code(config):
var = await text.new_text(config)
await cg.register_component(var, config)
source = await cg.get_variable(config[CONF_SOURCE_ID])
cg.add(var.set_source(source))

View File

@ -0,0 +1,25 @@
#include "copy_text.h"
#include "esphome/core/log.h"
namespace esphome {
namespace copy {
static const char *const TAG = "copy.text";
void CopyText::setup() {
source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); });
if (source_->has_state())
this->publish_state(source_->state);
}
void CopyText::dump_config() { LOG_TEXT("", "Copy Text", this); }
void CopyText::control(const std::string &value) {
auto call2 = source_->make_call();
call2.set_value(value);
call2.perform();
}
} // namespace copy
} // namespace esphome

View File

@ -0,0 +1,23 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/text/text.h"
namespace esphome {
namespace copy {
class CopyText : public text::Text, public Component {
public:
void set_source(text::Text *source) { source_ = source; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
protected:
void control(const std::string &value) override;
text::Text *source_;
};
} // namespace copy
} // namespace esphome

View File

@ -86,7 +86,7 @@ void CS5460AComponent::hw_init_() {
}
uint32_t status = this->read_register_(REG_STATUS);
ESP_LOGCONFIG(TAG, " Version: %x", (status >> 6) & 7);
ESP_LOGCONFIG(TAG, " Version: %" PRIx32, (status >> 6) & 7);
this->write_register_(REG_CYCLE_COUNT, samples_);
this->write_register_(REG_PULSE_RATE, lroundf(pulse_freq_ * 32.0f));
@ -323,7 +323,7 @@ void CS5460AComponent::dump_config() {
ESP_LOGCONFIG(TAG, " Init status: %s",
state == COMPONENT_STATE_LOOP ? "OK" : (state == COMPONENT_STATE_FAILED ? "failed" : "other"));
LOG_PIN(" CS Pin: ", cs_);
ESP_LOGCONFIG(TAG, " Samples / cycle: %u", samples_);
ESP_LOGCONFIG(TAG, " Samples / cycle: %" PRIu32, samples_);
ESP_LOGCONFIG(TAG, " Phase offset: %i", phase_offset_);
ESP_LOGCONFIG(TAG, " PGA Gain: %s", pga_gain_ == CS5460A_PGA_GAIN_50X ? "50x" : "10x");
ESP_LOGCONFIG(TAG, " Current gain: %.5f", current_gain_);

View File

@ -5,6 +5,8 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/spi/spi.h"
#include <cinttypes>
namespace esphome {
namespace cs5460a {

View File

@ -217,7 +217,7 @@ void CSE7761Component::get_data_() {
this->voltage_sensor_->publish_state(voltage);
}
for (uint32_t channel = 0; channel < 2; channel++) {
for (uint8_t channel = 0; channel < 2; channel++) {
// Active power = PowerPA * PowerPAC * 1000 / 0x80000000
float active_power = (float) this->data_.active_power[channel] / this->coefficient_by_unit_(POWER_PAC); // W
float amps = (float) this->data_.current_rms[channel] / this->coefficient_by_unit_(RMS_IAC); // A

View File

@ -1,5 +1,6 @@
#include "cse7766.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace cse7766 {
@ -162,7 +163,7 @@ void CSE7766Component::update() {
if (counts != 0) {
const auto avg = acc / counts;
ESP_LOGV(TAG, "Got %s_acc=%.2f %s_counts=%d %s=%.1f", name, acc, name, counts, name, avg);
ESP_LOGV(TAG, "Got %s_acc=%.2f %s_counts=%" PRIu32 " %s=%.1f", name, acc, name, counts, name, avg);
if (sensor != nullptr) {
sensor->publish_state(avg);
@ -178,7 +179,8 @@ void CSE7766Component::update() {
publish_state("power", this->power_sensor_, this->power_acc_, this->power_counts_);
if (this->energy_total_counts_ != 0) {
ESP_LOGV(TAG, "Got energy_total=%.2f energy_total_counts=%d", this->energy_total_, this->energy_total_counts_);
ESP_LOGV(TAG, "Got energy_total=%.2f energy_total_counts=%" PRIu32, this->energy_total_,
this->energy_total_counts_);
if (this->energy_sensor_ != nullptr) {
this->energy_sensor_->publish_state(this->energy_total_);

View File

@ -39,7 +39,7 @@ void DeepSleepComponent::setup() {
const optional<uint32_t> run_duration = get_run_duration_();
if (run_duration.has_value()) {
ESP_LOGI(TAG, "Scheduling Deep Sleep to start in %u ms", *run_duration);
ESP_LOGI(TAG, "Scheduling Deep Sleep to start in %" PRIu32 " ms", *run_duration);
this->set_timeout(*run_duration, [this]() { this->begin_sleep(); });
} else {
ESP_LOGD(TAG, "Not scheduling Deep Sleep, as no run duration is configured.");
@ -49,19 +49,20 @@ void DeepSleepComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Setting up Deep Sleep...");
if (this->sleep_duration_.has_value()) {
uint32_t duration = *this->sleep_duration_ / 1000;
ESP_LOGCONFIG(TAG, " Sleep Duration: %u ms", duration);
ESP_LOGCONFIG(TAG, " Sleep Duration: %" PRIu32 " ms", duration);
}
if (this->run_duration_.has_value()) {
ESP_LOGCONFIG(TAG, " Run Duration: %u ms", *this->run_duration_);
ESP_LOGCONFIG(TAG, " Run Duration: %" PRIu32 " ms", *this->run_duration_);
}
#ifdef USE_ESP32
if (wakeup_pin_ != nullptr) {
LOG_PIN(" Wakeup Pin: ", this->wakeup_pin_);
}
if (this->wakeup_cause_to_run_duration_.has_value()) {
ESP_LOGCONFIG(TAG, " Default Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->default_cause);
ESP_LOGCONFIG(TAG, " Touch Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->touch_cause);
ESP_LOGCONFIG(TAG, " GPIO Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->gpio_cause);
ESP_LOGCONFIG(TAG, " Default Wakeup Run Duration: %" PRIu32 " ms",
this->wakeup_cause_to_run_duration_->default_cause);
ESP_LOGCONFIG(TAG, " Touch Wakeup Run Duration: %" PRIu32 " ms", this->wakeup_cause_to_run_duration_->touch_cause);
ESP_LOGCONFIG(TAG, " GPIO Wakeup Run Duration: %" PRIu32 " ms", this->wakeup_cause_to_run_duration_->gpio_cause);
}
#endif
}

View File

@ -14,6 +14,8 @@
#include "esphome/core/time.h"
#endif
#include <cinttypes>
namespace esphome {
namespace deep_sleep {

View File

@ -0,0 +1,208 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome import core
from esphome.automation import maybe_simple_id
from esphome.const import CONF_ID
from esphome.components import uart
CODEOWNERS = ["@niklasweber"]
DEPENDENCIES = ["uart"]
MULTI_CONF = True
dfrobot_sen0395_ns = cg.esphome_ns.namespace("dfrobot_sen0395")
DfrobotSen0395Component = dfrobot_sen0395_ns.class_(
"DfrobotSen0395Component", cg.Component
)
# Actions
DfrobotSen0395ResetAction = dfrobot_sen0395_ns.class_(
"DfrobotSen0395ResetAction", automation.Action
)
DfrobotSen0395SettingsAction = dfrobot_sen0395_ns.class_(
"DfrobotSen0395SettingsAction", automation.Action
)
CONF_DFROBOT_SEN0395_ID = "dfrobot_sen0395_id"
CONF_DELAY_AFTER_DETECT = "delay_after_detect"
CONF_DELAY_AFTER_DISAPPEAR = "delay_after_disappear"
CONF_DETECTION_SEGMENTS = "detection_segments"
CONF_OUTPUT_LATENCY = "output_latency"
CONF_FACTORY_RESET = "factory_reset"
CONF_SENSITIVITY = "sensitivity"
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(DfrobotSen0395Component),
}
).extend(uart.UART_DEVICE_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
@automation.register_action(
"dfrobot_sen0395.reset",
DfrobotSen0395ResetAction,
maybe_simple_id(
{
cv.GenerateID(): cv.use_id(DfrobotSen0395Component),
}
),
)
async def dfrobot_sen0395_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
def range_segment_list(input):
"""Validate input is a list of ranges which can be used to configure the dfrobot mmwave radar
A list of segments should be provided. A minimum of one segment is required and a maximum of
four segments is allowed. A segment describes a range of distances. E.g. from 0mm to 1m.
The distances need to be defined in an ascending order and they cannot contain / intersect
each other.
"""
# Flatten input to one dimensional list
flat_list = []
if isinstance(input, list):
for list_item in input:
if isinstance(list_item, list):
for item in list_item:
flat_list.append(item)
else:
flat_list.append(list_item)
else:
flat_list.append(input)
input = flat_list
if len(input) < 2:
raise cv.Invalid(
"At least two values need to be specified (start + stop distances)"
)
if len(input) % 2 != 0:
raise cv.Invalid(
"An even number of arguments must be specified (pairs of min + max)"
)
if len(input) > 8:
raise cv.Invalid(
"Maximum four segments can be specified (8 values: 4 * min + max)"
)
largest_distance = -1
for distance in input:
if isinstance(distance, core.Lambda):
continue
m = cv.distance(distance)
if m > 9:
raise cv.Invalid("Maximum distance is 9m")
if m < 0:
raise cv.Invalid("Minimum distance is 0m")
if m <= largest_distance:
raise cv.Invalid(
"Distances must be delared from small to large "
"and they cannot contain each other"
)
largest_distance = m
# Replace distance object with meters float
input[input.index(distance)] = m
return input
MMWAVE_SETTINGS_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(DfrobotSen0395Component),
cv.Optional(CONF_FACTORY_RESET): cv.templatable(cv.boolean),
cv.Optional(CONF_DETECTION_SEGMENTS): range_segment_list,
cv.Optional(CONF_OUTPUT_LATENCY): {
cv.Required(CONF_DELAY_AFTER_DETECT): cv.templatable(
cv.All(
cv.positive_time_period,
cv.Range(max=core.TimePeriod(seconds=1638.375)),
)
),
cv.Required(CONF_DELAY_AFTER_DISAPPEAR): cv.templatable(
cv.All(
cv.positive_time_period,
cv.Range(max=core.TimePeriod(seconds=1638.375)),
)
),
},
cv.Optional(CONF_SENSITIVITY): cv.templatable(cv.int_range(min=0, max=9)),
}
).add_extra(
cv.has_at_least_one_key(
CONF_FACTORY_RESET,
CONF_DETECTION_SEGMENTS,
CONF_OUTPUT_LATENCY,
CONF_SENSITIVITY,
)
)
@automation.register_action(
"dfrobot_sen0395.settings",
DfrobotSen0395SettingsAction,
MMWAVE_SETTINGS_SCHEMA,
)
async def dfrobot_sen0395_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])
if factory_reset_config := config.get(CONF_FACTORY_RESET):
template_ = await cg.templatable(factory_reset_config, args, int)
cg.add(var.set_factory_reset(template_))
if CONF_DETECTION_SEGMENTS in config:
segments = config[CONF_DETECTION_SEGMENTS]
if len(segments) >= 2:
template_ = await cg.templatable(segments[0], args, float)
cg.add(var.set_det_min1(template_))
template_ = await cg.templatable(segments[1], args, float)
cg.add(var.set_det_max1(template_))
if len(segments) >= 4:
template_ = await cg.templatable(segments[2], args, float)
cg.add(var.set_det_min2(template_))
template_ = await cg.templatable(segments[3], args, float)
cg.add(var.set_det_max2(template_))
if len(segments) >= 6:
template_ = await cg.templatable(segments[4], args, float)
cg.add(var.set_det_min3(template_))
template_ = await cg.templatable(segments[5], args, float)
cg.add(var.set_det_max3(template_))
if len(segments) >= 8:
template_ = await cg.templatable(segments[6], args, float)
cg.add(var.set_det_min4(template_))
template_ = await cg.templatable(segments[7], args, float)
cg.add(var.set_det_max4(template_))
if CONF_OUTPUT_LATENCY in config:
template_ = await cg.templatable(
config[CONF_OUTPUT_LATENCY][CONF_DELAY_AFTER_DETECT], args, float
)
if isinstance(template_, cv.TimePeriod):
template_ = template_.total_milliseconds / 1000
cg.add(var.set_delay_after_detect(template_))
template_ = await cg.templatable(
config[CONF_OUTPUT_LATENCY][CONF_DELAY_AFTER_DISAPPEAR], args, float
)
if isinstance(template_, cv.TimePeriod):
template_ = template_.total_milliseconds / 1000
cg.add(var.set_delay_after_disappear(template_))
if CONF_SENSITIVITY in config:
template_ = await cg.templatable(config[CONF_SENSITIVITY], args, int)
cg.add(var.set_sensitivity(template_))
return var

View File

@ -0,0 +1,89 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/helpers.h"
#include "dfrobot_sen0395.h"
namespace esphome {
namespace dfrobot_sen0395 {
template<typename... Ts>
class DfrobotSen0395ResetAction : public Action<Ts...>, public Parented<DfrobotSen0395Component> {
public:
void play(Ts... x) { this->parent_->enqueue(make_unique<ResetSystemCommand>()); }
};
template<typename... Ts>
class DfrobotSen0395SettingsAction : public Action<Ts...>, public Parented<DfrobotSen0395Component> {
public:
TEMPLATABLE_VALUE(int8_t, factory_reset)
TEMPLATABLE_VALUE(int8_t, start_after_power_on)
TEMPLATABLE_VALUE(int8_t, turn_on_led)
TEMPLATABLE_VALUE(int8_t, presence_via_uart)
TEMPLATABLE_VALUE(int8_t, sensitivity)
TEMPLATABLE_VALUE(float, delay_after_detect)
TEMPLATABLE_VALUE(float, delay_after_disappear)
TEMPLATABLE_VALUE(float, det_min1)
TEMPLATABLE_VALUE(float, det_max1)
TEMPLATABLE_VALUE(float, det_min2)
TEMPLATABLE_VALUE(float, det_max2)
TEMPLATABLE_VALUE(float, det_min3)
TEMPLATABLE_VALUE(float, det_max3)
TEMPLATABLE_VALUE(float, det_min4)
TEMPLATABLE_VALUE(float, det_max4)
void play(Ts... x) {
this->parent_->enqueue(make_unique<PowerCommand>(0));
if (this->factory_reset_.has_value() && this->factory_reset_.value(x...) == true) {
this->parent_->enqueue(make_unique<FactoryResetCommand>());
}
if (this->det_min1_.has_value() && this->det_max1_.has_value()) {
if (this->det_min1_.value() >= 0 && this->det_max1_.value() >= 0) {
this->parent_->enqueue(make_unique<DetRangeCfgCommand>(
this->det_min1_.value_or(-1), this->det_max1_.value_or(-1), this->det_min2_.value_or(-1),
this->det_max2_.value_or(-1), this->det_min3_.value_or(-1), this->det_max3_.value_or(-1),
this->det_min4_.value_or(-1), this->det_max4_.value_or(-1)));
}
}
if (this->delay_after_detect_.has_value() && this->delay_after_disappear_.has_value()) {
float detect = this->delay_after_detect_.value(x...);
float disappear = this->delay_after_disappear_.value(x...);
if (detect >= 0 && disappear >= 0) {
this->parent_->enqueue(make_unique<OutputLatencyCommand>(detect, disappear));
}
}
if (this->start_after_power_on_.has_value()) {
int8_t val = this->start_after_power_on_.value(x...);
if (val >= 0) {
this->parent_->enqueue(make_unique<SensorCfgStartCommand>(val));
}
}
if (this->turn_on_led_.has_value()) {
int8_t val = this->turn_on_led_.value(x...);
if (val >= 0) {
this->parent_->enqueue(make_unique<LedModeCommand>(val));
}
}
if (this->presence_via_uart_.has_value()) {
int8_t val = this->presence_via_uart_.value(x...);
if (val >= 0) {
this->parent_->enqueue(make_unique<UartOutputCommand>(val));
}
}
if (this->sensitivity_.has_value()) {
int8_t val = this->sensitivity_.value(x...);
if (val >= 0) {
if (val > 9) {
val = 9;
}
this->parent_->enqueue(make_unique<SensitivityCommand>(val));
}
}
this->parent_->enqueue(make_unique<SaveCfgCommand>());
this->parent_->enqueue(make_unique<PowerCommand>(1));
}
};
} // namespace dfrobot_sen0395
} // namespace esphome

View File

@ -0,0 +1,22 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import DEVICE_CLASS_MOTION
from . import CONF_DFROBOT_SEN0395_ID, DfrobotSen0395Component
DEPENDENCIES = ["dfrobot_sen0395"]
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_MOTION
).extend(
{
cv.GenerateID(CONF_DFROBOT_SEN0395_ID): cv.use_id(DfrobotSen0395Component),
}
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_DFROBOT_SEN0395_ID])
binary_sens = await binary_sensor.new_binary_sensor(config)
cg.add(parent.set_detected_binary_sensor(binary_sens))

View File

@ -0,0 +1,329 @@
#include "commands.h"
#include "esphome/core/log.h"
#include "dfrobot_sen0395.h"
namespace esphome {
namespace dfrobot_sen0395 {
static const char *const TAG = "dfrobot_sen0395.commands";
uint8_t Command::execute(DfrobotSen0395Component *parent) {
this->parent_ = parent;
if (this->cmd_sent_) {
if (this->parent_->read_message_()) {
std::string message(this->parent_->read_buffer_);
if (message.rfind("is not recognized as a CLI command") != std::string::npos) {
ESP_LOGD(TAG, "Command not recognized properly by sensor");
if (this->retries_left_ > 0) {
this->retries_left_ -= 1;
this->cmd_sent_ = false;
ESP_LOGD(TAG, "Retrying...");
return 0;
} else {
this->parent_->find_prompt_();
return 1; // Command done
}
}
uint8_t rc = on_message(message);
if (rc == 2) {
if (this->retries_left_ > 0) {
this->retries_left_ -= 1;
this->cmd_sent_ = false;
ESP_LOGD(TAG, "Retrying...");
return 0;
} else {
this->parent_->find_prompt_();
return 1; // Command done
}
} else if (rc == 0) {
return 0;
} else {
this->parent_->find_prompt_();
return 1;
}
}
if (millis() - this->parent_->ts_last_cmd_sent_ > this->timeout_ms_) {
ESP_LOGD(TAG, "Command timeout");
if (this->retries_left_ > 0) {
this->retries_left_ -= 1;
this->cmd_sent_ = false;
ESP_LOGD(TAG, "Retrying...");
} else {
return 1; // Command done
}
}
} else if (this->parent_->send_cmd_(this->cmd_.c_str(), this->cmd_duration_ms_)) {
this->cmd_sent_ = true;
}
return 0; // Command not done yet
}
uint8_t ReadStateCommand::execute(DfrobotSen0395Component *parent) {
this->parent_ = parent;
if (this->parent_->read_message_()) {
std::string message(this->parent_->read_buffer_);
if (message.rfind("$JYBSS,0, , , *") != std::string::npos) {
this->parent_->set_detected_(false);
this->parent_->set_active(true);
return 1; // Command done
} else if (message.rfind("$JYBSS,1, , , *") != std::string::npos) {
this->parent_->set_detected_(true);
this->parent_->set_active(true);
return 1; // Command done
}
}
if (millis() - this->parent_->ts_last_cmd_sent_ > this->timeout_ms_) {
return 1; // Command done, timeout
}
return 0; // Command not done yet.
}
uint8_t ReadStateCommand::on_message(std::string &message) { return 1; }
uint8_t PowerCommand::on_message(std::string &message) {
if (message == "sensor stopped already") {
this->parent_->set_active(false);
ESP_LOGI(TAG, "Stopped sensor (already stopped)");
return 1; // Command done
} else if (message == "sensor started already") {
this->parent_->set_active(true);
ESP_LOGI(TAG, "Started sensor (already started)");
return 1; // Command done
} else if (message == "new parameter isn't save, can't startSensor") {
this->parent_->set_active(false);
ESP_LOGE(TAG, "Can't start sensor! (Use SaveCfgCommand to save config first)");
return 1; // Command done
} else if (message == "Done") {
this->parent_->set_active(this->power_on_);
if (this->power_on_) {
ESP_LOGI(TAG, "Started sensor");
} else {
ESP_LOGI(TAG, "Stopped sensor");
}
return 1; // Command done
}
return 0; // Command not done yet.
}
DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float max2, float min3, float max3,
float min4, float max4) {
// TODO: Print warning when values are rounded
if (min1 < 0 || max1 < 0) {
this->min1_ = min1 = 0;
this->max1_ = max1 = 0;
this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 =
this->max4_ = max4 = -1;
ESP_LOGW(TAG, "DetRangeCfgCommand invalid input parameters. Using range config 0 0.");
this->cmd_ = "detRangeCfg -1 0 0";
} else if (min2 < 0 || max2 < 0) {
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 =
this->max4_ = max4 = -1;
this->cmd_ = str_sprintf("detRangeCfg -1 %.0f %.0f", min1 / 0.15, max1 / 0.15);
} else if (min3 < 0 || max3 < 0) {
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
this->min2_ = min2 = round(min2 / 0.15) * 0.15;
this->max2_ = max2 = round(max2 / 0.15) * 0.15;
this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 = this->max4_ = max4 = -1;
this->cmd_ = str_sprintf("detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15);
} else if (min4 < 0 || max4 < 0) {
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
this->min2_ = min2 = round(min2 / 0.15) * 0.15;
this->max2_ = max2 = round(max2 / 0.15) * 0.15;
this->min3_ = min3 = round(min3 / 0.15) * 0.15;
this->max3_ = max3 = round(max3 / 0.15) * 0.15;
this->min4_ = min4 = this->max4_ = max4 = -1;
this->cmd_ = str_sprintf("detRangeCfg -1 "
"%.0f %.0f %.0f %.0f %.0f %.0f",
min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15);
} else {
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
this->min2_ = min2 = round(min2 / 0.15) * 0.15;
this->max2_ = max2 = round(max2 / 0.15) * 0.15;
this->min3_ = min3 = round(min3 / 0.15) * 0.15;
this->max3_ = max3 = round(max3 / 0.15) * 0.15;
this->min4_ = min4 = round(min4 / 0.15) * 0.15;
this->max4_ = max4 = round(max4 / 0.15) * 0.15;
this->cmd_ = str_sprintf("detRangeCfg -1 "
"%.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f",
min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15, min4 / 0.15,
max4 / 0.15);
}
this->min1_ = min1;
this->max1_ = max1;
this->min2_ = min2;
this->max2_ = max2;
this->min3_ = min3;
this->max3_ = max3;
this->min4_ = min4;
this->max4_ = max4;
};
uint8_t DetRangeCfgCommand::on_message(std::string &message) {
if (message == "sensor is not stopped") {
ESP_LOGE(TAG, "Cannot configure range config. Sensor is not stopped!");
return 1; // Command done
} else if (message == "Done") {
ESP_LOGI(TAG, "Updated detection area config:");
ESP_LOGI(TAG, "Detection area 1 from %.02fm to %.02fm.", this->min1_, this->max1_);
if (this->min2_ >= 0 && this->max2_ >= 0) {
ESP_LOGI(TAG, "Detection area 2 from %.02fm to %.02fm.", this->min2_, this->max2_);
}
if (this->min3_ >= 0 && this->max3_ >= 0) {
ESP_LOGI(TAG, "Detection area 3 from %.02fm to %.02fm.", this->min3_, this->max3_);
}
if (this->min4_ >= 0 && this->max4_ >= 0) {
ESP_LOGI(TAG, "Detection area 4 from %.02fm to %.02fm.", this->min4_, this->max4_);
}
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
return 1; // Command done
}
return 0; // Command not done yet.
}
OutputLatencyCommand::OutputLatencyCommand(float delay_after_detection, float delay_after_disappear) {
delay_after_detection = round(delay_after_detection / 0.025) * 0.025;
delay_after_disappear = round(delay_after_disappear / 0.025) * 0.025;
if (delay_after_detection < 0)
delay_after_detection = 0;
if (delay_after_detection > 1638.375)
delay_after_detection = 1638.375;
if (delay_after_disappear < 0)
delay_after_disappear = 0;
if (delay_after_disappear > 1638.375)
delay_after_disappear = 1638.375;
this->delay_after_detection_ = delay_after_detection;
this->delay_after_disappear_ = delay_after_disappear;
this->cmd_ = str_sprintf("outputLatency -1 %.0f %.0f", delay_after_detection / 0.025, delay_after_disappear / 0.025);
};
uint8_t OutputLatencyCommand::on_message(std::string &message) {
if (message == "sensor is not stopped") {
ESP_LOGE(TAG, "Cannot configure output latency. Sensor is not stopped!");
return 1; // Command done
} else if (message == "Done") {
ESP_LOGI(TAG, "Updated output latency config:");
ESP_LOGI(TAG, "Signal that someone was detected is delayed by %.02fs.", this->delay_after_detection_);
ESP_LOGI(TAG, "Signal that nobody is detected anymore is delayed by %.02fs.", this->delay_after_disappear_);
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
return 1; // Command done
}
return 0; // Command not done yet
}
uint8_t SensorCfgStartCommand::on_message(std::string &message) {
if (message == "sensor is not stopped") {
ESP_LOGE(TAG, "Cannot configure sensor startup behavior. Sensor is not stopped!");
return 1; // Command done
} else if (message == "Done") {
ESP_LOGI(TAG, "Updated sensor startup behavior:");
if (startup_mode_) {
this->parent_->set_start_after_boot(true);
ESP_LOGI(TAG, "Sensor will start automatically after power-on.");
} else {
this->parent_->set_start_after_boot(false);
ESP_LOGI(TAG, "Sensor needs to be started manually after power-on.");
}
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
return 1; // Command done
}
return 0; // Command not done yet
}
uint8_t FactoryResetCommand::on_message(std::string &message) {
if (message == "sensor is not stopped") {
ESP_LOGE(TAG, "Cannot factory reset. Sensor is not stopped!");
return 1; // Command done
} else if (message == "Done") {
ESP_LOGI(TAG, "Sensor factory reset done.");
return 1; // Command done
}
return 0; // Command not done yet
}
uint8_t ResetSystemCommand::on_message(std::string &message) {
if (message == "leapMMW:/>") {
ESP_LOGI(TAG, "Restarted sensor.");
return 1; // Command done
}
return 0; // Command not done yet
}
uint8_t SaveCfgCommand::on_message(std::string &message) {
if (message == "no parameter has changed") {
ESP_LOGI(TAG, "Not saving config (no parameter changed).");
return 1; // Command done
} else if (message == "Done") {
ESP_LOGI(TAG, "Saved config. Saving a lot may damage the sensor.");
return 1; // Command done
}
return 0; // Command not done yet
}
uint8_t LedModeCommand::on_message(std::string &message) {
if (message == "sensor is not stopped") {
ESP_LOGE(TAG, "Cannot set led mode. Sensor is not stopped!");
return 1; // Command done
} else if (message == "Done") {
ESP_LOGI(TAG, "Set led mode done.");
if (this->active_) {
this->parent_->set_led_active(true);
ESP_LOGI(TAG, "Sensor LED will blink.");
} else {
this->parent_->set_led_active(false);
ESP_LOGI(TAG, "Turned off LED.");
}
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
return 1; // Command done
}
return 0; // Command not done yet
}
uint8_t UartOutputCommand::on_message(std::string &message) {
if (message == "sensor is not stopped") {
ESP_LOGE(TAG, "Cannot set uart output mode. Sensor is not stopped!");
return 1; // Command done
} else if (message == "Done") {
ESP_LOGI(TAG, "Set uart mode done.");
if (this->active_) {
this->parent_->set_uart_presence_active(true);
ESP_LOGI(TAG, "Presence information is sent via UART and GPIO.");
} else {
this->parent_->set_uart_presence_active(false);
ESP_LOGI(TAG, "Presence information is only sent via GPIO.");
}
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
return 1; // Command done
}
return 0; // Command not done yet
}
uint8_t SensitivityCommand::on_message(std::string &message) {
if (message == "sensor is not stopped") {
ESP_LOGE(TAG, "Cannot set sensitivity. Sensor is not stopped!");
return 1; // Command done
} else if (message == "Done") {
ESP_LOGI(TAG, "Set sensitivity done. Set to value %d.", this->sensitivity_);
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
return 1; // Command done
}
return 0; // Command not done yet
}
} // namespace dfrobot_sen0395
} // namespace esphome

View File

@ -0,0 +1,156 @@
#pragma once
#include <cstdint>
#include <string>
#include "esphome/core/helpers.h"
namespace esphome {
namespace dfrobot_sen0395 {
class DfrobotSen0395Component;
// Use command queue and time stamps to avoid blocking.
// When component has run time, check if minimum time (1s) between
// commands has passed. After that run a command from the queue.
class Command {
public:
virtual ~Command() = default;
virtual uint8_t execute(DfrobotSen0395Component *parent);
virtual uint8_t on_message(std::string &message) = 0;
protected:
DfrobotSen0395Component *parent_{nullptr};
std::string cmd_;
bool cmd_sent_{false};
int8_t retries_left_{2};
uint32_t cmd_duration_ms_{1000};
uint32_t timeout_ms_{1500};
};
class ReadStateCommand : public Command {
public:
uint8_t execute(DfrobotSen0395Component *parent) override;
uint8_t on_message(std::string &message) override;
protected:
uint32_t timeout_ms_{500};
};
class PowerCommand : public Command {
public:
PowerCommand(bool power_on) : power_on_(power_on) {
if (power_on) {
cmd_ = "sensorStart";
} else {
cmd_ = "sensorStop";
}
};
uint8_t on_message(std::string &message) override;
protected:
bool power_on_;
};
class DetRangeCfgCommand : public Command {
public:
DetRangeCfgCommand(float min1, float max1, float min2, float max2, float min3, float max3, float min4, float max4);
uint8_t on_message(std::string &message) override;
protected:
float min1_, max1_, min2_, max2_, min3_, max3_, min4_, max4_;
// TODO: Set min max values in component, so they can be published as sensor.
};
class OutputLatencyCommand : public Command {
public:
OutputLatencyCommand(float delay_after_detection, float delay_after_disappear);
uint8_t on_message(std::string &message) override;
protected:
float delay_after_detection_;
float delay_after_disappear_;
};
class SensorCfgStartCommand : public Command {
public:
SensorCfgStartCommand(bool startup_mode) : startup_mode_(startup_mode) {
char tmp_cmd[20] = {0};
sprintf(tmp_cmd, "sensorCfgStart %d", startup_mode);
cmd_ = std::string(tmp_cmd);
}
uint8_t on_message(std::string &message) override;
protected:
bool startup_mode_;
};
class FactoryResetCommand : public Command {
public:
FactoryResetCommand() { cmd_ = "factoryReset 0x45670123 0xCDEF89AB 0x956128C6 0xDF54AC89"; };
uint8_t on_message(std::string &message) override;
};
class ResetSystemCommand : public Command {
public:
ResetSystemCommand() { cmd_ = "resetSystem"; }
uint8_t on_message(std::string &message) override;
};
class SaveCfgCommand : public Command {
public:
SaveCfgCommand() { cmd_ = "saveCfg 0x45670123 0xCDEF89AB 0x956128C6 0xDF54AC89"; }
uint8_t on_message(std::string &message) override;
protected:
uint32_t cmd_duration_ms_{3000};
uint32_t timeout_ms_{3500};
};
class LedModeCommand : public Command {
public:
LedModeCommand(bool active) : active_(active) {
if (active) {
cmd_ = "setLedMode 1 0";
} else {
cmd_ = "setLedMode 1 1";
}
};
uint8_t on_message(std::string &message) override;
protected:
bool active_;
};
class UartOutputCommand : public Command {
public:
UartOutputCommand(bool active) : active_(active) {
if (active) {
cmd_ = "setUartOutput 1 1";
} else {
cmd_ = "setUartOutput 1 0";
}
};
uint8_t on_message(std::string &message) override;
protected:
bool active_;
};
class SensitivityCommand : public Command {
public:
SensitivityCommand(uint8_t sensitivity) : sensitivity_(sensitivity) {
if (sensitivity > 9)
sensitivity_ = sensitivity = 9;
char tmp_cmd[20] = {0};
sprintf(tmp_cmd, "setSensitivity %d", sensitivity);
cmd_ = std::string(tmp_cmd);
};
uint8_t on_message(std::string &message) override;
protected:
uint8_t sensitivity_;
};
} // namespace dfrobot_sen0395
} // namespace esphome

View File

@ -0,0 +1,142 @@
#include "dfrobot_sen0395.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace dfrobot_sen0395 {
static const char *const TAG = "dfrobot_sen0395";
const char ASCII_CR = 0x0D;
const char ASCII_LF = 0x0A;
void DfrobotSen0395Component::dump_config() {
ESP_LOGCONFIG(TAG, "Dfrobot Mmwave Radar:");
#ifdef USE_BINARY_SENSOR
LOG_BINARY_SENSOR(" ", "Registered", this->detected_binary_sensor_);
#endif
#ifdef USE_SWITCH
LOG_SWITCH(" ", "Sensor Active Switch", this->sensor_active_switch_);
LOG_SWITCH(" ", "Turn on LED Switch", this->turn_on_led_switch_);
LOG_SWITCH(" ", "Presence via UART Switch", this->presence_via_uart_switch_);
LOG_SWITCH(" ", "Start after Boot Switch", this->start_after_boot_switch_);
#endif
}
void DfrobotSen0395Component::loop() {
if (cmd_queue_.is_empty()) {
// Command queue empty. Read sensor state.
cmd_queue_.enqueue(make_unique<ReadStateCommand>());
}
// Commands are non-blocking and need to be called repeatedly.
if (cmd_queue_.process(this)) {
// Dequeue if command is done
cmd_queue_.dequeue();
}
}
int8_t DfrobotSen0395Component::enqueue(std::unique_ptr<Command> cmd) {
return cmd_queue_.enqueue(std::move(cmd)); // Transfer ownership using std::move
}
uint8_t DfrobotSen0395Component::read_message_() {
while (this->available()) {
uint8_t byte;
this->read_byte(&byte);
if (this->read_pos_ == MMWAVE_READ_BUFFER_LENGTH)
this->read_pos_ = 0;
ESP_LOGVV(TAG, "Buffer pos: %u %d", this->read_pos_, byte);
if (byte == ASCII_CR)
continue;
if (byte >= 0x7F)
byte = '?'; // needs to be valid utf8 string for log functions.
this->read_buffer_[this->read_pos_] = byte;
if (this->read_pos_ == 9 && byte == '>')
this->read_buffer_[++this->read_pos_] = ASCII_LF;
if (this->read_buffer_[this->read_pos_] == ASCII_LF) {
this->read_buffer_[this->read_pos_] = 0;
this->read_pos_ = 0;
ESP_LOGV(TAG, "Message: %s", this->read_buffer_);
return 1; // Full message in buffer
} else {
this->read_pos_++;
}
}
return 0; // No full message yet
}
uint8_t DfrobotSen0395Component::find_prompt_() {
if (this->read_message_()) {
std::string message(this->read_buffer_);
if (message.rfind("leapMMW:/>") != std::string::npos) {
return 1; // Prompt found
}
}
return 0; // Not found yet
}
uint8_t DfrobotSen0395Component::send_cmd_(const char *cmd, uint32_t duration) {
// The interval between two commands must be larger than the specified duration (in ms).
if (millis() - ts_last_cmd_sent_ > duration) {
this->write_str(cmd);
ts_last_cmd_sent_ = millis();
return 1; // Command sent
}
// Could not send command yet as command duration did not fully pass yet.
return 0;
}
void DfrobotSen0395Component::set_detected_(bool detected) {
this->detected_ = detected;
#ifdef USE_BINARY_SENSOR
if (this->detected_binary_sensor_ != nullptr)
this->detected_binary_sensor_->publish_state(detected);
#endif
}
int8_t CircularCommandQueue::enqueue(std::unique_ptr<Command> cmd) {
if (this->is_full()) {
ESP_LOGE(TAG, "Command queue is full");
return -1;
} else if (this->is_empty())
front_++;
rear_ = (rear_ + 1) % COMMAND_QUEUE_SIZE;
commands_[rear_] = std::move(cmd); // Transfer ownership using std::move
return 1;
}
std::unique_ptr<Command> CircularCommandQueue::dequeue() {
if (this->is_empty())
return nullptr;
std::unique_ptr<Command> dequeued_cmd = std::move(commands_[front_]);
if (front_ == rear_) {
front_ = -1;
rear_ = -1;
} else
front_ = (front_ + 1) % COMMAND_QUEUE_SIZE;
return dequeued_cmd;
}
bool CircularCommandQueue::is_empty() { return front_ == -1; }
bool CircularCommandQueue::is_full() { return (rear_ + 1) % COMMAND_QUEUE_SIZE == front_; }
// Run execute method of first in line command.
// Execute is non-blocking and has to be called until it returns 1.
uint8_t CircularCommandQueue::process(DfrobotSen0395Component *parent) {
if (!is_empty()) {
return commands_[front_]->execute(parent);
} else {
return 1;
}
}
} // namespace dfrobot_sen0395
} // namespace esphome

View File

@ -0,0 +1,125 @@
#pragma once
#include "esphome/components/uart/uart.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#ifdef USE_SWITCH
#include "esphome/components/switch/switch.h"
#endif
#include "commands.h"
namespace esphome {
namespace dfrobot_sen0395 {
const uint8_t MMWAVE_READ_BUFFER_LENGTH = 255;
// forward declaration due to circular dependency
class DfrobotSen0395Component;
static const uint8_t COMMAND_QUEUE_SIZE = 20;
class CircularCommandQueue {
public:
int8_t enqueue(std::unique_ptr<Command> cmd);
std::unique_ptr<Command> dequeue();
bool is_empty();
bool is_full();
uint8_t process(DfrobotSen0395Component *parent);
protected:
int front_{-1};
int rear_{-1};
std::unique_ptr<Command> commands_[COMMAND_QUEUE_SIZE];
};
class DfrobotSen0395Component : public uart::UARTDevice, public Component {
#ifdef USE_SWITCH
SUB_SWITCH(sensor_active)
SUB_SWITCH(turn_on_led)
SUB_SWITCH(presence_via_uart)
SUB_SWITCH(start_after_boot)
#endif
public:
void dump_config() override;
void loop() override;
void set_active(bool active) {
if (active != active_) {
#ifdef USE_SWITCH
if (this->sensor_active_switch_ != nullptr)
this->sensor_active_switch_->publish_state(active);
#endif
active_ = active;
}
}
bool is_active() { return active_; }
void set_led_active(bool active) {
if (led_active_ != active) {
#ifdef USE_SWITCH
if (this->turn_on_led_switch_ != nullptr)
this->turn_on_led_switch_->publish_state(active);
#endif
led_active_ = active;
}
}
bool is_led_active() { return led_active_; }
void set_uart_presence_active(bool active) {
uart_presence_active_ = active;
#ifdef USE_SWITCH
if (this->presence_via_uart_switch_ != nullptr)
this->presence_via_uart_switch_->publish_state(active);
#endif
}
bool is_uart_presence_active() { return uart_presence_active_; }
void set_start_after_boot(bool start) {
start_after_boot_ = start;
#ifdef USE_SWITCH
if (this->start_after_boot_switch_ != nullptr)
this->start_after_boot_switch_->publish_state(start);
#endif
}
bool does_start_after_boot() { return start_after_boot_; }
#ifdef USE_BINARY_SENSOR
void set_detected_binary_sensor(binary_sensor::BinarySensor *detected_binary_sensor) {
detected_binary_sensor_ = detected_binary_sensor;
}
#endif
int8_t enqueue(std::unique_ptr<Command> cmd);
protected:
#ifdef USE_BINARY_SENSOR
binary_sensor::BinarySensor *detected_binary_sensor_{nullptr};
#endif
bool detected_{false};
bool active_{false};
bool led_active_{false};
bool uart_presence_active_{false};
bool start_after_boot_{false};
char read_buffer_[MMWAVE_READ_BUFFER_LENGTH];
size_t read_pos_{0};
CircularCommandQueue cmd_queue_;
uint32_t ts_last_cmd_sent_{0};
uint8_t read_message_();
uint8_t find_prompt_();
uint8_t send_cmd_(const char *cmd, uint32_t duration);
void set_detected_(bool detected);
friend class Command;
friend class ReadStateCommand;
};
} // namespace dfrobot_sen0395
} // namespace esphome

View File

@ -0,0 +1,65 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import switch
from esphome.const import ENTITY_CATEGORY_CONFIG, CONF_TYPE
from .. import CONF_DFROBOT_SEN0395_ID, DfrobotSen0395Component
DEPENDENCIES = ["dfrobot_sen0395"]
dfrobot_sen0395_ns = cg.esphome_ns.namespace("dfrobot_sen0395")
DfrobotSen0395Switch = dfrobot_sen0395_ns.class_(
"DfrobotSen0395Switch",
switch.Switch,
cg.Component,
cg.Parented.template(DfrobotSen0395Component),
)
Sen0395PowerSwitch = dfrobot_sen0395_ns.class_(
"Sen0395PowerSwitch", DfrobotSen0395Switch
)
Sen0395LedSwitch = dfrobot_sen0395_ns.class_("Sen0395LedSwitch", DfrobotSen0395Switch)
Sen0395UartPresenceSwitch = dfrobot_sen0395_ns.class_(
"Sen0395UartPresenceSwitch", DfrobotSen0395Switch
)
Sen0395StartAfterBootSwitch = dfrobot_sen0395_ns.class_(
"Sen0395StartAfterBootSwitch", DfrobotSen0395Switch
)
_SWITCH_SCHEMA = (
switch.switch_schema(
entity_category=ENTITY_CATEGORY_CONFIG,
)
.extend(
{
cv.GenerateID(CONF_DFROBOT_SEN0395_ID): cv.use_id(DfrobotSen0395Component),
}
)
.extend(cv.COMPONENT_SCHEMA)
)
CONFIG_SCHEMA = cv.typed_schema(
{
"sensor_active": _SWITCH_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(Sen0395PowerSwitch)}
),
"turn_on_led": _SWITCH_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(Sen0395LedSwitch)}
),
"presence_via_uart": _SWITCH_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(Sen0395UartPresenceSwitch)}
),
"start_after_boot": _SWITCH_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(Sen0395StartAfterBootSwitch)}
),
}
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_DFROBOT_SEN0395_ID])
var = await switch.new_switch(config)
await cg.register_component(var, config)
await cg.register_parented(var, parent)
cg.add(getattr(parent, f"set_{config[CONF_TYPE]}_switch")(var))

View File

@ -0,0 +1,48 @@
#include "dfrobot_sen0395_switch.h"
namespace esphome {
namespace dfrobot_sen0395 {
void Sen0395PowerSwitch::write_state(bool state) { this->parent_->enqueue(make_unique<PowerCommand>(state)); }
void Sen0395LedSwitch::write_state(bool state) {
bool was_active = false;
if (this->parent_->is_active()) {
was_active = true;
this->parent_->enqueue(make_unique<PowerCommand>(false));
}
this->parent_->enqueue(make_unique<LedModeCommand>(state));
this->parent_->enqueue(make_unique<SaveCfgCommand>());
if (was_active) {
this->parent_->enqueue(make_unique<PowerCommand>(true));
}
}
void Sen0395UartPresenceSwitch::write_state(bool state) {
bool was_active = false;
if (this->parent_->is_active()) {
was_active = true;
this->parent_->enqueue(make_unique<PowerCommand>(false));
}
this->parent_->enqueue(make_unique<UartOutputCommand>(state));
this->parent_->enqueue(make_unique<SaveCfgCommand>());
if (was_active) {
this->parent_->enqueue(make_unique<PowerCommand>(true));
}
}
void Sen0395StartAfterBootSwitch::write_state(bool state) {
bool was_active = false;
if (this->parent_->is_active()) {
was_active = true;
this->parent_->enqueue(make_unique<PowerCommand>(false));
}
this->parent_->enqueue(make_unique<SensorCfgStartCommand>(state));
this->parent_->enqueue(make_unique<SaveCfgCommand>());
if (was_active) {
this->parent_->enqueue(make_unique<PowerCommand>(true));
}
}
} // namespace dfrobot_sen0395
} // namespace esphome

View File

@ -0,0 +1,34 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "esphome/core/component.h"
#include "../dfrobot_sen0395.h"
namespace esphome {
namespace dfrobot_sen0395 {
class DfrobotSen0395Switch : public switch_::Switch, public Component, public Parented<DfrobotSen0395Component> {};
class Sen0395PowerSwitch : public DfrobotSen0395Switch {
public:
void write_state(bool state) override;
};
class Sen0395LedSwitch : public DfrobotSen0395Switch {
public:
void write_state(bool state) override;
};
class Sen0395UartPresenceSwitch : public DfrobotSen0395Switch {
public:
void write_state(bool state) override;
};
class Sen0395StartAfterBootSwitch : public DfrobotSen0395Switch {
public:
void write_state(bool state) override;
};
} // namespace dfrobot_sen0395
} // namespace esphome

View File

@ -95,7 +95,7 @@ void DutyTimeSensor::publish_and_save_(const uint32_t sec, const uint32_t ms) {
void DutyTimeSensor::dump_config() {
ESP_LOGCONFIG(TAG, "Duty Time:");
ESP_LOGCONFIG(TAG, " Update Interval: %dms", this->get_update_interval());
ESP_LOGCONFIG(TAG, " Update Interval: %" PRId32 "ms", this->get_update_interval());
ESP_LOGCONFIG(TAG, " Restore: %s", ONOFF(this->restore_));
LOG_SENSOR(" ", "Duty Time Sensor:", this);
LOG_SENSOR(" ", "Last Duty Time Sensor:", this->last_duty_time_sensor_);

View File

@ -1,5 +1,7 @@
#pragma once
#include <cinttypes>
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/preferences.h"

View File

@ -3,6 +3,7 @@
#include "esphome/components/socket/socket.h"
#include "esphome/core/component.h"
#include <cinttypes>
#include <map>
#include <memory>
#include <set>

View File

@ -57,8 +57,8 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet
std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1));
auto *input_data = packet.values + 1;
ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %d-%d.", get_name().c_str(), universe, output_offset,
output_end);
ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %" PRId32 "-%d.", get_name().c_str(), universe,
output_offset, output_end);
switch (channels_) {
case E131_MONO:

View File

@ -0,0 +1,81 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c
from esphome.const import CONF_ID, CONF_INVERTED, CONF_RESOLUTION
CODEOWNERS = ["@ellull"]
DEPENDENCIES = ["i2c"]
CONF_PWM = "pwm"
CONF_DIVIDER = "divider"
CONF_DAC = "dac"
CONF_CONVERSION_RATE = "conversion_rate"
CONF_EMC2101_ID = "emc2101_id"
emc2101_ns = cg.esphome_ns.namespace("emc2101")
Emc2101DACConversionRate = emc2101_ns.enum("Emc2101DACConversionRate")
CONVERSIONS_PER_SECOND = {
"1/16": Emc2101DACConversionRate.Emc2101_DAC_1_EVERY_16S,
"1/8": Emc2101DACConversionRate.Emc2101_DAC_1_EVERY_8S,
"1/4": Emc2101DACConversionRate.Emc2101_DAC_1_EVERY_4S,
"1/2": Emc2101DACConversionRate.Emc2101_DAC_1_EVERY_2S,
"1": Emc2101DACConversionRate.Emc2101_DAC_1_EVERY_SECOND,
"2": Emc2101DACConversionRate.Emc2101_DAC_2_EVERY_SECOND,
"4": Emc2101DACConversionRate.Emc2101_DAC_4_EVERY_SECOND,
"8": Emc2101DACConversionRate.Emc2101_DAC_8_EVERY_SECOND,
"16": Emc2101DACConversionRate.Emc2101_DAC_16_EVERY_SECOND,
"32": Emc2101DACConversionRate.Emc2101_DAC_32_EVERY_SECOND,
}
Emc2101Component = emc2101_ns.class_("Emc2101Component", cg.Component, i2c.I2CDevice)
EMC2101_COMPONENT_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_EMC2101_ID): cv.use_id(Emc2101Component),
}
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(Emc2101Component),
cv.Optional(CONF_PWM): cv.Schema(
{
cv.Optional(CONF_RESOLUTION, default=23): cv.int_range(
min=0, max=31
),
cv.Optional(CONF_DIVIDER, default=1): cv.uint8_t,
}
),
cv.Optional(CONF_DAC): cv.Schema(
{
cv.Optional(CONF_CONVERSION_RATE, default="16"): cv.enum(
CONVERSIONS_PER_SECOND
),
}
),
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x4C)),
cv.has_exactly_one_key(CONF_PWM, CONF_DAC),
)
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 pwm_config := config.get(CONF_PWM):
cg.add(var.set_dac_mode(False))
cg.add(var.set_pwm_resolution(pwm_config[CONF_RESOLUTION]))
cg.add(var.set_pwm_divider(pwm_config[CONF_DIVIDER]))
if dac_config := config.get(CONF_DAC):
cg.add(var.set_dac_mode(True))
cg.add(var.set_dac_conversion_rate(dac_config[CONF_CONVERSION_RATE]))
cg.add(var.set_inverted(config[CONF_INVERTED]))

View File

@ -0,0 +1,169 @@
// Implementation based on:
// - Adafruit_EMC2101: https://github.com/adafruit/Adafruit_EMC2101
// - Official Datasheet: https://ww1.microchip.com/downloads/en/DeviceDoc/2101.pdf
#include "esphome/core/log.h"
#include "emc2101.h"
namespace esphome {
namespace emc2101 {
static const char *const TAG = "EMC2101";
static const uint8_t EMC2101_CHIP_ID = 0x16; // EMC2101 default device id from part id
static const uint8_t EMC2101_ALT_CHIP_ID = 0x28; // EMC2101 alternate device id from part id
// EMC2101 registers from the datasheet. We only define what we use.
static const uint8_t EMC2101_REGISTER_INTERNAL_TEMP = 0x00; // The internal temperature register
static const uint8_t EMC2101_REGISTER_EXTERNAL_TEMP_MSB = 0x01; // high byte for the external temperature reading
static const uint8_t EMC2101_REGISTER_DAC_CONV_RATE = 0x04; // DAC convesion rate config
static const uint8_t EMC2101_REGISTER_EXTERNAL_TEMP_LSB = 0x10; // low byte for the external temperature reading
static const uint8_t EMC2101_REGISTER_CONFIG = 0x03; // configuration register
static const uint8_t EMC2101_REGISTER_TACH_LSB = 0x46; // Tach RPM data low byte
static const uint8_t EMC2101_REGISTER_TACH_MSB = 0x47; // Tach RPM data high byte
static const uint8_t EMC2101_REGISTER_FAN_CONFIG = 0x4A; // General fan config register
static const uint8_t EMC2101_REGISTER_FAN_SETTING = 0x4C; // Fan speed for non-LUT settings
static const uint8_t EMC2101_REGISTER_PWM_FREQ = 0x4D; // PWM frequency setting
static const uint8_t EMC2101_REGISTER_PWM_DIV = 0x4E; // PWM frequency divisor
static const uint8_t EMC2101_REGISTER_WHOAMI = 0xFD; // Chip ID register
// EMC2101 configuration bits from the datasheet. We only define what we use.
// Determines the funcionallity of the ALERT/TACH pin.
// 0 (default): The ALERT/TECH pin will function as an open drain, active low interrupt.
// 1: The ALERT/TECH pin will function as a high impedance TACH input. This may require an
// external pull-up resistor to set the proper signaling levels.
static const uint8_t EMC2101_ALT_TCH_BIT = 1 << 2;
// Determines the FAN output mode.
// 0 (default): PWM output enabled at FAN pin.
// 1: DAC output enabled at FAN ping.
static const uint8_t EMC2101_DAC_BIT = 1 << 4;
// Overrides the CLK_SEL bit and uses the Frequency Divide Register to determine
// the base PWM frequency. It is recommended that this bit be set for maximum PWM resolution.
// 0 (default): The base clock frequency for the PWM is determined by the CLK_SEL bit.
// 1 (recommended): The base clock that is used to determine the PWM frequency is set by the
// Frequency Divide Register
static const uint8_t EMC2101_CLK_OVR_BIT = 1 << 2;
// Sets the polarity of the Fan output driver.
// 0 (default): The polarity of the Fan output driver is non-inverted. A '00h' setting will
// correspond to a 0% duty cycle or a minimum DAC output voltage.
// 1: The polarity of the Fan output driver is inverted. A '00h' setting will correspond to a
// 100% duty cycle or a maximum DAC output voltage.
static const uint8_t EMC2101_POLARITY_BIT = 1 << 4;
float Emc2101Component::get_setup_priority() const { return setup_priority::HARDWARE; }
void Emc2101Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up Emc2101 sensor...");
// make sure we're talking to the right chip
uint8_t chip_id = reg(EMC2101_REGISTER_WHOAMI).get();
if ((chip_id != EMC2101_CHIP_ID) && (chip_id != EMC2101_ALT_CHIP_ID)) {
ESP_LOGE(TAG, "Wrong chip ID %02X", chip_id);
this->mark_failed();
return;
}
// Configure EMC2101
i2c::I2CRegister config = reg(EMC2101_REGISTER_CONFIG);
config |= EMC2101_ALT_TCH_BIT;
if (this->dac_mode_) {
config |= EMC2101_DAC_BIT;
}
if (this->inverted_) {
config |= EMC2101_POLARITY_BIT;
}
if (this->dac_mode_) { // DAC mode configurations
// set DAC conversion rate
reg(EMC2101_REGISTER_DAC_CONV_RATE) = this->dac_conversion_rate_;
} else { // PWM mode configurations
// set PWM divider
reg(EMC2101_REGISTER_FAN_CONFIG) |= EMC2101_CLK_OVR_BIT;
reg(EMC2101_REGISTER_PWM_DIV) = this->pwm_divider_;
// set PWM resolution
reg(EMC2101_REGISTER_PWM_FREQ) = this->pwm_resolution_;
}
}
void Emc2101Component::dump_config() {
ESP_LOGCONFIG(TAG, "Emc2101 component:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with EMC2101 failed!");
}
ESP_LOGCONFIG(TAG, " Mode: %s", this->dac_mode_ ? "DAC" : "PWM");
if (this->dac_mode_) {
ESP_LOGCONFIG(TAG, " DAC Conversion Rate: %X", this->dac_conversion_rate_);
} else {
ESP_LOGCONFIG(TAG, " PWM Resolution: %02X", this->pwm_resolution_);
ESP_LOGCONFIG(TAG, " PWM Divider: %02X", this->pwm_divider_);
}
ESP_LOGCONFIG(TAG, " Inverted: %s", YESNO(this->inverted_));
}
void Emc2101Component::set_duty_cycle(float value) {
uint8_t duty_cycle = remap(value, 0.0f, 1.0f, (uint8_t) 0, this->max_output_value_);
ESP_LOGD(TAG, "Setting duty fan setting to %02X", duty_cycle);
if (!this->write_byte(EMC2101_REGISTER_FAN_SETTING, duty_cycle)) {
ESP_LOGE(TAG, "Communication with EMC2101 failed!");
this->status_set_warning();
return;
}
}
float Emc2101Component::get_duty_cycle() {
uint8_t duty_cycle;
if (!this->read_byte(EMC2101_REGISTER_FAN_SETTING, &duty_cycle)) {
ESP_LOGE(TAG, "Communication with EMC2101 failed!");
this->status_set_warning();
return NAN;
}
return remap(duty_cycle, (uint8_t) 0, this->max_output_value_, 0.0f, 1.0f);
}
float Emc2101Component::get_internal_temperature() {
uint8_t temperature;
if (!this->read_byte(EMC2101_REGISTER_INTERNAL_TEMP, &temperature)) {
ESP_LOGE(TAG, "Communication with EMC2101 failed!");
this->status_set_warning();
return NAN;
}
return temperature;
}
float Emc2101Component::get_external_temperature() {
// Read **MSB** first to match 'Data Read Interlock' behavior from 6.1 of datasheet
uint8_t lsb, msb;
if (!this->read_byte(EMC2101_REGISTER_EXTERNAL_TEMP_MSB, &msb) ||
!this->read_byte(EMC2101_REGISTER_EXTERNAL_TEMP_LSB, &lsb)) {
ESP_LOGE(TAG, "Communication with EMC2101 failed!");
this->status_set_warning();
return NAN;
}
// join msb and lsb (5 least significant bits are not used)
uint16_t raw = (msb << 8 | lsb) >> 5;
return raw * 0.125;
}
float Emc2101Component::get_speed() {
// Read **LSB** first to match 'Data Read Interlock' behavior from 6.1 of datasheet
uint8_t lsb, msb;
if (!this->read_byte(EMC2101_REGISTER_TACH_LSB, &lsb) || !this->read_byte(EMC2101_REGISTER_TACH_MSB, &msb)) {
ESP_LOGE(TAG, "Communication with EMC2101 failed!");
this->status_set_warning();
return NAN;
}
// calculate RPMs
uint16_t tach = msb << 8 | lsb;
return tach == 0xFFFF ? 0.0f : 5400000.0f / tach;
}
} // namespace emc2101
} // namespace esphome

View File

@ -0,0 +1,115 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/component.h"
namespace esphome {
namespace emc2101 {
/** Enum listing all DAC conversion rates for the EMC2101.
*
* Specific values of the enum constants are register values taken from the EMC2101 datasheet.
*/
enum Emc2101DACConversionRate {
EMC2101_DAC_1_EVERY_16_S,
EMC2101_DAC_1_EVERY_8_S,
EMC2101_DAC_1_EVERY_4_S,
EMC2101_DAC_1_EVERY_2_S,
EMC2101_DAC_1_EVERY_SECOND,
EMC2101_DAC_2_EVERY_SECOND,
EMC2101_DAC_4_EVERY_SECOND,
EMC2101_DAC_8_EVERY_SECOND,
EMC2101_DAC_16_EVERY_SECOND,
EMC2101_DAC_32_EVERY_SECOND,
};
/// This class includes support for the EMC2101 i2c fan controller.
/// The device has an output (PWM or DAC) and several sensors and this
/// class is for the EMC2101 configuration.
class Emc2101Component : public Component, public i2c::I2CDevice {
public:
/** Sets the mode of the output.
*
* @param dac_mode false for PWM output and true for DAC mode.
*/
void set_dac_mode(bool dac_mode) {
this->dac_mode_ = dac_mode;
this->max_output_value_ = 63;
}
/** Sets the PWM resolution.
*
* @param resolution the PWM resolution.
*/
void set_pwm_resolution(uint8_t resolution) {
this->pwm_resolution_ = resolution;
this->max_output_value_ = 2 * resolution;
}
/** Sets the PWM divider used to derive the PWM frequency.
*
* @param divider The PWM divider.
*/
void set_pwm_divider(uint8_t divider) { this->pwm_divider_ = divider; }
/** Sets the DAC conversion rate (how many conversions per second).
*
* @param conversion_rate The DAC conversion rate.
*/
void set_dac_conversion_rate(Emc2101DACConversionRate conversion_rate) {
this->dac_conversion_rate_ = conversion_rate;
}
/** Inverts the polarity of the Fan output.
*
* @param inverted Invert or not the Fan output.
*/
void set_inverted(bool inverted) { this->inverted_ = inverted; }
/** Sets the Fan output duty cycle
*
* @param value The duty cycle value, from 0.0f to 1.0f.
*/
void set_duty_cycle(float value);
/** Gets the Fan output duty cycle
*
* @return The duty cycle percentage from 0.0f to 1.0f.
*/
float get_duty_cycle();
/** Gets the internal temperature sensor reading.
*
* @return The temperature in degrees celsius.
*/
float get_internal_temperature();
/** Gets the external temperature sensor reading.
*
* @return The temperature in degrees celsius.
*/
float get_external_temperature();
/** Gets the tachometer speed sensor reading.
*
* @return The fan speed in RPMs.
*/
float get_speed();
/** Used by ESPHome framework. */
void setup() override;
/** Used by ESPHome framework. */
void dump_config() override;
/** Used by ESPHome framework. */
float get_setup_priority() const override;
bool dac_mode_{false};
bool inverted_{false};
uint8_t max_output_value_;
uint8_t pwm_resolution_;
uint8_t pwm_divider_;
Emc2101DACConversionRate dac_conversion_rate_;
};
} // namespace emc2101
} // namespace esphome

View File

@ -0,0 +1,21 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import output
from esphome.const import CONF_ID
from .. import EMC2101_COMPONENT_SCHEMA, CONF_EMC2101_ID, emc2101_ns
DEPENDENCIES = ["emc2101"]
EMC2101Output = emc2101_ns.class_("EMC2101Output", output.FloatOutput)
CONFIG_SCHEMA = EMC2101_COMPONENT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(EMC2101Output),
}
)
async def to_code(config):
paren = await cg.get_variable(config[CONF_EMC2101_ID])
var = cg.new_Pvariable(config[CONF_ID], paren)
await output.register_output(var, config)

View File

@ -0,0 +1,9 @@
#include "emc2101_output.h"
namespace esphome {
namespace emc2101 {
void EMC2101Output::write_state(float state) { this->parent_->set_duty_cycle(state); }
} // namespace emc2101
} // namespace esphome

View File

@ -0,0 +1,22 @@
#pragma once
#include "../emc2101.h"
#include "esphome/components/output/float_output.h"
namespace esphome {
namespace emc2101 {
/// This class allows to control the EMC2101 output.
class EMC2101Output : public output::FloatOutput {
public:
EMC2101Output(Emc2101Component *parent) : parent_(parent) {}
protected:
/** Used by ESPHome framework. */
void write_state(float state) override;
Emc2101Component *parent_;
};
} // namespace emc2101
} // namespace esphome

View File

@ -0,0 +1,74 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_ID,
CONF_SPEED,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
UNIT_REVOLUTIONS_PER_MINUTE,
ICON_PERCENT,
)
from .. import EMC2101_COMPONENT_SCHEMA, CONF_EMC2101_ID, emc2101_ns
DEPENDENCIES = ["emc2101"]
CONF_INTERNAL_TEMPERATURE = "internal_temperature"
CONF_EXTERNAL_TEMPERATURE = "external_temperature"
CONF_DUTY_CYCLE = "duty_cycle"
EMC2101Sensor = emc2101_ns.class_("EMC2101Sensor", cg.PollingComponent)
CONFIG_SCHEMA = EMC2101_COMPONENT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(EMC2101Sensor),
cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=0,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=3,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_SPEED): sensor.sensor_schema(
unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE,
accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT,
icon="mdi:fan",
),
cv.Optional(CONF_DUTY_CYCLE): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT,
icon=ICON_PERCENT,
),
}
).extend(cv.polling_component_schema("60s"))
async def to_code(config):
paren = await cg.get_variable(config[CONF_EMC2101_ID])
var = cg.new_Pvariable(config[CONF_ID], paren)
await cg.register_component(var, config)
if CONF_INTERNAL_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_INTERNAL_TEMPERATURE])
cg.add(var.set_internal_temperature_sensor(sens))
if CONF_EXTERNAL_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_EXTERNAL_TEMPERATURE])
cg.add(var.set_external_temperature_sensor(sens))
if CONF_SPEED in config:
sens = await sensor.new_sensor(config[CONF_SPEED])
cg.add(var.set_speed_sensor(sens))
if CONF_DUTY_CYCLE in config:
sens = await sensor.new_sensor(config[CONF_DUTY_CYCLE])
cg.add(var.set_duty_cycle_sensor(sens))

View File

@ -0,0 +1,43 @@
#include "emc2101_sensor.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace emc2101 {
static const char *const TAG = "EMC2101.sensor";
float EMC2101Sensor::get_setup_priority() const { return setup_priority::DATA; }
void EMC2101Sensor::dump_config() {
ESP_LOGCONFIG(TAG, "Emc2101 sensor:");
LOG_SENSOR(" ", "Internal temperature", this->internal_temperature_sensor_);
LOG_SENSOR(" ", "External temperature", this->external_temperature_sensor_);
LOG_SENSOR(" ", "Speed", this->speed_sensor_);
LOG_SENSOR(" ", "Duty cycle", this->duty_cycle_sensor_);
}
void EMC2101Sensor::update() {
if (this->internal_temperature_sensor_ != nullptr) {
float internal_temperature = this->parent_->get_internal_temperature();
this->internal_temperature_sensor_->publish_state(internal_temperature);
}
if (this->external_temperature_sensor_ != nullptr) {
float external_temperature = this->parent_->get_external_temperature();
this->external_temperature_sensor_->publish_state(external_temperature);
}
if (this->speed_sensor_ != nullptr) {
float speed = this->parent_->get_speed();
this->speed_sensor_->publish_state(speed);
}
if (this->duty_cycle_sensor_ != nullptr) {
float duty_cycle = this->parent_->get_duty_cycle();
this->duty_cycle_sensor_->publish_state(duty_cycle * 100.0f);
}
}
} // namespace emc2101
} // namespace esphome

View File

@ -0,0 +1,39 @@
#pragma once
#include "../emc2101.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
namespace esphome {
namespace emc2101 {
/// This class exposes the EMC2101 sensors.
class EMC2101Sensor : public PollingComponent {
public:
EMC2101Sensor(Emc2101Component *parent) : parent_(parent) {}
/** Used by ESPHome framework. */
void dump_config() override;
/** Used by ESPHome framework. */
void update() override;
/** Used by ESPHome framework. */
float get_setup_priority() const override;
/** Used by ESPHome framework. */
void set_internal_temperature_sensor(sensor::Sensor *sensor) { this->internal_temperature_sensor_ = sensor; }
/** Used by ESPHome framework. */
void set_external_temperature_sensor(sensor::Sensor *sensor) { this->external_temperature_sensor_ = sensor; }
/** Used by ESPHome framework. */
void set_speed_sensor(sensor::Sensor *sensor) { this->speed_sensor_ = sensor; }
/** Used by ESPHome framework. */
void set_duty_cycle_sensor(sensor::Sensor *sensor) { this->duty_cycle_sensor_ = sensor; }
protected:
Emc2101Component *parent_;
sensor::Sensor *internal_temperature_sensor_{nullptr};
sensor::Sensor *external_temperature_sensor_{nullptr};
sensor::Sensor *speed_sensor_{nullptr};
sensor::Sensor *duty_cycle_sensor_{nullptr};
};
} // namespace emc2101
} // namespace esphome

View File

@ -386,10 +386,21 @@ FRAMEWORK_SCHEMA = cv.typed_schema(
)
FLASH_SIZES = [
"4MB",
"8MB",
"16MB",
"32MB",
]
CONF_FLASH_SIZE = "flash_size"
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.Required(CONF_BOARD): cv.string_strict,
cv.Optional(CONF_FLASH_SIZE, default="4MB"): cv.one_of(
*FLASH_SIZES, upper=True
),
cv.Optional(CONF_VARIANT): cv.one_of(*VARIANTS, upper=True),
cv.Optional(CONF_FRAMEWORK, default={}): FRAMEWORK_SCHEMA,
}
@ -401,6 +412,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
cg.add_platformio_option("board", config[CONF_BOARD])
cg.add_platformio_option("board_upload.flash_size", config[CONF_FLASH_SIZE])
cg.add_build_flag("-DUSE_ESP32")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
@ -505,24 +517,46 @@ async def to_code(config):
)
ARDUINO_PARTITIONS_CSV = """\
nvs, data, nvs, 0x009000, 0x005000,
otadata, data, ota, 0x00e000, 0x002000,
app0, app, ota_0, 0x010000, 0x1C0000,
app1, app, ota_1, 0x1D0000, 0x1C0000,
eeprom, data, 0x99, 0x390000, 0x001000,
spiffs, data, spiffs, 0x391000, 0x00F000
APP_PARTITION_SIZES = {
"4MB": 0x1C0000, # 1792 KB
"8MB": 0x3C0000, # 3840 KB
"16MB": 0x7C0000, # 7936 KB
"32MB": 0xFC0000, # 16128 KB
}
def get_arduino_partition_csv(flash_size):
app_partition_size = APP_PARTITION_SIZES[flash_size]
eeprom_partition_size = 0x1000 # 4 KB
spiffs_partition_size = 0xF000 # 60 KB
app0_partition_start = 0x010000 # 64 KB
app1_partition_start = app0_partition_start + app_partition_size
eeprom_partition_start = app1_partition_start + app_partition_size
spiffs_partition_start = eeprom_partition_start + eeprom_partition_size
partition_csv = f"""\
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xE000, 0x2000,
app0, app, ota_0, 0x{app0_partition_start:X}, 0x{app_partition_size:X},
app1, app, ota_1, 0x{app1_partition_start:X}, 0x{app_partition_size:X},
eeprom, data, 0x99, 0x{eeprom_partition_start:X}, 0x{eeprom_partition_size:X},
spiffs, data, spiffs, 0x{spiffs_partition_start:X}, 0x{spiffs_partition_size:X}
"""
return partition_csv
IDF_PARTITIONS_CSV = """\
# Name, Type, SubType, Offset, Size, Flags
def get_idf_partition_csv(flash_size):
app_partition_size = APP_PARTITION_SIZES[flash_size]
partition_csv = f"""\
otadata, data, ota, , 0x2000,
phy_init, data, phy, , 0x1000,
app0, app, ota_0, , 0x1C0000,
app1, app, ota_1, , 0x1C0000,
nvs, data, nvs, , 0x6d000,
app0, app, ota_0, , 0x{app_partition_size:X},
app1, app, ota_1, , 0x{app_partition_size:X},
nvs, data, nvs, , 0x6D000,
"""
return partition_csv
def _format_sdkconfig_val(value: SdkconfigValueType) -> str:
@ -565,13 +599,17 @@ def copy_files():
if CORE.using_arduino:
write_file_if_changed(
CORE.relative_build_path("partitions.csv"),
ARDUINO_PARTITIONS_CSV,
get_arduino_partition_csv(
CORE.platformio_options.get("board_upload.flash_size")
),
)
if CORE.using_esp_idf:
_write_sdkconfig()
write_file_if_changed(
CORE.relative_build_path("partitions.csv"),
IDF_PARTITIONS_CSV,
get_idf_partition_csv(
CORE.platformio_options.get("board_upload.flash_size")
),
)
# IDF build scripts look for version string to put in the build.
# However, if the build path does not have an initialized git repo,

View File

@ -11,6 +11,7 @@ from esphome.const import (
CONF_OUTPUT,
CONF_PULLDOWN,
CONF_PULLUP,
CONF_IGNORE_STRAPPING_WARNING,
)
from esphome import pins
from esphome.core import CORE
@ -176,6 +177,7 @@ ESP32_PIN_SCHEMA = cv.All(
}
),
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
cv.Optional(CONF_IGNORE_STRAPPING_WARNING, default=False): cv.boolean,
cv.Optional(CONF_DRIVE_STRENGTH, default="20mA"): cv.All(
cv.float_with_unit("current", "mA", optional_unit=True),
cv.enum(DRIVE_STRENGTHS),

View File

@ -9,6 +9,7 @@ from esphome.const import (
CONF_PULLUP,
)
import esphome.config_validation as cv
from esphome.pins import check_strapping_pin
_ESP_SDIO_PINS = {
@ -35,13 +36,6 @@ def esp32_validate_gpio_pin(value):
"flash interface in QUAD IO flash mode.",
value,
)
if value in _ESP32_STRAPPING_PINS:
_LOGGER.warning(
"GPIO%d is a Strapping PIN and should be avoided.\n"
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
value,
)
if value in (24, 28, 29, 30, 31):
# These pins are not exposed in GPIO mux (reason unknown)
# but they're missing from IO_MUX list in datasheet
@ -74,4 +68,5 @@ def esp32_validate_supports(value):
f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
)
check_strapping_pin(value, _ESP32_STRAPPING_PINS, _LOGGER)
return value

View File

@ -1,6 +1,7 @@
import logging
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.pins import check_strapping_pin
import esphome.config_validation as cv
@ -12,13 +13,6 @@ _LOGGER = logging.getLogger(__name__)
def esp32_c2_validate_gpio_pin(value):
if value < 0 or value > 20:
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-20)")
if value in _ESP32C2_STRAPPING_PINS:
_LOGGER.warning(
"GPIO%d is a Strapping PIN and should be avoided.\n"
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
value,
)
return value
@ -34,4 +28,6 @@ def esp32_c2_validate_supports(value):
if is_input:
# All ESP32 pins support input mode
pass
check_strapping_pin(value, _ESP32C2_STRAPPING_PINS, _LOGGER)
return value

View File

@ -6,6 +6,7 @@ from esphome.const import (
CONF_NUMBER,
)
import esphome.config_validation as cv
from esphome.pins import check_strapping_pin
_ESP32C3_SPI_PSRAM_PINS = {
12: "SPIHD",
@ -28,13 +29,6 @@ def esp32_c3_validate_gpio_pin(value):
raise cv.Invalid(
f"This pin cannot be used on ESP32-C3s and is already used by the SPI/PSRAM interface (function: {_ESP32C3_SPI_PSRAM_PINS[value]})"
)
if value in _ESP32C3_STRAPPING_PINS:
_LOGGER.warning(
"GPIO%d is a Strapping PIN and should be avoided.\n"
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
value,
)
return value
@ -50,4 +44,6 @@ def esp32_c3_validate_supports(value):
if is_input:
# All ESP32 pins support input mode
pass
check_strapping_pin(value, _ESP32C3_STRAPPING_PINS, _LOGGER)
return value

View File

@ -3,6 +3,7 @@ import logging
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
import esphome.config_validation as cv
from esphome.pins import check_strapping_pin
_ESP32C6_SPI_PSRAM_PINS = {
24: "SPICS0",
@ -26,13 +27,6 @@ def esp32_c6_validate_gpio_pin(value):
raise cv.Invalid(
f"This pin cannot be used on ESP32-C6s and is already used by the SPI/PSRAM interface (function: {_ESP32C6_SPI_PSRAM_PINS[value]})"
)
if value in _ESP32C6_STRAPPING_PINS:
_LOGGER.warning(
"GPIO%d is a Strapping PIN and should be avoided.\n"
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
value,
)
return value
@ -47,4 +41,6 @@ def esp32_c6_validate_supports(value):
if is_input:
# All ESP32 pins support input mode
pass
check_strapping_pin(value, _ESP32C6_STRAPPING_PINS, _LOGGER)
return value

View File

@ -10,6 +10,7 @@ from esphome.const import (
)
import esphome.config_validation as cv
from esphome.pins import check_strapping_pin
_ESP32S2_SPI_PSRAM_PINS = {
26: "SPICS1",
@ -34,13 +35,6 @@ def esp32_s2_validate_gpio_pin(value):
raise cv.Invalid(
f"This pin cannot be used on ESP32-S2s and is already used by the SPI/PSRAM interface (function: {_ESP32S2_SPI_PSRAM_PINS[value]})"
)
if value in _ESP32S2_STRAPPING_PINS:
_LOGGER.warning(
"GPIO%d is a Strapping PIN and should be avoided.\n"
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
value,
)
if value in (22, 23, 24, 25):
# These pins are not exposed in GPIO mux (reason unknown)
@ -77,4 +71,5 @@ def esp32_s2_validate_supports(value):
f"GPIO{num} does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
)
check_strapping_pin(value, _ESP32S2_STRAPPING_PINS, _LOGGER)
return value

View File

@ -7,6 +7,7 @@ from esphome.const import (
)
import esphome.config_validation as cv
from esphome.pins import check_strapping_pin
_ESP_32S3_SPI_PSRAM_PINS = {
26: "SPICS1",
@ -45,14 +46,6 @@ def esp32_s3_validate_gpio_pin(value):
value,
)
if value in _ESP_32S3_STRAPPING_PINS:
_LOGGER.warning(
"GPIO%d is a Strapping PIN and should be avoided.\n"
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
value,
)
if value in (22, 23, 24, 25):
# These pins are not exposed in GPIO mux (reason unknown)
# but they're missing from IO_MUX list in datasheet
@ -71,4 +64,6 @@ def esp32_s3_validate_supports(value):
if is_input:
# All ESP32 pins support input mode
pass
check_strapping_pin(value, _ESP_32S3_STRAPPING_PINS, _LOGGER)
return value

View File

@ -1,15 +1,17 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.const import CONF_ID
from esphome.core import CORE
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant, const
DEPENDENCIES = ["esp32"]
CODEOWNERS = ["@jesserockz"]
CODEOWNERS = ["@jesserockz", "@Rapsssito"]
CONFLICTS_WITH = ["esp32_ble_beacon"]
CONF_BLE_ID = "ble_id"
CONF_IO_CAPABILITY = "io_capability"
CONF_ENABLE_ON_BOOT = "enable_on_boot"
NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
@ -20,6 +22,10 @@ GAPEventHandler = esp32_ble_ns.class_("GAPEventHandler")
GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler")
GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler")
BLEEnabledCondition = esp32_ble_ns.class_("BLEEnabledCondition", automation.Condition)
BLEEnableAction = esp32_ble_ns.class_("BLEEnableAction", automation.Action)
BLEDisableAction = esp32_ble_ns.class_("BLEDisableAction", automation.Action)
IoCapability = esp32_ble_ns.enum("IoCapability")
IO_CAPABILITY = {
"none": IoCapability.IO_CAP_NONE,
@ -35,6 +41,7 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum(
IO_CAPABILITY, lower=True
),
cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
}
).extend(cv.COMPONENT_SCHEMA)
@ -50,9 +57,25 @@ FINAL_VALIDATE_SCHEMA = validate_variant
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT]))
cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY]))
await cg.register_component(var, config)
if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)
@automation.register_condition("ble.enabled", BLEEnabledCondition, cv.Schema({}))
async def ble_enabled_to_code(config, condition_id, template_arg, args):
return cg.new_Pvariable(condition_id, template_arg)
@automation.register_action("ble.enable", BLEEnableAction, cv.Schema({}))
async def ble_enable_to_code(config, action_id, template_arg, args):
return cg.new_Pvariable(action_id, template_arg)
@automation.register_action("ble.disable", BLEDisableAction, cv.Schema({}))
async def ble_disable_to_code(config, action_id, template_arg, args):
return cg.new_Pvariable(action_id, template_arg)

View File

@ -5,8 +5,8 @@
#include "esphome/core/log.h"
#include <esp_bt.h>
#include <esp_bt_main.h>
#include <esp_bt_device.h>
#include <esp_bt_main.h>
#include <esp_gap_ble_api.h>
#include <freertos/FreeRTOS.h>
#include <freertos/FreeRTOSConfig.h>
@ -26,30 +26,85 @@ void ESP32BLE::setup() {
global_ble = this;
ESP_LOGCONFIG(TAG, "Setting up BLE...");
if (!ble_setup_()) {
ESP_LOGE(TAG, "BLE could not be set up");
if (!ble_pre_setup_()) {
ESP_LOGE(TAG, "BLE could not be prepared for configuration");
this->mark_failed();
return;
}
#ifdef USE_ESP32_BLE_SERVER
this->advertising_ = new BLEAdvertising(); // NOLINT(cppcoreguidelines-owning-memory)
this->advertising_->set_scan_response(true);
this->advertising_->set_min_preferred_interval(0x06);
this->advertising_->start();
#endif // USE_ESP32_BLE_SERVER
ESP_LOGD(TAG, "BLE setup complete");
this->state_ = BLE_COMPONENT_STATE_DISABLED;
if (this->enable_on_boot_) {
this->enable();
}
}
bool ESP32BLE::ble_setup_() {
void ESP32BLE::enable() {
if (this->state_ != BLE_COMPONENT_STATE_DISABLED)
return;
this->state_ = BLE_COMPONENT_STATE_ENABLE;
}
void ESP32BLE::disable() {
if (this->state_ == BLE_COMPONENT_STATE_DISABLED)
return;
this->state_ = BLE_COMPONENT_STATE_DISABLE;
}
bool ESP32BLE::is_active() { return this->state_ == BLE_COMPONENT_STATE_ACTIVE; }
void ESP32BLE::advertising_start() {
this->advertising_init_();
if (!this->is_active())
return;
this->advertising_->start();
}
void ESP32BLE::advertising_set_service_data(const std::vector<uint8_t> &data) {
this->advertising_init_();
this->advertising_->set_service_data(data);
this->advertising_start();
}
void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &data) {
this->advertising_init_();
this->advertising_->set_manufacturer_data(data);
this->advertising_start();
}
void ESP32BLE::advertising_add_service_uuid(ESPBTUUID uuid) {
this->advertising_init_();
this->advertising_->add_service_uuid(uuid);
this->advertising_start();
}
void ESP32BLE::advertising_remove_service_uuid(ESPBTUUID uuid) {
this->advertising_init_();
this->advertising_->remove_service_uuid(uuid);
this->advertising_start();
}
bool ESP32BLE::ble_pre_setup_() {
esp_err_t err = nvs_flash_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "nvs_flash_init failed: %d", err);
return false;
}
return true;
}
void ESP32BLE::advertising_init_() {
if (this->advertising_ != nullptr)
return;
this->advertising_ = new BLEAdvertising(); // NOLINT(cppcoreguidelines-owning-memory)
this->advertising_->set_scan_response(true);
this->advertising_->set_min_preferred_interval(0x06);
}
bool ESP32BLE::ble_setup_() {
esp_err_t err;
#ifdef USE_ARDUINO
if (!btStart()) {
ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
@ -146,7 +201,88 @@ bool ESP32BLE::ble_setup_() {
return true;
}
bool ESP32BLE::ble_dismantle_() {
esp_err_t err = esp_bluedroid_disable();
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_bluedroid_disable failed: %d", err);
return false;
}
err = esp_bluedroid_deinit();
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_bluedroid_deinit failed: %d", err);
return false;
}
#ifdef USE_ARDUINO
if (!btStop()) {
ESP_LOGE(TAG, "btStop failed: %d", esp_bt_controller_get_status());
return false;
}
#else
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) {
// stop bt controller
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED) {
err = esp_bt_controller_disable();
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_bt_controller_disable failed: %s", esp_err_to_name(err));
return false;
}
while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED)
;
}
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) {
err = esp_bt_controller_deinit();
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_bt_controller_deinit failed: %s", esp_err_to_name(err));
return false;
}
}
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) {
ESP_LOGE(TAG, "esp bt controller disable failed");
return false;
}
}
#endif
return true;
}
void ESP32BLE::loop() {
switch (this->state_) {
case BLE_COMPONENT_STATE_OFF:
case BLE_COMPONENT_STATE_DISABLED:
return;
case BLE_COMPONENT_STATE_DISABLE: {
ESP_LOGD(TAG, "Disabling BLE...");
for (auto *ble_event_handler : this->ble_status_event_handlers_) {
ble_event_handler->ble_before_disabled_event_handler();
}
if (!ble_dismantle_()) {
ESP_LOGE(TAG, "BLE could not be dismantled");
this->mark_failed();
return;
}
this->state_ = BLE_COMPONENT_STATE_DISABLED;
return;
}
case BLE_COMPONENT_STATE_ENABLE: {
ESP_LOGD(TAG, "Enabling BLE...");
this->state_ = BLE_COMPONENT_STATE_OFF;
if (!ble_setup_()) {
ESP_LOGE(TAG, "BLE could not be set up");
this->mark_failed();
return;
}
this->state_ = BLE_COMPONENT_STATE_ACTIVE;
return;
}
case BLE_COMPONENT_STATE_ACTIVE:
break;
}
BLEEvent *ble_event = this->ble_events_.pop();
while (ble_event != nullptr) {
switch (ble_event->type_) {

View File

@ -1,19 +1,21 @@
#pragma once
#include "ble_advertising.h"
#include "ble_uuid.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "queue.h"
#include "ble_event.h"
#include "queue.h"
#ifdef USE_ESP32
#include <esp_gap_ble_api.h>
#include <esp_gatts_api.h>
#include <esp_gattc_api.h>
#include <esp_gatts_api.h>
namespace esphome {
namespace esp32_ble {
@ -35,6 +37,19 @@ enum IoCapability {
IO_CAP_KBDISP = ESP_IO_CAP_KBDISP,
};
enum BLEComponentState {
/** Nothing has been initialized yet. */
BLE_COMPONENT_STATE_OFF = 0,
/** BLE should be disabled on next loop. */
BLE_COMPONENT_STATE_DISABLE,
/** BLE is disabled. */
BLE_COMPONENT_STATE_DISABLED,
/** BLE should be enabled on next loop. */
BLE_COMPONENT_STATE_ENABLE,
/** BLE is active. */
BLE_COMPONENT_STATE_ACTIVE,
};
class GAPEventHandler {
public:
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
@ -52,20 +67,36 @@ class GATTsEventHandler {
esp_ble_gatts_cb_param_t *param) = 0;
};
class BLEStatusEventHandler {
public:
virtual void ble_before_disabled_event_handler() = 0;
};
class ESP32BLE : public Component {
public:
void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; }
void enable();
void disable();
bool is_active();
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override;
BLEAdvertising *get_advertising() { return this->advertising_; }
void advertising_start();
void advertising_set_service_data(const std::vector<uint8_t> &data);
void advertising_set_manufacturer_data(const std::vector<uint8_t> &data);
void advertising_add_service_uuid(ESPBTUUID uuid);
void advertising_remove_service_uuid(ESPBTUUID uuid);
void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); }
void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); }
void register_gatts_event_handler(GATTsEventHandler *handler) { this->gatts_event_handlers_.push_back(handler); }
void register_ble_status_event_handler(BLEStatusEventHandler *handler) {
this->ble_status_event_handlers_.push_back(handler);
}
void set_enable_on_boot(bool enable_on_boot) { this->enable_on_boot_ = enable_on_boot; }
protected:
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
@ -77,19 +108,40 @@ class ESP32BLE : public Component {
void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
bool ble_setup_();
bool ble_dismantle_();
bool ble_pre_setup_();
void advertising_init_();
std::vector<GAPEventHandler *> gap_event_handlers_;
std::vector<GATTcEventHandler *> gattc_event_handlers_;
std::vector<GATTsEventHandler *> gatts_event_handlers_;
std::vector<BLEStatusEventHandler *> ble_status_event_handlers_;
BLEComponentState state_{BLE_COMPONENT_STATE_OFF};
Queue<BLEEvent> ble_events_;
BLEAdvertising *advertising_;
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
bool enable_on_boot_;
};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
extern ESP32BLE *global_ble;
template<typename... Ts> class BLEEnabledCondition : public Condition<Ts...> {
public:
bool check(Ts... x) override { return global_ble->is_active(); }
};
template<typename... Ts> class BLEEnableAction : public Action<Ts...> {
public:
void play(Ts... x) override { global_ble->enable(); }
};
template<typename... Ts> class BLEDisableAction : public Action<Ts...> {
public:
void play(Ts... x) override { global_ble->disable(); }
};
} // namespace esp32_ble
} // namespace esphome

View File

@ -20,16 +20,21 @@ static const esp_bt_uuid_t NOTIFY_DESC_UUID = {
void BLEClientBase::setup() {
static uint8_t connection_index = 0;
this->connection_index_ = connection_index++;
auto ret = esp_ble_gattc_app_register(this->app_id);
if (ret) {
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
this->mark_failed();
}
this->set_state(espbt::ClientState::IDLE);
}
void BLEClientBase::loop() {
if (!esp32_ble::global_ble->is_active()) {
this->set_state(espbt::ClientState::INIT);
return;
}
if (this->state_ == espbt::ClientState::INIT) {
auto ret = esp_ble_gattc_app_register(this->app_id);
if (ret) {
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
this->mark_failed();
}
this->set_state(espbt::ClientState::IDLE);
}
// READY_TO_CONNECT means we have discovered the device
// and the scanner has been stopped by the tracker.
if (this->state_ == espbt::ClientState::READY_TO_CONNECT) {

View File

@ -6,7 +6,7 @@ from esphome.core import CORE
from esphome.components.esp32 import add_idf_sdkconfig_option
AUTO_LOAD = ["esp32_ble"]
CODEOWNERS = ["@jesserockz", "@clydebarrow"]
CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"]
CONFLICTS_WITH = ["esp32_ble_beacon"]
DEPENDENCIES = ["esp32"]
@ -41,6 +41,7 @@ async def to_code(config):
parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID])
cg.add(parent.register_gatts_event_handler(var))
cg.add(parent.register_ble_status_event_handler(var))
cg.add(var.set_parent(parent))
cg.add(var.set_manufacturer(config[CONF_MANUFACTURER]))

View File

@ -11,6 +11,13 @@ namespace esp32_ble_server {
static const char *const TAG = "esp32_ble_server.characteristic";
BLECharacteristic::~BLECharacteristic() {
for (auto *descriptor : this->descriptors_) {
delete descriptor; // NOLINT(cppcoreguidelines-owning-memory)
}
vSemaphoreDelete(this->set_value_lock_);
}
BLECharacteristic::BLECharacteristic(const ESPBTUUID uuid, uint32_t properties) : uuid_(uuid) {
this->set_value_lock_ = xSemaphoreCreateBinary();
xSemaphoreGive(this->set_value_lock_);
@ -98,6 +105,11 @@ void BLECharacteristic::notify(bool notification) {
void BLECharacteristic::add_descriptor(BLEDescriptor *descriptor) { this->descriptors_.push_back(descriptor); }
void BLECharacteristic::remove_descriptor(BLEDescriptor *descriptor) {
this->descriptors_.erase(std::remove(this->descriptors_.begin(), this->descriptors_.end(), descriptor),
this->descriptors_.end());
}
void BLECharacteristic::do_create(BLEService *service) {
this->service_ = service;
esp_attr_control_t control;

View File

@ -25,6 +25,7 @@ class BLEService;
class BLECharacteristic {
public:
BLECharacteristic(ESPBTUUID uuid, uint32_t properties);
~BLECharacteristic();
void set_value(const uint8_t *data, size_t length);
void set_value(std::vector<uint8_t> value);
@ -52,6 +53,7 @@ class BLECharacteristic {
void on_write(const std::function<void(const std::vector<uint8_t> &)> &&func) { this->on_write_ = func; }
void add_descriptor(BLEDescriptor *descriptor);
void remove_descriptor(BLEDescriptor *descriptor);
BLEService *get_service() { return this->service_; }
ESPBTUUID get_uuid() { return this->uuid_; }

View File

@ -30,13 +30,13 @@ void BLEServer::setup() {
ESP_LOGE(TAG, "BLE Server was marked failed by ESP32BLE");
return;
}
ESP_LOGD(TAG, "Setting up BLE Server...");
global_ble_server = this;
}
void BLEServer::loop() {
if (!this->parent_->is_active()) {
return;
}
switch (this->state_) {
case RUNNING:
return;
@ -53,10 +53,16 @@ void BLEServer::loop() {
}
case REGISTERING: {
if (this->registered_) {
this->device_information_service_ = this->create_service(DEVICE_INFORMATION_SERVICE_UUID);
this->create_device_characteristics_();
// Create all services previously created
for (auto &pair : this->services_) {
pair.second->do_create(this);
}
if (this->device_information_service_ == nullptr) {
this->create_service(ESPBTUUID::from_uint16(DEVICE_INFORMATION_SERVICE_UUID));
this->device_information_service_ =
this->get_service(ESPBTUUID::from_uint16(DEVICE_INFORMATION_SERVICE_UUID));
this->create_device_characteristics_();
}
this->state_ = STARTING_SERVICE;
}
break;
@ -67,7 +73,6 @@ void BLEServer::loop() {
}
if (this->device_information_service_->is_running()) {
this->state_ = RUNNING;
this->can_proceed_ = true;
this->restart_advertising_();
ESP_LOGD(TAG, "BLE server setup successfully");
} else if (!this->device_information_service_->is_starting()) {
@ -78,10 +83,13 @@ void BLEServer::loop() {
}
}
bool BLEServer::is_running() { return this->parent_->is_active() && this->state_ == RUNNING; }
bool BLEServer::can_proceed() { return this->is_running() || !this->parent_->is_active(); }
void BLEServer::restart_advertising_() {
if (this->state_ == RUNNING) {
esp32_ble::global_ble->get_advertising()->set_manufacturer_data(this->manufacturer_data_);
esp32_ble::global_ble->get_advertising()->start();
if (this->is_running()) {
this->parent_->advertising_set_manufacturer_data(this->manufacturer_data_);
}
}
@ -107,24 +115,36 @@ bool BLEServer::create_device_characteristics_() {
return true;
}
std::shared_ptr<BLEService> BLEServer::create_service(const uint8_t *uuid, bool advertise) {
return this->create_service(ESPBTUUID::from_raw(uuid), advertise);
}
std::shared_ptr<BLEService> BLEServer::create_service(uint16_t uuid, bool advertise) {
return this->create_service(ESPBTUUID::from_uint16(uuid), advertise);
}
std::shared_ptr<BLEService> BLEServer::create_service(const std::string &uuid, bool advertise) {
return this->create_service(ESPBTUUID::from_raw(uuid), advertise);
}
std::shared_ptr<BLEService> BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles,
uint8_t inst_id) {
ESP_LOGV(TAG, "Creating service - %s", uuid.to_string().c_str());
std::shared_ptr<BLEService> service = std::make_shared<BLEService>(uuid, num_handles, inst_id);
this->services_.emplace_back(service);
if (advertise) {
esp32_ble::global_ble->get_advertising()->add_service_uuid(uuid);
void BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t num_handles, uint8_t inst_id) {
ESP_LOGV(TAG, "Creating BLE service - %s", uuid.to_string().c_str());
// If the service already exists, do nothing
BLEService *service = this->get_service(uuid);
if (service != nullptr) {
ESP_LOGW(TAG, "BLE service %s already exists", uuid.to_string().c_str());
return;
}
service = new BLEService(uuid, num_handles, inst_id, advertise); // NOLINT(cppcoreguidelines-owning-memory)
this->services_.emplace(uuid.to_string(), service);
service->do_create(this);
}
void BLEServer::remove_service(ESPBTUUID uuid) {
ESP_LOGV(TAG, "Removing BLE service - %s", uuid.to_string().c_str());
BLEService *service = this->get_service(uuid);
if (service == nullptr) {
ESP_LOGW(TAG, "BLE service %s not found", uuid.to_string().c_str());
return;
}
service->do_delete();
delete service; // NOLINT(cppcoreguidelines-owning-memory)
this->services_.erase(uuid.to_string());
}
BLEService *BLEServer::get_service(ESPBTUUID uuid) {
BLEService *service = nullptr;
if (this->services_.count(uuid.to_string()) > 0) {
service = this->services_.at(uuid.to_string());
}
return service;
}
@ -144,7 +164,7 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga
ESP_LOGD(TAG, "BLE Client disconnected");
if (this->remove_client_(param->disconnect.conn_id))
this->connected_clients_--;
esp32_ble::global_ble->get_advertising()->start();
this->parent_->advertising_start();
for (auto *component : this->service_components_) {
component->on_client_disconnect();
}
@ -159,11 +179,22 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga
break;
}
for (const auto &service : this->services_) {
service->gatts_event_handler(event, gatts_if, param);
for (const auto &pair : this->services_) {
pair.second->gatts_event_handler(event, gatts_if, param);
}
}
void BLEServer::ble_before_disabled_event_handler() {
// Delete all clients
this->clients_.clear();
// Delete all services
for (auto &pair : this->services_) {
pair.second->do_delete();
}
this->registered_ = false;
this->state_ = INIT;
}
float BLEServer::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH + 10; }
void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); }

View File

@ -11,9 +11,9 @@
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#include <map>
#include <memory>
#include <vector>
#include <unordered_map>
#ifdef USE_ESP32
@ -33,15 +33,16 @@ class BLEServiceComponent {
virtual void stop();
};
class BLEServer : public Component, public GATTsEventHandler, public Parented<ESP32BLE> {
class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEventHandler, public Parented<ESP32BLE> {
public:
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override;
bool can_proceed() override { return this->can_proceed_; }
bool can_proceed() override;
void teardown();
bool is_running();
void set_manufacturer(const std::string &manufacturer) { this->manufacturer_ = manufacturer; }
void set_model(const std::string &model) { this->model_ = model; }
@ -50,32 +51,28 @@ class BLEServer : public Component, public GATTsEventHandler, public Parented<ES
this->restart_advertising_();
}
std::shared_ptr<BLEService> create_service(const uint8_t *uuid, bool advertise = false);
std::shared_ptr<BLEService> create_service(uint16_t uuid, bool advertise = false);
std::shared_ptr<BLEService> create_service(const std::string &uuid, bool advertise = false);
std::shared_ptr<BLEService> create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15,
uint8_t inst_id = 0);
void create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15, uint8_t inst_id = 0);
void remove_service(ESPBTUUID uuid);
BLEService *get_service(ESPBTUUID uuid);
esp_gatt_if_t get_gatts_if() { return this->gatts_if_; }
uint32_t get_connected_client_count() { return this->connected_clients_; }
const std::map<uint16_t, void *> &get_clients() { return this->clients_; }
const std::unordered_map<uint16_t, void *> &get_clients() { return this->clients_; }
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) override;
void ble_before_disabled_event_handler() override;
void register_service_component(BLEServiceComponent *component) { this->service_components_.push_back(component); }
protected:
bool create_device_characteristics_();
void restart_advertising_();
void add_client_(uint16_t conn_id, void *client) {
this->clients_.insert(std::pair<uint16_t, void *>(conn_id, client));
}
void add_client_(uint16_t conn_id, void *client) { this->clients_.emplace(conn_id, client); }
bool remove_client_(uint16_t conn_id) { return this->clients_.erase(conn_id) > 0; }
bool can_proceed_{false};
std::string manufacturer_;
optional<std::string> model_;
std::vector<uint8_t> manufacturer_data_;
@ -83,10 +80,9 @@ class BLEServer : public Component, public GATTsEventHandler, public Parented<ES
bool registered_{false};
uint32_t connected_clients_{0};
std::map<uint16_t, void *> clients_;
std::vector<std::shared_ptr<BLEService>> services_;
std::shared_ptr<BLEService> device_information_service_;
std::unordered_map<uint16_t, void *> clients_;
std::unordered_map<std::string, BLEService *> services_;
BLEService *device_information_service_;
std::vector<BLEServiceComponent *> service_components_;

View File

@ -9,8 +9,8 @@ namespace esp32_ble_server {
static const char *const TAG = "esp32_ble_server.service";
BLEService::BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id)
: uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id) {}
BLEService::BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id, bool advertise)
: uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id), advertise_(advertise) {}
BLEService::~BLEService() {
for (auto &chr : this->characteristics_)
@ -58,6 +58,20 @@ void BLEService::do_create(BLEServer *server) {
this->init_state_ = CREATING;
}
void BLEService::do_delete() {
if (this->init_state_ == DELETING || this->init_state_ == DELETED)
return;
this->init_state_ = DELETING;
this->created_characteristic_count_ = 0;
this->last_created_characteristic_ = nullptr;
this->stop_();
esp_err_t err = esp_ble_gatts_delete_service(this->handle_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gatts_delete_service failed: %d", err);
return;
}
}
bool BLEService::do_create_characteristics_() {
if (this->created_characteristic_count_ >= this->characteristics_.size() &&
(this->last_created_characteristic_ == nullptr || this->last_created_characteristic_->is_created()))
@ -75,24 +89,34 @@ bool BLEService::do_create_characteristics_() {
void BLEService::start() {
if (this->do_create_characteristics_())
return;
should_start_ = true;
esp_err_t err = esp_ble_gatts_start_service(this->handle_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gatts_start_service failed: %d", err);
return;
}
if (this->advertise_)
esp32_ble::global_ble->advertising_add_service_uuid(this->uuid_);
this->running_state_ = STARTING;
}
void BLEService::stop() {
should_start_ = false;
this->stop_();
}
void BLEService::stop_() {
if (this->running_state_ == STOPPING || this->running_state_ == STOPPED)
return;
this->running_state_ = STOPPING;
esp_err_t err = esp_ble_gatts_stop_service(this->handle_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gatts_stop_service failed: %d", err);
return;
}
esp32_ble::global_ble->get_advertising()->remove_service_uuid(this->uuid_);
esp32_ble::global_ble->get_advertising()->start();
this->running_state_ = STOPPING;
if (this->advertise_)
esp32_ble::global_ble->advertising_remove_service_uuid(this->uuid_);
}
bool BLEService::is_created() { return this->init_state_ == CREATED; }
@ -116,9 +140,16 @@ void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t g
this->inst_id_ == param->create.service_id.id.inst_id) {
this->handle_ = param->create.service_handle;
this->init_state_ = CREATED;
if (this->should_start_)
this->start();
}
break;
}
case ESP_GATTS_DELETE_EVT:
if (param->del.service_handle == this->handle_) {
this->init_state_ = DELETED;
}
break;
case ESP_GATTS_START_EVT: {
if (param->start.service_handle == this->handle_) {
this->running_state_ = RUNNING;

View File

@ -22,7 +22,7 @@ using namespace esp32_ble;
class BLEService {
public:
BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id);
BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id, bool advertise);
~BLEService();
BLECharacteristic *get_characteristic(ESPBTUUID uuid);
BLECharacteristic *get_characteristic(uint16_t uuid);
@ -38,6 +38,7 @@ class BLEService {
BLEServer *get_server() { return this->server_; }
void do_create(BLEServer *server);
void do_delete();
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
void start();
@ -48,6 +49,7 @@ class BLEService {
bool is_running() { return this->running_state_ == RUNNING; }
bool is_starting() { return this->running_state_ == STARTING; }
bool is_deleted() { return this->init_state_ == DELETED; }
protected:
std::vector<BLECharacteristic *> characteristics_;
@ -58,8 +60,11 @@ class BLEService {
uint16_t num_handles_;
uint16_t handle_{0xFFFF};
uint8_t inst_id_;
bool advertise_{false};
bool should_start_{false};
bool do_create_characteristics_();
void stop_();
enum InitState : uint8_t {
FAILED = 0x00,
@ -67,6 +72,8 @@ class BLEService {
CREATING,
CREATING_DEPENDENTS,
CREATED,
DELETING,
DELETED,
} init_state_{INIT};
enum RunningState : uint8_t {

View File

@ -212,6 +212,7 @@ async def to_code(config):
parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID])
cg.add(parent.register_gap_event_handler(var))
cg.add(parent.register_gattc_event_handler(var))
cg.add(parent.register_ble_status_event_handler(var))
cg.add(var.set_parent(parent))
params = config[CONF_SCAN_PARAMETERS]

View File

@ -64,17 +64,19 @@ void ESP32BLETracker::setup() {
}
});
#endif
if (this->scan_continuous_) {
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
this->start_scan_(true);
} else {
ESP_LOGW(TAG, "Cannot start scan!");
}
}
}
void ESP32BLETracker::loop() {
if (!this->parent_->is_active()) {
this->ble_was_disabled_ = true;
return;
} else if (this->ble_was_disabled_) {
this->ble_was_disabled_ = false;
// If the BLE stack was disabled, we need to start the scan again.
if (this->scan_continuous_) {
this->start_scan();
}
}
int connecting = 0;
int discovered = 0;
int searching = 0;
@ -182,8 +184,7 @@ void ESP32BLETracker::loop() {
xSemaphoreGive(this->scan_end_lock_);
} else {
ESP_LOGD(TAG, "Stopping scan after failure...");
esp_ble_gap_stop_scanning();
this->cancel_timeout("scan");
this->stop_scan_();
}
if (this->scan_start_failed_) {
ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_);
@ -212,8 +213,7 @@ void ESP32BLETracker::loop() {
client->set_state(ClientState::READY_TO_CONNECT);
} else {
ESP_LOGD(TAG, "Pausing scan to make connection...");
esp_ble_gap_stop_scanning();
this->cancel_timeout("scan");
this->stop_scan_();
}
break;
}
@ -232,11 +232,31 @@ void ESP32BLETracker::start_scan() {
void ESP32BLETracker::stop_scan() {
ESP_LOGD(TAG, "Stopping scan.");
this->scan_continuous_ = false;
esp_ble_gap_stop_scanning();
this->stop_scan_();
}
void ESP32BLETracker::ble_before_disabled_event_handler() {
this->stop_scan_();
xSemaphoreGive(this->scan_end_lock_);
}
void ESP32BLETracker::stop_scan_() {
this->cancel_timeout("scan");
if (this->scanner_idle_) {
return;
}
esp_err_t err = esp_ble_gap_stop_scanning();
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_stop_scanning failed: %d", err);
return;
}
}
void ESP32BLETracker::start_scan_(bool first) {
if (!this->parent_->is_active()) {
ESP_LOGW(TAG, "Cannot start scan while ESP32BLE is disabled.");
return;
}
// The lock must be held when calling this function.
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
ESP_LOGE(TAG, "start_scan called without holding scan_end_lock_");
@ -249,15 +269,23 @@ void ESP32BLETracker::start_scan_(bool first) {
listener->on_scan_end();
}
this->already_discovered_.clear();
this->scanner_idle_ = false;
this->scan_params_.scan_type = this->scan_active_ ? BLE_SCAN_TYPE_ACTIVE : BLE_SCAN_TYPE_PASSIVE;
this->scan_params_.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
this->scan_params_.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL;
this->scan_params_.scan_interval = this->scan_interval_;
this->scan_params_.scan_window = this->scan_window_;
esp_ble_gap_set_scan_params(&this->scan_params_);
esp_ble_gap_start_scanning(this->scan_duration_);
esp_err_t err = esp_ble_gap_set_scan_params(&this->scan_params_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_set_scan_params failed: %d", err);
return;
}
err = esp_ble_gap_start_scanning(this->scan_duration_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_start_scanning failed: %d", err);
return;
}
this->scanner_idle_ = false;
this->set_timeout("scan", this->scan_duration_ * 2000, []() {
ESP_LOGE(TAG, "ESP-IDF BLE scan never terminated, rebooting to restore BLE stack...");

View File

@ -177,7 +177,11 @@ class ESPBTClient : public ESPBTDeviceListener {
ClientState state_;
};
class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEventHandler, public Parented<ESP32BLE> {
class ESP32BLETracker : public Component,
public GAPEventHandler,
public GATTcEventHandler,
public BLEStatusEventHandler,
public Parented<ESP32BLE> {
public:
void set_scan_duration(uint32_t scan_duration) { scan_duration_ = scan_duration; }
void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; }
@ -204,8 +208,10 @@ class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEv
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
void ble_before_disabled_event_handler() override;
protected:
void stop_scan_();
/// Start a single scan by setting up the parameters and doing some esp-idf calls.
void start_scan_(bool first);
/// Called when a scan ends
@ -236,6 +242,7 @@ class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEv
bool scan_continuous_;
bool scan_active_;
bool scanner_idle_;
bool ble_was_disabled_{true};
bool raw_advertisements_{false};
bool parse_advertisements_{false};
SemaphoreHandle_t scan_result_lock_;

View File

@ -194,8 +194,8 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
int64_t frame_time = millis() - last_frame;
last_frame = millis();
ESP_LOGD(TAG, "MJPG: %uB %ums (%.1ffps)", (uint32_t) image->get_data_length(), (uint32_t) frame_time,
1000.0 / (uint32_t) frame_time);
ESP_LOGD(TAG, "MJPG: %" PRIu32 "B %" PRIu32 "ms (%.1ffps)", (uint32_t) image->get_data_length(),
(uint32_t) frame_time, 1000.0 / (uint32_t) frame_time);
}
}
@ -205,7 +205,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::WEB_REQUESTER);
ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);
ESP_LOGI(TAG, "STREAM: closed. Frames: %" PRIu32, frames);
return res;
}

View File

@ -2,6 +2,7 @@
#ifdef USE_ESP32
#include <cinttypes>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>

View File

@ -16,9 +16,6 @@ static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirec
ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; }
void ESP32ImprovComponent::setup() {
this->service_ = global_ble_server->create_service(improv::SERVICE_UUID, true);
this->setup_characteristics();
#ifdef USE_BINARY_SENSOR
if (this->authorizer_ != nullptr) {
this->authorizer_->add_on_state_callback([this](bool state) {
@ -70,6 +67,19 @@ void ESP32ImprovComponent::setup_characteristics() {
}
void ESP32ImprovComponent::loop() {
if (!global_ble_server->is_running()) {
this->state_ = improv::STATE_STOPPED;
this->incoming_data_.clear();
return;
}
if (this->service_ == nullptr) {
// Setup the service
ESP_LOGD(TAG, "Creating Improv service");
global_ble_server->create_service(ESPBTUUID::from_raw(improv::SERVICE_UUID), true);
this->service_ = global_ble_server->get_service(ESPBTUUID::from_raw(improv::SERVICE_UUID));
this->setup_characteristics();
}
if (!this->incoming_data_.empty())
this->process_incoming_data_();
uint32_t now = millis();
@ -80,11 +90,10 @@ void ESP32ImprovComponent::loop() {
if (this->service_->is_created() && this->should_start_ && this->setup_complete_) {
if (this->service_->is_running()) {
esp32_ble::global_ble->get_advertising()->start();
esp32_ble::global_ble->advertising_start();
this->set_state_(improv::STATE_AWAITING_AUTHORIZATION);
this->set_error_(improv::ERROR_NONE);
this->should_start_ = false;
ESP_LOGD(TAG, "Service started!");
} else {
this->service_->start();
@ -138,10 +147,7 @@ void ESP32ImprovComponent::loop() {
#endif
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
this->send_response_(data);
this->set_timeout("end-service", 1000, [this] {
this->service_->stop();
this->set_state_(improv::STATE_STOPPED);
});
this->stop();
}
break;
}
@ -206,8 +212,7 @@ void ESP32ImprovComponent::set_state_(improv::State state) {
service_data[6] = 0x00; // Reserved
service_data[7] = 0x00; // Reserved
esp32_ble::global_ble->get_advertising()->set_service_data(service_data);
esp32_ble::global_ble->get_advertising()->start();
esp32_ble::global_ble->advertising_set_service_data(service_data);
}
void ESP32ImprovComponent::set_error_(improv::Error error) {
@ -237,7 +242,10 @@ void ESP32ImprovComponent::start() {
}
void ESP32ImprovComponent::stop() {
this->should_start_ = false;
this->set_timeout("end-service", 1000, [this] {
if (this->state_ == improv::STATE_STOPPED || this->service_ == nullptr)
return;
this->service_->stop();
this->set_state_(improv::STATE_STOPPED);
});

View File

@ -68,7 +68,7 @@ class ESP32ImprovComponent : public Component, public BLEServiceComponent {
std::vector<uint8_t> incoming_data_;
wifi::WiFiAP connecting_sta_;
std::shared_ptr<BLEService> service_;
BLEService *service_ = nullptr;
BLECharacteristic *status_;
BLECharacteristic *error_;
BLECharacteristic *rpc_;

View File

@ -93,19 +93,19 @@ CONFIG_SCHEMA = cv.All(
cv.Inclusive(
CONF_BIT0_HIGH,
"custom",
): cv.positive_time_period_microseconds,
): cv.positive_time_period_nanoseconds,
cv.Inclusive(
CONF_BIT0_LOW,
"custom",
): cv.positive_time_period_microseconds,
): cv.positive_time_period_nanoseconds,
cv.Inclusive(
CONF_BIT1_HIGH,
"custom",
): cv.positive_time_period_microseconds,
): cv.positive_time_period_nanoseconds,
cv.Inclusive(
CONF_BIT1_LOW,
"custom",
): cv.positive_time_period_microseconds,
): cv.positive_time_period_nanoseconds,
}
),
cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH),

View File

@ -5,6 +5,7 @@
#ifdef USE_ESP32
#include <cinttypes>
#include <lwip/dns.h>
#include "esp_event.h"
@ -275,7 +276,7 @@ void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_b
#if ENABLE_IPV6
void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
void *event_data) {
ESP_LOGV(TAG, "[Ethernet event] ETH Got IP6 (num=%d)", event_id);
ESP_LOGV(TAG, "[Ethernet event] ETH Got IP6 (num=%" PRId32 ")", event_id);
global_eth_component->got_ipv6_ = true;
global_eth_component->ipv6_count_ += 1;
}

View File

@ -14,6 +14,7 @@ from esphome.const import (
CONF_SPEED_LEVEL_STATE_TOPIC,
CONF_SPEED_COMMAND_TOPIC,
CONF_SPEED_STATE_TOPIC,
CONF_OFF_SPEED_CYCLE,
CONF_ON_SPEED_SET,
CONF_ON_TURN_OFF,
CONF_ON_TURN_ON,
@ -217,10 +218,22 @@ async def fan_turn_on_to_code(config, action_id, template_arg, args):
return var
@automation.register_action("fan.cycle_speed", CycleSpeedAction, FAN_ACTION_SCHEMA)
@automation.register_action(
"fan.cycle_speed",
CycleSpeedAction,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(Fan),
cv.Optional(CONF_OFF_SPEED_CYCLE, default=True): cv.boolean,
}
),
)
async def fan_cycle_speed_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = await cg.templatable(config[CONF_OFF_SPEED_CYCLE], args, bool)
cg.add(var.set_no_off_cycle(template_))
return var
@automation.register_condition(

View File

@ -54,18 +54,26 @@ template<typename... Ts> class CycleSpeedAction : public Action<Ts...> {
public:
explicit CycleSpeedAction(Fan *state) : state_(state) {}
TEMPLATABLE_VALUE(bool, no_off_cycle)
void play(Ts... x) override {
// check to see if fan supports speeds and is on
if (this->state_->get_traits().supported_speed_count()) {
if (this->state_->state) {
int speed = this->state_->speed + 1;
int supported_speed_count = this->state_->get_traits().supported_speed_count();
if (speed > supported_speed_count) {
// was running at max speed, so turn off
bool off_speed_cycle = no_off_cycle_.value(x...);
if (speed > supported_speed_count && off_speed_cycle) {
// was running at max speed, off speed cycle enabled, so turn off
speed = 1;
auto call = this->state_->turn_off();
call.set_speed(speed);
call.perform();
} else if (speed > supported_speed_count && !off_speed_cycle) {
// was running at max speed, off speed cycle disabled, so set to lowest speed
auto call = this->state_->turn_on();
call.set_speed(1);
call.perform();
} else {
auto call = this->state_->turn_on();
call.set_speed(speed);

View File

@ -1,5 +1,6 @@
#include "fingerprint_grow.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace fingerprint_grow {
@ -204,7 +205,7 @@ bool FingerprintGrowComponent::check_password_() {
}
bool FingerprintGrowComponent::set_password_() {
ESP_LOGI(TAG, "Setting new password: %d", this->new_password_);
ESP_LOGI(TAG, "Setting new password: %" PRIu32, this->new_password_);
this->data_ = {SET_PASSWORD, (uint8_t) (this->new_password_ >> 24), (uint8_t) (this->new_password_ >> 16),
(uint8_t) (this->new_password_ >> 8), (uint8_t) (this->new_password_ & 0xFF)};
if (this->send_command_() == OK) {

View File

View File

@ -0,0 +1,33 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate_ir
from esphome.const import CONF_ID, CONF_MODEL
CODEOWNERS = ["@orestismers"]
AUTO_LOAD = ["climate_ir"]
gree_ns = cg.esphome_ns.namespace("gree")
GreeClimate = gree_ns.class_("GreeClimate", climate_ir.ClimateIR)
Model = gree_ns.enum("Model")
MODELS = {
"generic": Model.GREE_GENERIC,
"yan": Model.GREE_YAN,
"yaa": Model.GREE_YAA,
"yac": Model.GREE_YAC,
}
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(GreeClimate),
cv.Required(CONF_MODEL): cv.enum(MODELS),
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_model(config[CONF_MODEL]))
await climate_ir.register_climate_ir(var, config)

View File

@ -0,0 +1,157 @@
#include "gree.h"
#include "esphome/components/remote_base/remote_base.h"
namespace esphome {
namespace gree {
static const char *const TAG = "gree.climate";
void GreeClimate::set_model(Model model) { this->model_ = model; }
void GreeClimate::transmit_state() {
uint8_t remote_state[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00};
remote_state[0] = this->fan_speed_() | this->operation_mode_();
remote_state[1] = this->temperature_();
if (this->model_ == GREE_YAN) {
remote_state[2] = 0x60;
remote_state[3] = 0x50;
remote_state[4] = this->vertical_swing_();
}
if (this->model_ == GREE_YAC) {
remote_state[4] |= (this->horizontal_swing_() << 4);
}
if (this->model_ == GREE_YAA || this->model_ == GREE_YAC) {
remote_state[2] = 0x20; // bits 0..3 always 0000, bits 4..7 TURBO,LIGHT,HEALTH,X-FAN
remote_state[3] = 0x50; // bits 4..7 always 0101
remote_state[6] = 0x20; // YAA1FB, FAA1FB1, YB1F2 bits 4..7 always 0010
if (this->vertical_swing_() == GREE_VDIR_SWING) {
remote_state[0] |= (1 << 6); // Enable swing by setting bit 6
} else if (this->vertical_swing_() != GREE_VDIR_AUTO) {
remote_state[5] = this->vertical_swing_();
}
}
// Calculate the checksum
if (this->model_ == GREE_YAN) {
remote_state[7] = ((remote_state[0] << 4) + (remote_state[1] << 4) + 0xC0);
} else {
remote_state[7] =
((((remote_state[0] & 0x0F) + (remote_state[1] & 0x0F) + (remote_state[2] & 0x0F) + (remote_state[3] & 0x0F) +
((remote_state[5] & 0xF0) >> 4) + ((remote_state[6] & 0xF0) >> 4) + ((remote_state[7] & 0xF0) >> 4) + 0x0A) &
0x0F)
<< 4) |
(remote_state[7] & 0x0F);
}
auto transmit = this->transmitter_->transmit();
auto *data = transmit.get_data();
data->set_carrier_frequency(GREE_IR_FREQUENCY);
data->mark(GREE_HEADER_MARK);
data->space(GREE_HEADER_SPACE);
for (int i = 0; i < 4; i++) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(GREE_BIT_MARK);
bool bit = remote_state[i] & mask;
data->space(bit ? GREE_ONE_SPACE : GREE_ZERO_SPACE);
}
}
data->mark(GREE_BIT_MARK);
data->space(GREE_ZERO_SPACE);
data->mark(GREE_BIT_MARK);
data->space(GREE_ONE_SPACE);
data->mark(GREE_BIT_MARK);
data->space(GREE_ZERO_SPACE);
data->mark(GREE_BIT_MARK);
data->space(GREE_MESSAGE_SPACE);
for (int i = 4; i < 8; i++) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(GREE_BIT_MARK);
bool bit = remote_state[i] & mask;
data->space(bit ? GREE_ONE_SPACE : GREE_ZERO_SPACE);
}
}
data->mark(GREE_BIT_MARK);
data->space(0);
transmit.perform();
}
uint8_t GreeClimate::operation_mode_() {
uint8_t operating_mode = GREE_MODE_ON;
switch (this->mode) {
case climate::CLIMATE_MODE_COOL:
operating_mode |= GREE_MODE_COOL;
break;
case climate::CLIMATE_MODE_DRY:
operating_mode |= GREE_MODE_DRY;
break;
case climate::CLIMATE_MODE_HEAT:
operating_mode |= GREE_MODE_HEAT;
break;
case climate::CLIMATE_MODE_HEAT_COOL:
operating_mode |= GREE_MODE_AUTO;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
operating_mode |= GREE_MODE_FAN;
break;
case climate::CLIMATE_MODE_OFF:
default:
operating_mode = GREE_MODE_OFF;
break;
}
return operating_mode;
}
uint8_t GreeClimate::fan_speed_() {
switch (this->fan_mode.value()) {
case climate::CLIMATE_FAN_LOW:
return GREE_FAN_1;
case climate::CLIMATE_FAN_MEDIUM:
return GREE_FAN_2;
case climate::CLIMATE_FAN_HIGH:
return GREE_FAN_3;
case climate::CLIMATE_FAN_AUTO:
default:
return GREE_FAN_AUTO;
}
}
uint8_t GreeClimate::horizontal_swing_() {
switch (this->swing_mode) {
case climate::CLIMATE_SWING_HORIZONTAL:
case climate::CLIMATE_SWING_BOTH:
return GREE_HDIR_SWING;
default:
return GREE_HDIR_MANUAL;
}
}
uint8_t GreeClimate::vertical_swing_() {
switch (this->swing_mode) {
case climate::CLIMATE_SWING_VERTICAL:
case climate::CLIMATE_SWING_BOTH:
return GREE_VDIR_SWING;
default:
return GREE_VDIR_MANUAL;
}
}
uint8_t GreeClimate::temperature_() {
return (uint8_t) roundf(clamp<float>(this->target_temperature, GREE_TEMP_MIN, GREE_TEMP_MAX));
}
} // namespace gree
} // namespace esphome

View File

@ -0,0 +1,97 @@
#pragma once
#include "esphome/components/climate_ir/climate_ir.h"
namespace esphome {
namespace gree {
// Values for GREE IR Controllers
// Temperature
const uint8_t GREE_TEMP_MIN = 16; // Celsius
const uint8_t GREE_TEMP_MAX = 30; // Celsius
// Modes
const uint8_t GREE_MODE_AUTO = 0x00;
const uint8_t GREE_MODE_COOL = 0x01;
const uint8_t GREE_MODE_HEAT = 0x04;
const uint8_t GREE_MODE_DRY = 0x02;
const uint8_t GREE_MODE_FAN = 0x03;
const uint8_t GREE_MODE_OFF = 0x00;
const uint8_t GREE_MODE_ON = 0x08;
// Fan Speed
const uint8_t GREE_FAN_AUTO = 0x00;
const uint8_t GREE_FAN_1 = 0x10;
const uint8_t GREE_FAN_2 = 0x20;
const uint8_t GREE_FAN_3 = 0x30;
const uint8_t GREE_FAN_TURBO = 0x80;
// IR Transmission
const uint32_t GREE_IR_FREQUENCY = 38000;
const uint32_t GREE_HEADER_MARK = 9000;
const uint32_t GREE_HEADER_SPACE = 4000;
const uint32_t GREE_BIT_MARK = 620;
const uint32_t GREE_ONE_SPACE = 1600;
const uint32_t GREE_ZERO_SPACE = 540;
const uint32_t GREE_MESSAGE_SPACE = 19000;
// Timing specific for YAC features (I-Feel mode)
const uint32_t GREE_YAC_HEADER_MARK = 6000;
const uint32_t GREE_YAC_HEADER_SPACE = 3000;
const uint32_t GREE_YAC_BIT_MARK = 650;
// State Frame size
const uint8_t GREE_STATE_FRAME_SIZE = 8;
// Only available on YAN
// Vertical air directions. Note that these cannot be set on all heat pumps
const uint8_t GREE_VDIR_AUTO = 0x00;
const uint8_t GREE_VDIR_MANUAL = 0x00;
const uint8_t GREE_VDIR_SWING = 0x01;
const uint8_t GREE_VDIR_UP = 0x02;
const uint8_t GREE_VDIR_MUP = 0x03;
const uint8_t GREE_VDIR_MIDDLE = 0x04;
const uint8_t GREE_VDIR_MDOWN = 0x05;
const uint8_t GREE_VDIR_DOWN = 0x06;
// Only available on YAC
// Horizontal air directions. Note that these cannot be set on all heat pumps
const uint8_t GREE_HDIR_AUTO = 0x00;
const uint8_t GREE_HDIR_MANUAL = 0x00;
const uint8_t GREE_HDIR_SWING = 0x01;
const uint8_t GREE_HDIR_LEFT = 0x02;
const uint8_t GREE_HDIR_MLEFT = 0x03;
const uint8_t GREE_HDIR_MIDDLE = 0x04;
const uint8_t GREE_HDIR_MRIGHT = 0x05;
const uint8_t GREE_HDIR_RIGHT = 0x06;
// Model codes
enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC };
class GreeClimate : public climate_ir::ClimateIR {
public:
GreeClimate()
: climate_ir::ClimateIR(GREE_TEMP_MIN, GREE_TEMP_MAX, 1.0f, true, true,
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
climate::CLIMATE_FAN_HIGH},
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL,
climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
void set_model(Model model);
protected:
// Transmit via IR the state of this climate controller.
void transmit_state() override;
uint8_t operation_mode_();
uint8_t fan_speed_();
uint8_t horizontal_swing_();
uint8_t vertical_swing_();
uint8_t temperature_();
Model model_{};
};
} // namespace gree
} // namespace esphome

View File

@ -38,7 +38,7 @@ void HLW8012Component::dump_config() {
LOG_PIN(" SEL Pin: ", this->sel_pin_)
LOG_PIN(" CF Pin: ", this->cf_pin_)
LOG_PIN(" CF1 Pin: ", this->cf1_pin_)
ESP_LOGCONFIG(TAG, " Change measurement mode every %u", this->change_mode_every_);
ESP_LOGCONFIG(TAG, " Change measurement mode every %" PRIu32, this->change_mode_every_);
ESP_LOGCONFIG(TAG, " Current resistor: %.1f mΩ", this->current_resistor_ * 1000.0f);
ESP_LOGCONFIG(TAG, " Voltage Divider: %.1f", this->voltage_divider_);
LOG_UPDATE_INTERVAL(this)

View File

@ -5,6 +5,8 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/pulse_counter/pulse_counter_sensor.h"
#include <cinttypes>
namespace esphome {
namespace hlw8012 {

View File

@ -0,0 +1,2 @@
"""Support for Honeywell ABP2"""
CODEOWNERS = ["@jpfaff"]

View File

@ -0,0 +1,108 @@
#include "honeywellabp2.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace honeywellabp2_i2c {
static const uint8_t STATUS_BIT_POWER = 6;
static const uint8_t STATUS_BIT_BUSY = 5;
static const uint8_t STATUS_BIT_ERROR = 2;
static const uint8_t STATUS_MATH_SAT = 0;
static const char *const TAG = "honeywellabp2";
void HONEYWELLABP2Sensor::read_sensor_data() {
if (this->read(raw_data_, 7) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Communication with ABP2 failed!");
this->mark_failed();
return;
}
float press_counts = encode_uint24(raw_data_[1], raw_data_[2], raw_data_[3]); // calculate digital pressure counts
float temp_counts = encode_uint24(raw_data_[4], raw_data_[5], raw_data_[6]); // calculate digital temperature counts
this->last_pressure_ = (((press_counts - this->min_count_) / (this->max_count_ - this->min_count_)) *
(this->max_pressure_ - this->min_pressure_)) +
this->min_pressure_;
this->last_temperature_ = (temp_counts * 200 / 16777215) - 50;
}
void HONEYWELLABP2Sensor::start_measurement() {
if (this->write(i2c_cmd_, 3) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Communication with ABP2 failed!");
this->mark_failed();
return;
}
this->measurement_running_ = true;
}
bool HONEYWELLABP2Sensor::is_measurement_ready() {
if (this->read(raw_data_, 1) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Communication with ABP2 failed!");
this->mark_failed();
return false;
}
if ((raw_data_[0] & (0x1 << STATUS_BIT_BUSY)) > 0) {
return false;
}
this->measurement_running_ = false;
return true;
}
void HONEYWELLABP2Sensor::measurement_timeout() {
ESP_LOGE(TAG, "Timeout!");
this->measurement_running_ = false;
this->mark_failed();
}
float HONEYWELLABP2Sensor::get_pressure() { return this->last_pressure_; }
float HONEYWELLABP2Sensor::get_temperature() { return this->last_temperature_; }
void HONEYWELLABP2Sensor::loop() {
if (this->measurement_running_) {
if (this->is_measurement_ready()) {
this->cancel_timeout("meas_timeout");
this->read_sensor_data();
if (pressure_sensor_ != nullptr) {
this->pressure_sensor_->publish_state(this->get_pressure());
}
if (temperature_sensor_ != nullptr) {
this->temperature_sensor_->publish_state(this->get_temperature());
}
}
}
}
void HONEYWELLABP2Sensor::update() {
ESP_LOGV(TAG, "Update Honeywell ABP2 Sensor");
this->start_measurement();
this->set_timeout("meas_timeout", 50, [this] { this->measurement_timeout(); });
}
void HONEYWELLABP2Sensor::dump_config() {
ESP_LOGCONFIG(TAG, " Min Pressure Range: %0.1f", this->min_pressure_);
ESP_LOGCONFIG(TAG, " Max Pressure Range: %0.1f", this->max_pressure_);
if (this->transfer_function_ == ABP2_TRANS_FUNC_A) {
ESP_LOGCONFIG(TAG, " Transfer function A");
} else {
ESP_LOGCONFIG(TAG, " Transfer function B");
}
LOG_UPDATE_INTERVAL(this);
}
void HONEYWELLABP2Sensor::set_transfer_function(ABP2TRANFERFUNCTION transfer_function) {
this->transfer_function_ = transfer_function;
if (this->transfer_function_ == ABP2_TRANS_FUNC_B) {
this->max_count_ = this->max_count_b_;
this->min_count_ = this->min_count_b_;
} else {
this->max_count_ = this->max_count_a_;
this->min_count_ = this->min_count_a_;
}
}
} // namespace honeywellabp2_i2c
} // namespace esphome

View File

@ -0,0 +1,60 @@
// for Honeywell ABP sensor
// adapting code from https://github.com/vwls/Honeywell_pressure_sensors
#pragma once
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/hal.h"
#include "esphome/core/component.h"
namespace esphome {
namespace honeywellabp2_i2c {
enum ABP2TRANFERFUNCTION { ABP2_TRANS_FUNC_A = 0, ABP2_TRANS_FUNC_B = 1 };
class HONEYWELLABP2Sensor : public PollingComponent, public i2c::I2CDevice {
public:
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { this->pressure_sensor_ = pressure_sensor; };
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; };
void loop() override;
void update() override;
float get_setup_priority() const override { return setup_priority::DATA; };
void dump_config() override;
void read_sensor_data();
void start_measurement();
bool is_measurement_ready();
void measurement_timeout();
float get_pressure();
float get_temperature();
void set_min_pressure(float min_pressure) { this->min_pressure_ = min_pressure; };
void set_max_pressure(float max_pressure) { this->max_pressure_ = max_pressure; };
void set_transfer_function(ABP2TRANFERFUNCTION transfer_function);
protected:
float min_pressure_ = 0.0;
float max_pressure_ = 0.0;
ABP2TRANFERFUNCTION transfer_function_ = ABP2_TRANS_FUNC_A;
sensor::Sensor *pressure_sensor_{nullptr};
sensor::Sensor *temperature_sensor_{nullptr};
const float max_count_a_ = 15099494.4; // (90% of 2^24 counts or 0xE66666)
const float min_count_a_ = 1677721.6; // (10% of 2^24 counts or 0x19999A)
const float max_count_b_ = 11744051.2; // (70% of 2^24 counts or 0xB33333)
const float min_count_b_ = 5033164.8; // (30% of 2^24 counts or 0x4CCCCC)
float max_count_;
float min_count_;
bool measurement_running_ = false;
uint8_t raw_data_[7]; // holds output data
uint8_t i2c_cmd_[3] = {0xAA, 0x00, 0x00}; // command to be sent
float last_pressure_;
float last_temperature_;
};
} // namespace honeywellabp2_i2c
} // namespace esphome

View File

@ -0,0 +1,75 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.components import i2c
from esphome.const import (
CONF_ID,
CONF_PRESSURE,
CONF_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
)
DEPENDENCIES = ["i2c"]
honeywellabp2_ns = cg.esphome_ns.namespace("honeywellabp2_i2c")
CONF_MIN_PRESSURE = "min_pressure"
CONF_MAX_PRESSURE = "max_pressure"
TRANSFER_FUNCTION = "transfer_function"
ABP2TRANFERFUNCTION = honeywellabp2_ns.enum("ABP2TRANFERFUNCTION")
TRANS_FUNC_OPTIONS = {
"A": ABP2TRANFERFUNCTION.ABP2_TRANS_FUNC_A,
"B": ABP2TRANFERFUNCTION.ABP2_TRANS_FUNC_B,
}
HONEYWELLABP2Sensor = honeywellabp2_ns.class_(
"HONEYWELLABP2Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(HONEYWELLABP2Sensor),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement="Pa",
accuracy_decimals=1,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Required(CONF_MIN_PRESSURE): cv.float_,
cv.Required(CONF_MAX_PRESSURE): cv.float_,
cv.Required(TRANSFER_FUNCTION): cv.enum(TRANS_FUNC_OPTIONS),
}
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x28))
)
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 pressure_config := config.get(CONF_PRESSURE):
sens = await sensor.new_sensor(pressure_config)
cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_min_pressure(pressure_config[CONF_MIN_PRESSURE]))
cg.add(var.set_max_pressure(pressure_config[CONF_MAX_PRESSURE]))
cg.add(var.set_transfer_function(pressure_config[TRANSFER_FUNCTION]))
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens))

View File

@ -28,7 +28,7 @@ void HX711Sensor::update() {
uint32_t result;
if (this->read_sensor_(&result)) {
int32_t value = static_cast<int32_t>(result);
ESP_LOGD(TAG, "'%s': Got value %d", this->name_.c_str(), value);
ESP_LOGD(TAG, "'%s': Got value %" PRId32, this->name_.c_str(), value);
this->publish_state(value);
}
}

View File

@ -4,6 +4,8 @@
#include "esphome/core/hal.h"
#include "esphome/components/sensor/sensor.h"
#include <cinttypes>
namespace esphome {
namespace hx711 {

View File

@ -25,6 +25,10 @@ void HydreonRGxxComponent::dump_config() {
LOG_SENSOR(" ", #s, this->sensors_[i - 1]); \
}
HYDREON_RGXX_PROTOCOL_LIST(HYDREON_RGXX_LOG_SENSOR, );
if (this->model_ == RG9) {
ESP_LOGCONFIG(TAG, "disable_led: %s", TRUEFALSE(this->disable_led_));
}
}
void HydreonRGxxComponent::setup() {
@ -187,7 +191,20 @@ void HydreonRGxxComponent::process_line_() {
this->cancel_interval("reboot");
this->no_response_count_ = 0;
ESP_LOGI(TAG, "Boot detected: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
this->write_str("P\nH\nM\n"); // set sensor to polling mode, high res mode, metric mode
if (this->model_ == RG15) {
this->write_str("P\nH\nM\n"); // set sensor to (P)polling mode, (H)high res mode, (M)metric mode
}
if (this->model_ == RG9) {
this->write_str("P\n"); // set sensor to (P)polling mode
if (this->disable_led_) {
this->write_str("D 1\n"); // set sensor (D 1)rain detection LED disabled
} else {
this->write_str("D 0\n"); // set sensor (D 0)rain detection LED enabled
}
}
return;
}
if (this->buffer_starts_with_("SW")) {
@ -227,7 +244,22 @@ void HydreonRGxxComponent::process_line_() {
if (n == std::string::npos) {
continue;
}
float data = strtof(this->buffer_.substr(n + strlen(PROTOCOL_NAMES[i])).c_str(), nullptr);
if (n == this->buffer_.find('t', n)) {
// The device temperature ('t') response contains both °C and °F values:
// "t 72F 22C".
// ESPHome uses only °C, only parse °C value (move past 'F').
n = this->buffer_.find('F', n);
if (n == std::string::npos) {
continue;
}
n += 1; // move past 'F'
} else {
n += strlen(PROTOCOL_NAMES[i]); // move past protocol name
}
// parse value, starting at str position n
float data = strtof(this->buffer_.substr(n).c_str(), nullptr);
this->sensors_[i]->publish_state(data);
ESP_LOGD(TAG, "Received %s: %f", PROTOCOL_NAMES[i], this->sensors_[i]->get_raw_state());
this->sensors_received_ |= (1 << i);

View File

@ -49,6 +49,8 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice {
float get_setup_priority() const override;
void set_disable_led(bool disable_led) { this->disable_led_ = disable_led; }
protected:
void process_line_();
void schedule_reboot_();
@ -72,6 +74,7 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice {
bool lens_bad_ = false;
bool em_sat_ = false;
bool request_temperature_ = false;
bool disable_led_ = false;
// bit field showing which sensors we have received data for
int sensors_received_ = -1;

View File

@ -25,12 +25,17 @@ CONF_EVENT_ACC = "event_acc"
CONF_TOTAL_ACC = "total_acc"
CONF_R_INT = "r_int"
CONF_DISABLE_LED = "disable_led"
RG_MODELS = {
"RG_9": RGModel.RG9,
"RG_15": RGModel.RG15,
# https://rainsensors.com/wp-content/uploads/sites/3/2020/07/rg-15_instructions_sw_1.000.pdf
# https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2020.08.25-rg-9_instructions.pdf
# https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2021.03.11-rg-9_instructions.pdf
# RG-15
# 1.000 - https://rainsensors.com/wp-content/uploads/sites/3/2020/07/rg-15_instructions_sw_1.000.pdf
# RG-9
# 1.000 - https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2020.08.25-rg-9_instructions.pdf
# 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 = {
CONF_ACC: ["RG_15"],
@ -39,6 +44,7 @@ SUPPORTED_SENSORS = {
CONF_R_INT: ["RG_15"],
CONF_MOISTURE: ["RG_9"],
CONF_TEMPERATURE: ["RG_9"],
CONF_DISABLE_LED: ["RG_9"],
}
PROTOCOL_NAMES = {
CONF_MOISTURE: "R",
@ -105,6 +111,7 @@ CONFIG_SCHEMA = cv.All(
icon=ICON_THERMOMETER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_DISABLE_LED): cv.boolean,
}
)
.extend(cv.polling_component_schema("60s"))
@ -132,3 +139,6 @@ async def to_code(config):
cg.add(var.set_sensor(sens, i))
cg.add(var.set_request_temperature(CONF_TEMPERATURE in config))
if CONF_DISABLE_LED in config:
cg.add(var.set_disable_led(config[CONF_DISABLE_LED]))

View File

@ -17,7 +17,7 @@ void HYT271Component::dump_config() {
LOG_SENSOR(" ", "Humidity", this->humidity_);
}
void HYT271Component::update() {
uint8_t raw_data[4];
uint8_t raw_data[4] = {0, 0, 0, 0};
if (this->write(&raw_data[0], 0) != i2c::ERROR_OK) {
this->status_set_warning();

View File

View File

@ -0,0 +1,99 @@
#include "iaqcore.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace iaqcore {
static const char *const TAG = "iaqcore";
enum IAQCoreErrorCode : uint8_t { ERROR_OK = 0, ERROR_RUNIN = 0x10, ERROR_BUSY = 0x01, ERROR_ERROR = 0x80 };
struct SensorData {
uint16_t co2;
IAQCoreErrorCode status;
int32_t resistance;
uint16_t tvoc;
SensorData(const uint8_t *buffer) {
this->co2 = encode_uint16(buffer[0], buffer[1]);
this->status = static_cast<IAQCoreErrorCode>(buffer[2]);
this->resistance = encode_uint32(buffer[3], buffer[4], buffer[5], buffer[6]);
this->tvoc = encode_uint16(buffer[7], buffer[8]);
}
};
void IAQCore::setup() {
if (this->write(nullptr, 0) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication failed!");
this->mark_failed();
return;
}
}
void IAQCore::update() {
uint8_t buffer[sizeof(SensorData)];
if (this->read_register(0xB5, buffer, sizeof(buffer), false) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Read failed");
this->status_set_warning();
this->publish_nans_();
return;
}
SensorData data(buffer);
switch (data.status) {
case ERROR_OK:
ESP_LOGD(TAG, "OK");
break;
case ERROR_RUNIN:
ESP_LOGI(TAG, "Warming up");
break;
case ERROR_BUSY:
ESP_LOGI(TAG, "Busy");
break;
case ERROR_ERROR:
ESP_LOGE(TAG, "Error");
break;
}
if (data.status != ERROR_OK) {
this->status_set_warning();
this->publish_nans_();
return;
}
if (this->co2_ != nullptr) {
this->co2_->publish_state(data.co2);
}
if (this->tvoc_ != nullptr) {
this->tvoc_->publish_state(data.tvoc);
}
this->status_clear_warning();
}
void IAQCore::publish_nans_() {
if (this->co2_ != nullptr) {
this->co2_->publish_state(NAN);
}
if (this->tvoc_ != nullptr) {
this->tvoc_->publish_state(NAN);
}
}
void IAQCore::dump_config() {
ESP_LOGCONFIG(TAG, "AMS iAQ Core:");
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with AMS iAQ Core failed!");
}
LOG_SENSOR(" ", "CO2", this->co2_);
LOG_SENSOR(" ", "TVOC", this->tvoc_);
}
} // namespace iaqcore
} // namespace esphome

View File

@ -0,0 +1,29 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace iaqcore {
class IAQCore : public PollingComponent, public i2c::I2CDevice {
public:
void set_co2(sensor::Sensor *co2) { co2_ = co2; }
void set_tvoc(sensor::Sensor *tvoc) { tvoc_ = tvoc; }
void setup() override;
void update() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
protected:
sensor::Sensor *co2_{nullptr};
sensor::Sensor *tvoc_{nullptr};
void publish_nans_();
};
} // namespace iaqcore
} // namespace esphome

View File

@ -0,0 +1,57 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_CO2,
CONF_ID,
CONF_TVOC,
DEVICE_CLASS_CARBON_DIOXIDE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
STATE_CLASS_MEASUREMENT,
UNIT_PARTS_PER_MILLION,
UNIT_PARTS_PER_BILLION,
)
DEPENDENCIES = ["i2c"]
CODEOWNERS = ["@yozik04"]
iaqcore_ns = cg.esphome_ns.namespace("iaqcore")
iAQCore = iaqcore_ns.class_("IAQCore", cg.PollingComponent, i2c.I2CDevice)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(iAQCore),
cv.Optional(CONF_CO2): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
accuracy_decimals=0,
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TVOC): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_BILLION,
accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x5A))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
if co2_config := config.get(CONF_CO2):
sens = await sensor.new_sensor(co2_config)
cg.add(var.set_co2(sens))
if tvoc_config := config.get(CONF_TVOC):
sens = await sensor.new_sensor(tvoc_config)
cg.add(var.set_tvoc(sens))
await i2c.register_i2c_device(var, config)

View File

@ -180,7 +180,7 @@ void ILI9XXXDisplay::display_() {
ESP_LOGV(TAG,
"Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, "
"heigth:%d, start_pos:%d)",
"heigth:%d, start_pos:%" PRId32 ")",
this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, start_pos);
this->start_data_();

View File

@ -1,6 +1,8 @@
#pragma once
#include "esphome/core/helpers.h"
#include <cinttypes>
namespace esphome {
namespace ili9xxx {

View File

@ -122,7 +122,7 @@ void INA219Component::setup() {
this->calibration_lsb_ = lsb;
auto calibration = uint32_t(0.04096f / (0.000001 * lsb * this->shunt_resistance_ohm_));
ESP_LOGV(TAG, " Using LSB=%u calibration=%u", lsb, calibration);
ESP_LOGV(TAG, " Using LSB=%" PRIu32 " calibration=%" PRIu32, lsb, calibration);
if (!this->write_byte_16(INA219_REGISTER_CALIBRATION, calibration)) {
this->mark_failed();
return;

View File

@ -4,6 +4,8 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include <cinttypes>
namespace esphome {
namespace ina219 {

View File

@ -0,0 +1,39 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart
from esphome.const import CONF_ID
CODEOWNERS = ["@descipher"]
DEPENDENCIES = ["uart"]
MULTI_CONF = True
ld2420_ns = cg.esphome_ns.namespace("ld2420")
LD2420Component = ld2420_ns.class_("LD2420Component", cg.Component, uart.UARTDevice)
CONF_LD2420_ID = "ld2420_id"
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(LD2420Component),
}
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"ld2420_uart",
require_tx=True,
require_rx=True,
parity="NONE",
stop_bits=1,
)
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)

View File

@ -0,0 +1,33 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import CONF_ID, DEVICE_CLASS_OCCUPANCY
from .. import ld2420_ns, LD2420Component, CONF_LD2420_ID
LD2420BinarySensor = ld2420_ns.class_(
"LD2420BinarySensor", binary_sensor.BinarySensor, cg.Component
)
CONF_HAS_TARGET = "has_target"
CONFIG_SCHEMA = cv.All(
cv.COMPONENT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(LD2420BinarySensor),
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_OCCUPANCY
),
}
),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
if CONF_HAS_TARGET in config:
sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_TARGET])
cg.add(var.set_presence_sensor(sens))
ld2420 = await cg.get_variable(config[CONF_LD2420_ID])
cg.add(ld2420.register_listener(var))

View File

@ -0,0 +1,16 @@
#include "ld2420_binary_sensor.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ld2420 {
static const char *const TAG = "LD2420.binary_sensor";
void LD2420BinarySensor::dump_config() {
ESP_LOGCONFIG(TAG, "LD2420 BinarySensor:");
LOG_BINARY_SENSOR(" ", "Presence", this->presence_bsensor_);
}
} // namespace ld2420
} // namespace esphome

View File

@ -0,0 +1,25 @@
#pragma once
#include "../ld2420.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome {
namespace ld2420 {
class LD2420BinarySensor : public LD2420Listener, public Component, binary_sensor::BinarySensor {
public:
void dump_config() override;
void set_presence_sensor(binary_sensor::BinarySensor *bsensor) { this->presence_bsensor_ = bsensor; };
void on_presence(bool presence) override {
if (this->presence_bsensor_ != nullptr) {
if (this->presence_bsensor_->state != presence)
this->presence_bsensor_->publish_state(presence);
}
}
protected:
binary_sensor::BinarySensor *presence_bsensor_{nullptr};
};
} // namespace ld2420
} // namespace esphome

View File

@ -0,0 +1,69 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import (
DEVICE_CLASS_RESTART,
ENTITY_CATEGORY_DIAGNOSTIC,
ENTITY_CATEGORY_CONFIG,
ICON_RESTART,
ICON_RESTART_ALERT,
ICON_DATABASE,
)
from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns
LD2420ApplyConfigButton = ld2420_ns.class_("LD2420ApplyConfigButton", button.Button)
LD2420RevertConfigButton = ld2420_ns.class_("LD2420RevertConfigButton", button.Button)
LD2420RestartModuleButton = ld2420_ns.class_("LD2420RestartModuleButton", button.Button)
LD2420FactoryResetButton = ld2420_ns.class_("LD2420FactoryResetButton", button.Button)
CONF_APPLY_CONFIG = "apply_config"
CONF_REVERT_CONFIG = "revert_config"
CONF_RESTART_MODULE = "restart_module"
CONF_FACTORY_RESET = "factory_reset"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
cv.Required(CONF_APPLY_CONFIG): button.button_schema(
LD2420ApplyConfigButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART_ALERT,
),
cv.Optional(CONF_REVERT_CONFIG): button.button_schema(
LD2420RevertConfigButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART,
),
cv.Optional(CONF_RESTART_MODULE): button.button_schema(
LD2420RestartModuleButton,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_DATABASE,
),
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
LD2420FactoryResetButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_DATABASE,
),
}
async def to_code(config):
ld2420_component = await cg.get_variable(config[CONF_LD2420_ID])
if apply_config := config.get(CONF_APPLY_CONFIG):
b = await button.new_button(apply_config)
await cg.register_parented(b, config[CONF_LD2420_ID])
cg.add(ld2420_component.set_apply_config_button(b))
if revert_config := config.get(CONF_REVERT_CONFIG):
b = await button.new_button(revert_config)
await cg.register_parented(b, config[CONF_LD2420_ID])
cg.add(ld2420_component.set_revert_config_button(b))
if restart_config := config.get(CONF_RESTART_MODULE):
b = await button.new_button(restart_config)
await cg.register_parented(b, config[CONF_LD2420_ID])
cg.add(ld2420_component.set_restart_module_button(b))
if factory_reset := config.get(CONF_FACTORY_RESET):
b = await button.new_button(factory_reset)
await cg.register_parented(b, config[CONF_LD2420_ID])
cg.add(ld2420_component.set_factory_reset_button(b))

View File

@ -0,0 +1,16 @@
#include "reconfig_buttons.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
static const char *const TAG = "LD2420.button";
namespace esphome {
namespace ld2420 {
void LD2420ApplyConfigButton::press_action() { this->parent_->apply_config_action(); }
void LD2420RevertConfigButton::press_action() { this->parent_->revert_config_action(); }
void LD2420RestartModuleButton::press_action() { this->parent_->restart_module_action(); }
void LD2420FactoryResetButton::press_action() { this->parent_->factory_reset_action(); }
} // namespace ld2420
} // namespace esphome

View File

@ -0,0 +1,42 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2420.h"
namespace esphome {
namespace ld2420 {
class LD2420ApplyConfigButton : public button::Button, public Parented<LD2420Component> {
public:
LD2420ApplyConfigButton() = default;
protected:
void press_action() override;
};
class LD2420RevertConfigButton : public button::Button, public Parented<LD2420Component> {
public:
LD2420RevertConfigButton() = default;
protected:
void press_action() override;
};
class LD2420RestartModuleButton : public button::Button, public Parented<LD2420Component> {
public:
LD2420RestartModuleButton() = default;
protected:
void press_action() override;
};
class LD2420FactoryResetButton : public button::Button, public Parented<LD2420Component> {
public:
LD2420FactoryResetButton() = default;
protected:
void press_action() override;
};
} // namespace ld2420
} // namespace esphome

View File

@ -0,0 +1,781 @@
#include "ld2420.h"
#include "esphome/core/helpers.h"
/*
Configure commands - little endian
No command can exceed 64 bytes, otherwise they would need be to be split up into multiple sends.
All send command frames will have:
Header = FD FC FB FA, Bytes 0 - 3, uint32_t 0xFAFBFCFD
Length, bytes 4 - 5, uint16_t 0x0002, must be at least 2 for the command byte if no addon data.
Command bytes 6 - 7, uint16_t
Footer = 04 03 02 01 - uint32_t 0x01020304, Always last 4 Bytes.
Receive
Error bytes 8-9 uint16_t, 0 = success, all other positive values = error
Enable config mode:
Send:
UART Tx: FD FC FB FA 04 00 FF 00 02 00 04 03 02 01
Command = FF 00 - uint16_t 0x00FF
Protocol version = 02 00, can be 1 or 2 - uint16_t 0x0002
Reply:
UART Rx: FD FC FB FA 06 00 FF 01 00 00 02 00 04 03 02 01
Disable config mode:
Send:
UART Tx: FD FC FB FA 02 00 FE 00 04 03 02 01
Command = FE 00 - uint16_t 0x00FE
Receive:
UART Rx: FD FC FB FA 04 00 FE 01 00 00 04 03 02 01
Configure system parameters:
UART Tx: FD FC FB FA 08 00 12 00 00 00 64 00 00 00 04 03 02 01 Set system parms
Command = 12 00 - uint16_t 0x0012, Param
There are three documented parameters for modes:
00 64 = Basic status mode
This mode outputs text as presence "ON" or "OFF" and "Range XXXX"
where XXXX is a decimal value for distance in cm
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
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.
Header HH, Doppler DD, Range RR, Footer FF
HH HH HH HH DD DD DD DD .. 20x .. RR RR RR RR .. 16x .. FF FF FF FF
AA BF 10 14 00 00 00 00 .. .. .. .. 00 00 00 00 .. .. .. .. FD FC FB FA
Configure gate sensitivity parameters:
UART Tx: FD FC FB FA 0E 00 07 00 10 00 60 EA 00 00 20 00 60 EA 00 00 04 03 02 01
Command = 12 00 - uint16_t 0x0007
Gate 0 high thresh = 10 00 uint16_t 0x0010, Threshold value = 60 EA 00 00 uint32_t 0x0000EA60
Gate 0 low thresh = 20 00 uint16_t 0x0020, Threshold value = 60 EA 00 00 uint32_t 0x0000EA60
*/
namespace esphome {
namespace ld2420 {
static const char *const TAG = "ld2420";
float LD2420Component::get_setup_priority() const { return setup_priority::BUS; }
void LD2420Component::dump_config() {
ESP_LOGCONFIG(TAG, "LD2420:");
ESP_LOGCONFIG(TAG, " Firmware Version : %7s", this->ld2420_firmware_ver_);
ESP_LOGCONFIG(TAG, "LD2420 Number:");
#ifdef USE_NUMBER
LOG_NUMBER(TAG, " Gate Timeout:", this->gate_timeout_number_);
LOG_NUMBER(TAG, " Gate Max Distance:", this->max_gate_distance_number_);
LOG_NUMBER(TAG, " Gate Min Distance:", this->min_gate_distance_number_);
LOG_NUMBER(TAG, " Gate Select:", this->gate_select_number_);
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
LOG_NUMBER(TAG, " Gate Move Threshold:", this->gate_move_threshold_numbers_[gate]);
LOG_NUMBER(TAG, " Gate Still Threshold::", this->gate_still_threshold_numbers_[gate]);
}
#endif
#ifdef USE_BUTTON
LOG_BUTTON(TAG, " Apply Config:", this->apply_config_button_);
LOG_BUTTON(TAG, " Revert Edits:", this->revert_config_button_);
LOG_BUTTON(TAG, " Factory Reset:", this->factory_reset_button_);
LOG_BUTTON(TAG, " Restart Module:", this->restart_module_button_);
#endif
ESP_LOGCONFIG(TAG, "LD2420 Select:");
LOG_SELECT(TAG, " Operating Mode", this->operating_selector_);
if (this->get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
ESP_LOGW(TAG, "LD2420 Firmware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_);
}
}
uint8_t LD2420Component::calc_checksum(void *data, size_t size) {
uint8_t checksum = 0;
uint8_t *data_bytes = (uint8_t *) data;
for (size_t i = 0; i < size; i++) {
checksum ^= data_bytes[i]; // XOR operation
}
return checksum;
}
int LD2420Component::get_firmware_int_(const char *version_string) {
std::string version_str = version_string;
if (version_str[0] == 'v') {
version_str = version_str.substr(1);
}
version_str.erase(remove(version_str.begin(), version_str.end(), '.'), version_str.end());
int version_integer = stoi(version_str);
return version_integer;
}
void LD2420Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up LD2420...");
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
this->mark_failed();
return;
}
this->get_min_max_distances_timeout_();
#ifdef USE_NUMBER
this->init_gate_config_numbers();
#endif
this->get_firmware_version_();
const char *pfw = this->ld2420_firmware_ver_;
std::string fw_str(pfw);
for (auto &listener : listeners_) {
listener->on_fw_version(fw_str);
}
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
delay_microseconds_safe(125);
this->get_gate_threshold_(gate);
}
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
if (get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
this->set_operating_mode(OP_SIMPLE_MODE_STRING);
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
this->set_mode_(CMD_SYSTEM_MODE_SIMPLE);
ESP_LOGW(TAG, "LD2420 Frimware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_);
} else {
this->set_mode_(CMD_SYSTEM_MODE_ENERGY);
this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING);
}
#ifdef USE_NUMBER
this->init_gate_config_numbers();
#endif
this->set_system_mode(this->system_mode_);
this->set_config_mode(false);
ESP_LOGCONFIG(TAG, "LD2420 setup complete.");
}
void LD2420Component::apply_config_action() {
const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config));
if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) {
ESP_LOGCONFIG(TAG, "No configuration change detected");
return;
}
ESP_LOGCONFIG(TAG, "Reconfiguring LD2420...");
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
this->mark_failed();
return;
}
this->set_min_max_distances_timeout(this->new_config.max_gate, this->new_config.min_gate, this->new_config.timeout);
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
delay_microseconds_safe(125);
this->set_gate_threshold(gate);
}
memcpy(&current_config, &new_config, sizeof(new_config));
#ifdef USE_NUMBER
this->init_gate_config_numbers();
#endif
this->set_system_mode(this->system_mode_);
this->set_config_mode(false); // Disable config mode to save new values in LD2420 nvm
this->set_operating_mode(OP_NORMAL_MODE_STRING);
ESP_LOGCONFIG(TAG, "LD2420 reconfig complete.");
}
void LD2420Component::factory_reset_action() {
ESP_LOGCONFIG(TAG, "Setiing factory defaults...");
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
this->mark_failed();
return;
}
this->set_min_max_distances_timeout(FACTORY_MAX_GATE, FACTORY_MIN_GATE, FACTORY_TIMEOUT);
#ifdef USE_NUMBER
this->gate_timeout_number_->state = FACTORY_TIMEOUT;
this->min_gate_distance_number_->state = FACTORY_MIN_GATE;
this->max_gate_distance_number_->state = FACTORY_MAX_GATE;
#endif
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
this->new_config.move_thresh[gate] = FACTORY_MOVE_THRESH[gate];
this->new_config.still_thresh[gate] = FACTORY_STILL_THRESH[gate];
delay_microseconds_safe(125);
this->set_gate_threshold(gate);
}
memcpy(&this->current_config, &this->new_config, sizeof(this->new_config));
this->set_system_mode(this->system_mode_);
this->set_config_mode(false);
#ifdef USE_NUMBER
this->init_gate_config_numbers();
this->refresh_gate_config_numbers();
#endif
ESP_LOGCONFIG(TAG, "LD2420 factory reset complete.");
}
void LD2420Component::restart_module_action() {
ESP_LOGCONFIG(TAG, "Restarting LD2420 module...");
this->send_module_restart();
delay_microseconds_safe(45000);
this->set_config_mode(true);
this->set_system_mode(system_mode_);
this->set_config_mode(false);
ESP_LOGCONFIG(TAG, "LD2420 Restarted.");
}
void LD2420Component::revert_config_action() {
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
#ifdef USE_NUMBER
this->init_gate_config_numbers();
#endif
ESP_LOGCONFIG(TAG, "Reverted config number edits.");
}
void LD2420Component::loop() {
// If there is a active send command do not process it here, the send command call will handle it.
if (!get_cmd_active_()) {
if (!available())
return;
static uint8_t buffer[2048];
static uint8_t rx_data;
while (available()) {
rx_data = read();
this->readline_(rx_data, buffer, sizeof(buffer));
}
}
}
void LD2420Component::update_radar_data(uint16_t const *gate_energy, uint8_t sample_number) {
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) {
this->radar_data[gate][sample_number] = gate_energy[gate];
}
this->total_sample_number_counter++;
}
void LD2420Component::auto_calibrate_sensitivity() {
// Calculate average and peak values for each gate
const float move_factor = gate_move_sensitivity_factor + 1;
const float still_factor = (gate_still_sensitivity_factor / 2) + 1;
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) {
uint32_t sum = 0;
uint16_t peak = 0;
for (uint8_t sample_number = 0; sample_number < CALIBRATE_SAMPLES; ++sample_number) {
// Calculate average
sum += this->radar_data[gate][sample_number];
// Calculate max value
if (this->radar_data[gate][sample_number] > peak) {
peak = this->radar_data[gate][sample_number];
}
}
// Store average and peak values
this->gate_avg[gate] = sum / CALIBRATE_SAMPLES;
if (this->gate_peak[gate] < peak)
this->gate_peak[gate] = peak;
uint32_t calculated_value =
(static_cast<uint32_t>(this->gate_peak[gate]) + (move_factor * static_cast<uint32_t>(this->gate_peak[gate])));
this->new_config.move_thresh[gate] = static_cast<uint16_t>(calculated_value <= 65535 ? calculated_value : 65535);
calculated_value =
(static_cast<uint32_t>(this->gate_peak[gate]) + (still_factor * static_cast<uint32_t>(this->gate_peak[gate])));
this->new_config.still_thresh[gate] = static_cast<uint16_t>(calculated_value <= 65535 ? calculated_value : 65535);
}
}
void LD2420Component::report_gate_data() {
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) {
// Output results
ESP_LOGI(TAG, "Gate: %2d Avg: %5d Peak: %5d", gate, this->gate_avg[gate], this->gate_peak[gate]);
}
ESP_LOGI(TAG, "Total samples: %d", this->total_sample_number_counter);
}
void LD2420Component::set_operating_mode(const std::string &state) {
// If unsupported firmware ignore mode select
if (get_firmware_int_(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) {
this->current_operating_mode = OP_MODE_TO_UINT.at(state);
// Entering Auto Calibrate we need to clear the privoiuos data collection
this->operating_selector_->publish_state(state);
if (current_operating_mode == OP_CALIBRATE_MODE) {
this->set_calibration_(true);
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
this->gate_avg[gate] = 0;
this->gate_peak[gate] = 0;
for (uint8_t i = 0; i < CALIBRATE_SAMPLES; i++) {
this->radar_data[gate][i] = 0;
}
this->total_sample_number_counter = 0;
}
} else {
// Set the current data back so we don't have new data that can be applied in error.
if (this->get_calibration_())
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
this->set_calibration_(false);
}
} else {
this->current_operating_mode = OP_SIMPLE_MODE;
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
}
}
void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) {
static int pos = 0;
if (rx_data >= 0) {
if (pos < len - 1) {
buffer[pos++] = rx_data;
buffer[pos] = 0;
} else {
pos = 0;
}
if (pos >= 4) {
if (memcmp(&buffer[pos - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) {
this->set_cmd_active_(false); // Set command state to inactive after responce.
this->handle_ack_data_(buffer, pos);
pos = 0;
} else if ((buffer[pos - 2] == 0x0D && buffer[pos - 1] == 0x0A) && (get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) {
this->handle_simple_mode_(buffer, pos);
pos = 0;
} else if ((memcmp(&buffer[pos - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) &&
(get_mode_() == CMD_SYSTEM_MODE_ENERGY)) {
this->handle_energy_mode_(buffer, pos);
pos = 0;
}
}
}
}
void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
uint8_t index = 6; // Start at presence byte position
uint16_t range;
const uint8_t elements = sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]);
this->set_presence_(buffer[index]);
index++;
memcpy(&range, &buffer[index], sizeof(range));
index += sizeof(range);
this->set_distance_(range);
for (uint8_t i = 0; i < elements; i++) { // NOLINT
memcpy(&this->gate_energy_[i], &buffer[index], sizeof(this->gate_energy_[0]));
index += sizeof(this->gate_energy_[0]);
}
if (this->current_operating_mode == OP_CALIBRATE_MODE) {
this->update_radar_data(gate_energy_, sample_number_counter);
this->sample_number_counter > CALIBRATE_SAMPLES ? this->sample_number_counter = 0 : this->sample_number_counter++;
}
// Resonable refresh rate for home assistant database size health
const int32_t current_millis = millis();
if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS)
return;
this->last_periodic_millis = current_millis;
for (auto &listener : this->listeners_) {
listener->on_distance(get_distance_());
listener->on_presence(get_presence_());
listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]));
}
if (this->current_operating_mode == OP_CALIBRATE_MODE) {
this->auto_calibrate_sensitivity();
if (current_millis - this->report_periodic_millis > REFRESH_RATE_MS * CALIBRATE_REPORT_INTERVAL) {
this->report_periodic_millis = current_millis;
this->report_gate_data();
}
}
}
void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
const uint8_t bufsize = 16;
uint8_t index{0};
uint8_t pos{0};
char *endptr{nullptr};
char outbuf[bufsize]{0};
while (true) {
if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') {
set_presence_(false);
} else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') {
set_presence_(true);
}
if (inbuf[pos] >= '0' && inbuf[pos] <= '9') {
if (index < bufsize - 1) {
outbuf[index++] = inbuf[pos];
pos++;
}
} else {
if (pos < len - 1) {
pos++;
} else {
break;
}
}
}
outbuf[index] = '\0';
if (index > 1)
set_distance_(strtol(outbuf, &endptr, 10));
if (get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
// Resonable refresh rate for home assistant database size health
const int32_t current_millis = millis();
if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS)
return;
this->last_normal_periodic_millis = current_millis;
for (auto &listener : this->listeners_)
listener->on_distance(get_distance_());
for (auto &listener : this->listeners_)
listener->on_presence(get_presence_());
}
}
void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
this->cmd_reply_.command = buffer[CMD_FRAME_COMMAND];
this->cmd_reply_.length = buffer[CMD_FRAME_DATA_LENGTH];
uint8_t reg_element = 0;
uint8_t data_element = 0;
uint16_t data_pos = 0;
if (this->cmd_reply_.length > CMD_MAX_BYTES) {
ESP_LOGW(TAG, "LD2420 reply - received command reply frame is corrupt, length exceeds %d bytes.", CMD_MAX_BYTES);
return;
} else if (this->cmd_reply_.length < 2) {
ESP_LOGW(TAG, "LD2420 reply - received command frame is corrupt, length is less than 2 bytes.");
return;
}
memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error));
const char *result = this->cmd_reply_.error ? "failure" : "success";
if (this->cmd_reply_.error > 0) {
return;
};
this->cmd_reply_.ack = true;
switch ((uint16_t) this->cmd_reply_.command) {
case (CMD_ENABLE_CONF):
ESP_LOGD(TAG, "LD2420 reply - set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result);
break;
case (CMD_DISABLE_CONF):
ESP_LOGD(TAG, "LD2420 reply - set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result);
break;
case (CMD_READ_REGISTER):
ESP_LOGD(TAG, "LD2420 reply - read register: CMD = %2X %s", CMD_READ_REGISTER, result);
// TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file
data_pos = 0x0A;
for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE * // NOLINT
((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_REG_DATA_REPLY_SIZE));
index += CMD_REG_DATA_REPLY_SIZE) {
memcpy(&this->cmd_reply_.data[reg_element], &buffer[data_pos + index], sizeof(CMD_REG_DATA_REPLY_SIZE));
byteswap(this->cmd_reply_.data[reg_element]);
reg_element++;
}
break;
case (CMD_WRITE_REGISTER):
ESP_LOGD(TAG, "LD2420 reply - write register: CMD = %2X %s", CMD_WRITE_REGISTER, result);
break;
case (CMD_WRITE_ABD_PARAM):
ESP_LOGD(TAG, "LD2420 reply - write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result);
break;
case (CMD_READ_ABD_PARAM):
ESP_LOGD(TAG, "LD2420 reply - read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result);
data_pos = CMD_ABD_DATA_REPLY_START;
for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE * // NOLINT
((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE));
index += CMD_ABD_DATA_REPLY_SIZE) {
memcpy(&this->cmd_reply_.data[data_element], &buffer[data_pos + index],
sizeof(this->cmd_reply_.data[data_element]));
byteswap(this->cmd_reply_.data[data_element]);
data_element++;
}
break;
case (CMD_WRITE_SYS_PARAM):
ESP_LOGD(TAG, "LD2420 reply - set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result);
break;
case (CMD_READ_VERSION):
memcpy(this->ld2420_firmware_ver_, &buffer[12], buffer[10]);
ESP_LOGD(TAG, "LD2420 reply - module firmware version: %7s %s", this->ld2420_firmware_ver_, result);
break;
default:
break;
}
}
int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
uint8_t error = 0;
uint8_t ack_buffer[64];
uint8_t cmd_buffer[64];
uint16_t loop_count;
this->cmd_reply_.ack = false;
if (frame.command != CMD_RESTART)
this->set_cmd_active_(true); // Restart does not reply, thus no ack state required.
uint8_t retry = 3;
while (retry) {
// TODO setup a dynamic method e.g. millis time count etc. to tune for non ESP32 240Mhz devices
// this is ok for now since the module firmware is changing like the weather atm
frame.length = 0;
loop_count = 1250;
uint16_t frame_data_bytes = frame.data_length + 2; // Always add two bytes for the cmd size
memcpy(&cmd_buffer[frame.length], &frame.header, sizeof(frame.header));
frame.length += sizeof(frame.header);
memcpy(&cmd_buffer[frame.length], &frame_data_bytes, sizeof(frame.data_length));
frame.length += sizeof(frame.data_length);
memcpy(&cmd_buffer[frame.length], &frame.command, sizeof(frame.command));
frame.length += sizeof(frame.command);
for (uint16_t index = 0; index < frame.data_length; index++) {
memcpy(&cmd_buffer[frame.length], &frame.data[index], sizeof(frame.data[index]));
frame.length += sizeof(frame.data[index]);
}
memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer));
frame.length += sizeof(frame.footer);
for (uint16_t index = 0; index < frame.length; index++) {
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
}
while (!this->cmd_reply_.ack) {
while (available()) {
this->readline_(read(), ack_buffer, sizeof(ack_buffer));
}
delay_microseconds_safe(250);
if (loop_count <= 0) {
error = LD2420_ERROR_TIMEOUT;
retry--;
break;
}
loop_count--;
}
if (this->cmd_reply_.ack)
retry = 0;
if (this->cmd_reply_.error > 0)
handle_cmd_error(error);
}
return error;
}
uint8_t LD2420Component::set_config_mode(bool enable) {
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
if (enable) {
memcpy(&cmd_frame.data[0], &CMD_PROTOCOL_VER, sizeof(CMD_PROTOCOL_VER));
cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER);
}
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command);
return this->send_cmd_from_array(cmd_frame);
}
// Sends a restart and set system running mode to normal
void LD2420Component::send_module_restart() { this->ld2420_restart(); }
void LD2420Component::ld2420_restart() {
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_RESTART;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending restart command: %2X", cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
void LD2420Component::get_reg_value_(uint16_t reg) {
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_READ_REGISTER;
cmd_frame.data[1] = reg;
cmd_frame.data_length += 2;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
void LD2420Component::set_reg_value(uint16_t reg, uint16_t value) {
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_WRITE_REGISTER;
memcpy(&cmd_frame.data[cmd_frame.data_length], &reg, sizeof(CMD_REG_DATA_REPLY_SIZE));
cmd_frame.data_length += 2;
memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE));
cmd_frame.data_length += 2;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value);
this->send_cmd_from_array(cmd_frame);
}
void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGI(TAG, "Command failed: %s", ERR_MESSAGE[error]); }
int LD2420Component::get_gate_threshold_(uint8_t gate) {
uint8_t error;
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_READ_ABD_PARAM;
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_MOVE_THRESH[gate], sizeof(CMD_GATE_MOVE_THRESH[gate]));
cmd_frame.data_length += 2;
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate]));
cmd_frame.data_length += 2;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending read gate %d high/low theshold command: %2X", gate, cmd_frame.command);
error = this->send_cmd_from_array(cmd_frame);
if (error == 0) {
this->current_config.move_thresh[gate] = cmd_reply_.data[0];
this->current_config.still_thresh[gate] = cmd_reply_.data[1];
}
return error;
}
int LD2420Component::get_min_max_distances_timeout_() {
uint8_t error;
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_READ_ABD_PARAM;
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG,
sizeof(CMD_MIN_GATE_REG)); // Register: global min detect gate number
cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG);
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG,
sizeof(CMD_MAX_GATE_REG)); // Register: global max detect gate number
cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG);
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG,
sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command);
error = this->send_cmd_from_array(cmd_frame);
if (error == 0) {
this->current_config.min_gate = (uint16_t) cmd_reply_.data[0];
this->current_config.max_gate = (uint16_t) cmd_reply_.data[1];
this->current_config.timeout = (uint16_t) cmd_reply_.data[2];
}
return error;
}
void LD2420Component::set_system_mode(uint16_t mode) {
CmdFrameT cmd_frame;
uint16_t unknown_parm = 0x0000;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_WRITE_SYS_PARAM;
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_SYSTEM_MODE, sizeof(CMD_SYSTEM_MODE));
cmd_frame.data_length += sizeof(CMD_SYSTEM_MODE);
memcpy(&cmd_frame.data[cmd_frame.data_length], &mode, sizeof(mode));
cmd_frame.data_length += sizeof(mode);
memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm));
cmd_frame.data_length += sizeof(unknown_parm);
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending write system mode command: %2X", cmd_frame.command);
if (this->send_cmd_from_array(cmd_frame) == 0)
set_mode_(mode);
}
void LD2420Component::get_firmware_version_() {
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_READ_VERSION;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending read firmware version command: %2X", cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
void LD2420Component::set_min_max_distances_timeout(uint32_t max_gate_distance, uint32_t min_gate_distance, // NOLINT
uint32_t timeout) {
// Header H, Length L, Register R, Value V, Footer F
// |Min Gate |Max Gate |Timeout |
// HH HH HH HH LL LL CC CC RR RR VV VV VV VV RR RR VV VV VV VV RR RR VV VV VV VV FF FF FF FF
// FD FC FB FA 14 00 07 00 00 00 01 00 00 00 01 00 09 00 00 00 04 00 0A 00 00 00 04 03 02 01 e.g.
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_WRITE_ABD_PARAM;
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG,
sizeof(CMD_MIN_GATE_REG)); // Register: global min detect gate number
cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG);
memcpy(&cmd_frame.data[cmd_frame.data_length], &min_gate_distance, sizeof(min_gate_distance));
cmd_frame.data_length += sizeof(min_gate_distance);
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG,
sizeof(CMD_MAX_GATE_REG)); // Register: global max detect gate number
cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG);
memcpy(&cmd_frame.data[cmd_frame.data_length], &max_gate_distance, sizeof(max_gate_distance));
cmd_frame.data_length += sizeof(max_gate_distance);
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG,
sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
memcpy(&cmd_frame.data[cmd_frame.data_length], &timeout, sizeof(timeout));
;
cmd_frame.data_length += sizeof(timeout);
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
void LD2420Component::set_gate_threshold(uint8_t gate) {
// Header H, Length L, Command C, Register R, Value V, Footer F
// HH HH HH HH LL LL CC CC RR RR VV VV VV VV RR RR VV VV VV VV FF FF FF FF
// FD FC FB FA 14 00 07 00 10 00 00 FF 00 00 00 01 00 0F 00 00 04 03 02 01
uint16_t move_threshold_gate = CMD_GATE_MOVE_THRESH[gate];
uint16_t still_threshold_gate = CMD_GATE_STILL_THRESH[gate];
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_WRITE_ABD_PARAM;
memcpy(&cmd_frame.data[cmd_frame.data_length], &move_threshold_gate, sizeof(move_threshold_gate));
cmd_frame.data_length += sizeof(move_threshold_gate);
memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.move_thresh[gate],
sizeof(this->new_config.move_thresh[gate]));
cmd_frame.data_length += sizeof(this->new_config.move_thresh[gate]);
memcpy(&cmd_frame.data[cmd_frame.data_length], &still_threshold_gate, sizeof(still_threshold_gate));
cmd_frame.data_length += sizeof(still_threshold_gate);
memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.still_thresh[gate],
sizeof(this->new_config.still_thresh[gate]));
cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]);
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
#ifdef USE_NUMBER
void LD2420Component::init_gate_config_numbers() {
if (this->gate_timeout_number_ != nullptr)
this->gate_timeout_number_->publish_state(static_cast<uint16_t>(this->current_config.timeout));
if (this->gate_select_number_ != nullptr)
this->gate_select_number_->publish_state(0);
if (this->min_gate_distance_number_ != nullptr)
this->min_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.min_gate));
if (this->max_gate_distance_number_ != nullptr)
this->max_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.max_gate));
if (this->gate_move_sensitivity_factor_number_ != nullptr)
this->gate_move_sensitivity_factor_number_->publish_state(this->gate_move_sensitivity_factor);
if (this->gate_still_sensitivity_factor_number_ != nullptr)
this->gate_still_sensitivity_factor_number_->publish_state(this->gate_still_sensitivity_factor);
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
if (this->gate_still_threshold_numbers_[gate] != nullptr) {
this->gate_still_threshold_numbers_[gate]->publish_state(
static_cast<uint16_t>(this->current_config.still_thresh[gate]));
}
if (this->gate_move_threshold_numbers_[gate] != nullptr) {
this->gate_move_threshold_numbers_[gate]->publish_state(
static_cast<uint16_t>(this->current_config.move_thresh[gate]));
}
}
}
void LD2420Component::refresh_gate_config_numbers() {
this->gate_timeout_number_->publish_state(this->new_config.timeout);
this->min_gate_distance_number_->publish_state(this->new_config.min_gate);
this->max_gate_distance_number_->publish_state(this->new_config.max_gate);
}
#endif
} // namespace ld2420
} // namespace esphome

View File

@ -0,0 +1,272 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/automation.h"
#include "esphome/core/helpers.h"
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
#ifdef USE_SELECT
#include "esphome/components/select/select.h"
#endif
#ifdef USE_NUMBER
#include "esphome/components/number/number.h"
#endif
#ifdef USE_BUTTON
#include "esphome/components/button/button.h"
#endif
#include <map>
#include <functional>
namespace esphome {
namespace ld2420 {
// Local const's
static const uint16_t REFRESH_RATE_MS = 1000;
// Command sets
static const uint8_t CMD_ABD_DATA_REPLY_SIZE = 0x04;
static const uint8_t CMD_ABD_DATA_REPLY_START = 0x0A;
static const uint16_t CMD_DISABLE_CONF = 0x00FE;
static const uint16_t CMD_ENABLE_CONF = 0x00FF;
static const uint8_t CMD_MAX_BYTES = 0x64;
static const uint16_t CMD_PARM_HIGH_TRESH = 0x0012;
static const uint16_t CMD_PARM_LOW_TRESH = 0x0021;
static const uint16_t CMD_PROTOCOL_VER = 0x0002;
static const uint16_t CMD_READ_ABD_PARAM = 0x0008;
static const uint16_t CMD_READ_REG_ADDR = 0x0020;
static const uint16_t CMD_READ_REGISTER = 0x0002;
static const uint16_t CMD_READ_SERIAL_NUM = 0x0011;
static const uint16_t CMD_READ_SYS_PARAM = 0x0013;
static const uint16_t CMD_READ_VERSION = 0x0000;
static const uint8_t CMD_REG_DATA_REPLY_SIZE = 0x02;
static const uint16_t CMD_RESTART = 0x0068;
static const uint16_t CMD_SYSTEM_MODE = 0x0000;
static const uint16_t CMD_SYSTEM_MODE_GR = 0x0003;
static const uint16_t CMD_SYSTEM_MODE_MTT = 0x0001;
static const uint16_t CMD_SYSTEM_MODE_SIMPLE = 0x0064;
static const uint16_t CMD_SYSTEM_MODE_DEBUG = 0x0000;
static const uint16_t CMD_SYSTEM_MODE_ENERGY = 0x0004;
static const uint16_t CMD_SYSTEM_MODE_VS = 0x0002;
static const uint16_t CMD_WRITE_ABD_PARAM = 0x0007;
static const uint16_t CMD_WRITE_REGISTER = 0x0001;
static const uint16_t CMD_WRITE_SYS_PARAM = 0x0012;
static const uint8_t LD2420_ERROR_NONE = 0x00;
static const uint8_t LD2420_ERROR_TIMEOUT = 0x02;
static const uint8_t LD2420_ERROR_UNKNOWN = 0x01;
static const uint8_t LD2420_TOTAL_GATES = 16;
static const uint8_t CALIBRATE_SAMPLES = 64;
// Register address values
static const uint16_t CMD_MIN_GATE_REG = 0x0000;
static const uint16_t CMD_MAX_GATE_REG = 0x0001;
static const uint16_t CMD_TIMEOUT_REG = 0x0004;
static const uint16_t CMD_GATE_MOVE_THRESH[LD2420_TOTAL_GATES] = {0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015,
0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B,
0x001C, 0x001D, 0x001E, 0x001F};
static const uint16_t CMD_GATE_STILL_THRESH[LD2420_TOTAL_GATES] = {0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025,
0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B,
0x002C, 0x002D, 0x002E, 0x002F};
static const uint32_t FACTORY_MOVE_THRESH[LD2420_TOTAL_GATES] = {60000, 30000, 400, 250, 250, 250, 250, 250,
250, 250, 250, 250, 250, 250, 250, 250};
static const uint32_t FACTORY_STILL_THRESH[LD2420_TOTAL_GATES] = {40000, 20000, 200, 200, 200, 200, 200, 150,
150, 100, 100, 100, 100, 100, 100, 100};
static const uint16_t FACTORY_TIMEOUT = 120;
static const uint16_t FACTORY_MIN_GATE = 1;
static const uint16_t FACTORY_MAX_GATE = 12;
// COMMAND_BYTE Header & Footer
static const uint8_t CMD_FRAME_COMMAND = 6;
static const uint8_t CMD_FRAME_DATA_LENGTH = 4;
static const uint32_t CMD_FRAME_FOOTER = 0x01020304;
static const uint32_t CMD_FRAME_HEADER = 0xFAFBFCFD;
static const uint32_t DEBUG_FRAME_FOOTER = 0xFAFBFCFD;
static const uint32_t DEBUG_FRAME_HEADER = 0x1410BFAA;
static const uint32_t ENERGY_FRAME_FOOTER = 0xF5F6F7F8;
static const uint32_t ENERGY_FRAME_HEADER = 0xF1F2F3F4;
static const uint8_t CMD_FRAME_STATUS = 7;
static const uint8_t CMD_ERROR_WORD = 8;
static const uint8_t ENERGY_SENSOR_START = 9;
static const uint8_t CALIBRATE_REPORT_INTERVAL = 4;
static const int CALIBRATE_VERSION_MIN = 154;
static const std::string OP_NORMAL_MODE_STRING = "Normal";
static const std::string OP_SIMPLE_MODE_STRING = "Simple";
enum OpModeStruct : uint8_t { OP_NORMAL_MODE = 1, OP_CALIBRATE_MODE = 2, OP_SIMPLE_MODE = 3 };
static const std::map<std::string, uint8_t> OP_MODE_TO_UINT{
{"Normal", OP_NORMAL_MODE}, {"Calibrate", OP_CALIBRATE_MODE}, {"Simple", OP_SIMPLE_MODE}};
static constexpr const char *ERR_MESSAGE[] = {"None", "Unknown", "Timeout"};
class LD2420Listener {
public:
virtual void on_presence(bool presence){};
virtual void on_distance(uint16_t distance){};
virtual void on_energy(uint16_t *sensor_energy, size_t size){};
virtual void on_fw_version(std::string &fw){};
};
class LD2420Component : public Component, public uart::UARTDevice {
public:
void setup() override;
void dump_config() override;
void loop() override;
#ifdef USE_SELECT
void set_operating_mode_select(select::Select *selector) { this->operating_selector_ = selector; };
#endif
#ifdef USE_NUMBER
void set_gate_timeout_number(number::Number *number) { this->gate_timeout_number_ = number; };
void set_gate_select_number(number::Number *number) { this->gate_select_number_ = number; };
void set_min_gate_distance_number(number::Number *number) { this->min_gate_distance_number_ = number; };
void set_max_gate_distance_number(number::Number *number) { this->max_gate_distance_number_ = number; };
void set_gate_move_sensitivity_factor_number(number::Number *number) {
this->gate_move_sensitivity_factor_number_ = number;
};
void set_gate_still_sensitivity_factor_number(number::Number *number) {
this->gate_still_sensitivity_factor_number_ = number;
};
void set_gate_still_threshold_numbers(int gate, number::Number *n) { this->gate_still_threshold_numbers_[gate] = n; };
void set_gate_move_threshold_numbers(int gate, number::Number *n) { this->gate_move_threshold_numbers_[gate] = n; };
bool is_gate_select() { return gate_select_number_ != nullptr; };
uint8_t get_gate_select_value() { return static_cast<uint8_t>(this->gate_select_number_->state); };
float get_min_gate_distance_value() { return min_gate_distance_number_->state; };
float get_max_gate_distance_value() { return max_gate_distance_number_->state; };
void publish_gate_move_threshold(uint8_t gate) {
// With gate_select we only use 1 number pointer, thus we hard code [0]
this->gate_move_threshold_numbers_[0]->publish_state(this->new_config.move_thresh[gate]);
};
void publish_gate_still_threshold(uint8_t gate) {
this->gate_still_threshold_numbers_[0]->publish_state(this->new_config.still_thresh[gate]);
};
void init_gate_config_numbers();
void refresh_gate_config_numbers();
#endif
#ifdef USE_BUTTON
void set_apply_config_button(button::Button *button) { this->apply_config_button_ = button; };
void set_revert_config_button(button::Button *button) { this->revert_config_button_ = button; };
void set_restart_module_button(button::Button *button) { this->restart_module_button_ = button; };
void set_factory_reset_button(button::Button *button) { this->factory_reset_button_ = button; };
#endif
void register_listener(LD2420Listener *listener) { this->listeners_.push_back(listener); }
struct CmdFrameT {
uint32_t header{0};
uint16_t length{0};
uint16_t command{0};
uint8_t data[18];
uint16_t data_length{0};
uint32_t footer{0};
};
struct RegConfigT {
uint16_t min_gate{0};
uint16_t max_gate{0};
uint16_t timeout{0};
uint32_t move_thresh[LD2420_TOTAL_GATES];
uint32_t still_thresh[LD2420_TOTAL_GATES];
};
void send_module_restart();
void restart_module_action();
void apply_config_action();
void factory_reset_action();
void revert_config_action();
float get_setup_priority() const override;
int send_cmd_from_array(CmdFrameT cmd_frame);
void report_gate_data();
void handle_cmd_error(uint8_t error);
void set_operating_mode(const std::string &state);
void auto_calibrate_sensitivity();
void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number);
uint8_t calc_checksum(void *data, size_t size);
RegConfigT current_config;
RegConfigT new_config;
int32_t last_periodic_millis = millis();
int32_t report_periodic_millis = millis();
int32_t monitor_periodic_millis = millis();
int32_t last_normal_periodic_millis = millis();
bool output_energy_state{false};
uint8_t current_operating_mode{OP_NORMAL_MODE};
uint16_t radar_data[LD2420_TOTAL_GATES][CALIBRATE_SAMPLES];
uint16_t gate_avg[LD2420_TOTAL_GATES];
uint16_t gate_peak[LD2420_TOTAL_GATES];
uint8_t sample_number_counter{0};
uint16_t total_sample_number_counter{0};
float gate_move_sensitivity_factor{0.5};
float gate_still_sensitivity_factor{0.5};
#ifdef USE_SELECT
select::Select *operating_selector_{nullptr};
#endif
#ifdef USE_BUTTON
button::Button *apply_config_button_{nullptr};
button::Button *revert_config_button_{nullptr};
button::Button *restart_module_button_{nullptr};
button::Button *factory_reset_button_{nullptr};
#endif
void set_min_max_distances_timeout(uint32_t max_gate_distance, uint32_t min_gate_distance, uint32_t timeout);
void set_gate_threshold(uint8_t gate);
void set_reg_value(uint16_t reg, uint16_t value);
uint8_t set_config_mode(bool enable);
void set_system_mode(uint16_t mode);
void ld2420_restart();
protected:
struct CmdReplyT {
uint8_t command;
uint8_t status;
uint32_t data[4];
uint8_t length;
uint16_t error;
volatile bool ack;
};
int get_firmware_int_(const char *version_string);
void get_firmware_version_();
int get_gate_threshold_(uint8_t gate);
void get_reg_value_(uint16_t reg);
int get_min_max_distances_timeout_();
uint16_t get_mode_() { return this->system_mode_; };
void set_mode_(uint16_t mode) { this->system_mode_ = mode; };
bool get_presence_() { return this->presence_; };
void set_presence_(bool presence) { this->presence_ = presence; };
uint16_t get_distance_() { return this->distance_; };
void set_distance_(uint16_t distance) { this->distance_ = distance; };
bool get_cmd_active_() { return this->cmd_active_; };
void set_cmd_active_(bool active) { this->cmd_active_ = active; };
void handle_simple_mode_(const uint8_t *inbuf, int len);
void handle_energy_mode_(uint8_t *buffer, int len);
void handle_ack_data_(uint8_t *buffer, int len);
void readline_(int rx_data, uint8_t *buffer, int len);
void set_calibration_(bool state) { this->calibration_ = state; };
bool get_calibration_() { return this->calibration_; };
#ifdef USE_NUMBER
number::Number *gate_timeout_number_{nullptr};
number::Number *gate_select_number_{nullptr};
number::Number *min_gate_distance_number_{nullptr};
number::Number *max_gate_distance_number_{nullptr};
number::Number *gate_move_sensitivity_factor_number_{nullptr};
number::Number *gate_still_sensitivity_factor_number_{nullptr};
std::vector<number::Number *> gate_still_threshold_numbers_ = std::vector<number::Number *>(16);
std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(16);
#endif
uint16_t gate_energy_[LD2420_TOTAL_GATES];
CmdReplyT cmd_reply_;
uint32_t timeout_;
uint32_t max_distance_gate_;
uint32_t min_distance_gate_;
uint16_t system_mode_{CMD_SYSTEM_MODE_ENERGY};
bool cmd_active_{false};
char ld2420_firmware_ver_[8];
bool presence_{false};
bool calibration_{false};
uint16_t distance_{0};
uint8_t config_checksum_{0};
std::vector<LD2420Listener *> listeners_{};
};
} // namespace ld2420
} // namespace esphome

View File

@ -0,0 +1,183 @@
import esphome.codegen as cg
from esphome.components import number
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
DEVICE_CLASS_DISTANCE,
UNIT_SECOND,
ENTITY_CATEGORY_CONFIG,
ICON_MOTION_SENSOR,
ICON_TIMELAPSE,
ICON_SCALE,
)
from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns
LD2420TimeoutNumber = ld2420_ns.class_("LD2420TimeoutNumber", number.Number)
LD2420MoveSensFactorNumber = ld2420_ns.class_(
"LD2420MoveSensFactorNumber", number.Number
)
LD2420StillSensFactorNumber = ld2420_ns.class_(
"LD2420StillSensFactorNumber", number.Number
)
LD2420MinDistanceNumber = ld2420_ns.class_("LD2420MinDistanceNumber", number.Number)
LD2420MaxDistanceNumber = ld2420_ns.class_("LD2420MaxDistanceNumber", number.Number)
LD2420GateSelectNumber = ld2420_ns.class_("LD2420GateSelectNumber", number.Number)
LD2420MoveThresholdNumbers = ld2420_ns.class_(
"LD2420MoveThresholdNumbers", number.Number
)
LD2420StillThresholdNumbers = ld2420_ns.class_(
"LD2420StillThresholdNumbers", number.Number
)
CONF_MIN_GATE_DISTANCE = "min_gate_distance"
CONF_MAX_GATE_DISTANCE = "max_gate_distance"
CONF_STILL_THRESHOLD = "still_threshold"
CONF_MOVE_THRESHOLD = "move_threshold"
CONF_GATE_MOVE_SENSITIVITY = "gate_move_sensitivity"
CONF_GATE_STILL_SENSITIVITY = "gate_still_sensitivity"
CONF_GATE_SELECT = "gate_select"
CONF_PRESENCE_TIMEOUT = "presence_timeout"
GATE_GROUP = "gate_group"
TIMEOUT_GROUP = "timeout_group"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
cv.Inclusive(CONF_PRESENCE_TIMEOUT, TIMEOUT_GROUP): number.number_schema(
LD2420TimeoutNumber,
unit_of_measurement=UNIT_SECOND,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_TIMELAPSE,
),
cv.Inclusive(CONF_MIN_GATE_DISTANCE, TIMEOUT_GROUP): number.number_schema(
LD2420MinDistanceNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Inclusive(CONF_MAX_GATE_DISTANCE, TIMEOUT_GROUP): number.number_schema(
LD2420MaxDistanceNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Inclusive(CONF_GATE_SELECT, GATE_GROUP): number.number_schema(
LD2420GateSelectNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Inclusive(CONF_STILL_THRESHOLD, GATE_GROUP): number.number_schema(
LD2420StillThresholdNumbers,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Inclusive(CONF_MOVE_THRESHOLD, GATE_GROUP): number.number_schema(
LD2420MoveThresholdNumbers,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Optional(CONF_GATE_MOVE_SENSITIVITY): number.number_schema(
LD2420MoveSensFactorNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
cv.Optional(CONF_GATE_STILL_SENSITIVITY): number.number_schema(
LD2420StillSensFactorNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
}
)
CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
{
cv.Optional(f"gate_{x}"): (
{
cv.Required(CONF_MOVE_THRESHOLD): number.number_schema(
LD2420MoveThresholdNumbers,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Required(CONF_STILL_THRESHOLD): number.number_schema(
LD2420StillThresholdNumbers,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
}
)
for x in range(16)
}
)
async def to_code(config):
LD2420_component = await cg.get_variable(config[CONF_LD2420_ID])
if gate_timeout_config := config.get(CONF_PRESENCE_TIMEOUT):
n = await number.new_number(
gate_timeout_config, min_value=0, max_value=255, step=5
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_gate_timeout_number(n))
if min_distance_gate_config := config.get(CONF_MIN_GATE_DISTANCE):
n = await number.new_number(
min_distance_gate_config, min_value=0, max_value=15, step=1
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_min_gate_distance_number(n))
if max_distance_gate_config := config.get(CONF_MAX_GATE_DISTANCE):
n = await number.new_number(
max_distance_gate_config, min_value=1, max_value=15, step=1
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_max_gate_distance_number(n))
if gate_move_sensitivity_config := config.get(CONF_GATE_MOVE_SENSITIVITY):
n = await number.new_number(
gate_move_sensitivity_config, min_value=0.05, max_value=1, step=0.025
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_gate_move_sensitivity_factor_number(n))
if gate_still_sensitivity_config := config.get(CONF_GATE_STILL_SENSITIVITY):
n = await number.new_number(
gate_still_sensitivity_config, min_value=0.05, max_value=1, step=0.025
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_gate_still_sensitivity_factor_number(n))
if config.get(CONF_GATE_SELECT):
if gate_number := config.get(CONF_GATE_SELECT):
n = await number.new_number(gate_number, min_value=0, max_value=15, step=1)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_gate_select_number(n))
if gate_still_threshold := config.get(CONF_STILL_THRESHOLD):
n = cg.new_Pvariable(gate_still_threshold[CONF_ID])
await number.register_number(
n, gate_still_threshold, min_value=0, max_value=65535, step=25
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_gate_still_threshold_numbers(0, n))
if gate_move_threshold := config.get(CONF_MOVE_THRESHOLD):
n = cg.new_Pvariable(gate_move_threshold[CONF_ID])
await number.register_number(
n, gate_move_threshold, min_value=0, max_value=65535, step=25
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_gate_move_threshold_numbers(0, n))
else:
for x in range(16):
if gate_conf := config.get(f"gate_{x}"):
move_config = gate_conf[CONF_MOVE_THRESHOLD]
n = cg.new_Pvariable(move_config[CONF_ID], x)
await number.register_number(
n, move_config, min_value=0, max_value=65535, step=25
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_gate_move_threshold_numbers(x, n))
still_config = gate_conf[CONF_STILL_THRESHOLD]
n = cg.new_Pvariable(still_config[CONF_ID], x)
await number.register_number(
n, still_config, min_value=0, max_value=65535, step=25
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_gate_still_threshold_numbers(x, n))

View File

@ -0,0 +1,73 @@
#include "gate_config_number.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
static const char *const TAG = "LD2420.number";
namespace esphome {
namespace ld2420 {
void LD2420TimeoutNumber::control(float timeout) {
this->publish_state(timeout);
this->parent_->new_config.timeout = timeout;
}
void LD2420MinDistanceNumber::control(float min_gate) {
if ((uint16_t) min_gate > this->parent_->new_config.max_gate) {
min_gate = this->parent_->get_min_gate_distance_value();
} else {
this->parent_->new_config.min_gate = (uint16_t) min_gate;
}
this->publish_state(min_gate);
}
void LD2420MaxDistanceNumber::control(float max_gate) {
if ((uint16_t) max_gate < this->parent_->new_config.min_gate) {
max_gate = this->parent_->get_max_gate_distance_value();
} else {
this->parent_->new_config.max_gate = (uint16_t) max_gate;
}
this->publish_state(max_gate);
}
void LD2420GateSelectNumber::control(float gate_select) {
const uint8_t gate = (uint8_t) gate_select;
this->publish_state(gate_select);
this->parent_->publish_gate_move_threshold(gate);
this->parent_->publish_gate_still_threshold(gate);
}
void LD2420MoveSensFactorNumber::control(float move_factor) {
this->publish_state(move_factor);
this->parent_->gate_move_sensitivity_factor = move_factor;
}
void LD2420StillSensFactorNumber::control(float still_factor) {
this->publish_state(still_factor);
this->parent_->gate_still_sensitivity_factor = still_factor;
}
LD2420MoveThresholdNumbers::LD2420MoveThresholdNumbers(uint8_t gate) : gate_(gate) {}
void LD2420MoveThresholdNumbers::control(float move_threshold) {
this->publish_state(move_threshold);
if (!this->parent_->is_gate_select()) {
this->parent_->new_config.move_thresh[this->gate_] = move_threshold;
} else {
this->parent_->new_config.move_thresh[this->parent_->get_gate_select_value()] = move_threshold;
}
}
LD2420StillThresholdNumbers::LD2420StillThresholdNumbers(uint8_t gate) : gate_(gate) {}
void LD2420StillThresholdNumbers::control(float still_threshold) {
this->publish_state(still_threshold);
if (!this->parent_->is_gate_select()) {
this->parent_->new_config.still_thresh[this->gate_] = still_threshold;
} else {
this->parent_->new_config.still_thresh[this->parent_->get_gate_select_value()] = still_threshold;
}
}
} // namespace ld2420
} // namespace esphome

View File

@ -0,0 +1,78 @@
#pragma once
#include "esphome/components/number/number.h"
#include "../ld2420.h"
namespace esphome {
namespace ld2420 {
class LD2420TimeoutNumber : public number::Number, public Parented<LD2420Component> {
public:
LD2420TimeoutNumber() = default;
protected:
void control(float timeout) override;
};
class LD2420MinDistanceNumber : public number::Number, public Parented<LD2420Component> {
public:
LD2420MinDistanceNumber() = default;
protected:
void control(float min_gate) override;
};
class LD2420MaxDistanceNumber : public number::Number, public Parented<LD2420Component> {
public:
LD2420MaxDistanceNumber() = default;
protected:
void control(float max_gate) override;
};
class LD2420GateSelectNumber : public number::Number, public Parented<LD2420Component> {
public:
LD2420GateSelectNumber() = default;
protected:
void control(float gate_select) override;
};
class LD2420MoveSensFactorNumber : public number::Number, public Parented<LD2420Component> {
public:
LD2420MoveSensFactorNumber() = default;
protected:
void control(float move_factor) override;
};
class LD2420StillSensFactorNumber : public number::Number, public Parented<LD2420Component> {
public:
LD2420StillSensFactorNumber() = default;
protected:
void control(float still_factor) override;
};
class LD2420StillThresholdNumbers : public number::Number, public Parented<LD2420Component> {
public:
LD2420StillThresholdNumbers() = default;
LD2420StillThresholdNumbers(uint8_t gate);
protected:
uint8_t gate_;
void control(float still_threshold) override;
};
class LD2420MoveThresholdNumbers : public number::Number, public Parented<LD2420Component> {
public:
LD2420MoveThresholdNumbers() = default;
LD2420MoveThresholdNumbers(uint8_t gate);
protected:
uint8_t gate_;
void control(float move_threshold) override;
};
} // namespace ld2420
} // namespace esphome

View File

@ -0,0 +1,33 @@
import esphome.codegen as cg
from esphome.components import select
import esphome.config_validation as cv
from esphome.const import ENTITY_CATEGORY_CONFIG
from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns
CONF_OPERATING_MODE = "operating_mode"
CONF_SELECTS = [
"Normal",
"Calibrate",
"Simple",
]
LD2420Select = ld2420_ns.class_("LD2420Select", cg.Component)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
cv.Required(CONF_OPERATING_MODE): select.select_schema(
LD2420Select,
entity_category=ENTITY_CATEGORY_CONFIG,
),
}
async def to_code(config):
LD2420_component = await cg.get_variable(config[CONF_LD2420_ID])
if operating_mode_config := config.get(CONF_OPERATING_MODE):
sel = await select.new_select(
operating_mode_config,
options=[CONF_SELECTS],
)
await cg.register_parented(sel, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_operating_mode_select(sel))

View File

@ -0,0 +1,16 @@
#include "operating_mode_select.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ld2420 {
static const char *const TAG = "LD2420.select";
void LD2420Select::control(const std::string &value) {
this->publish_state(value);
this->parent_->set_operating_mode(value);
}
} // namespace ld2420
} // namespace esphome

View File

@ -0,0 +1,18 @@
#pragma once
#include "../ld2420.h"
#include "esphome/components/select/select.h"
namespace esphome {
namespace ld2420 {
class LD2420Select : public Component, public select::Select, public Parented<LD2420Component> {
public:
LD2420Select() = default;
protected:
void control(const std::string &value) override;
};
} // namespace ld2420
} // namespace esphome

View File

@ -0,0 +1,35 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import CONF_ID, DEVICE_CLASS_DISTANCE, UNIT_CENTIMETER
from .. import ld2420_ns, LD2420Component, CONF_LD2420_ID
LD2420Sensor = ld2420_ns.class_("LD2420Sensor", sensor.Sensor, cg.Component)
CONF_MOVING_DISTANCE = "moving_distance"
CONF_GATE_ENERGY = "gate_energy"
CONFIG_SCHEMA = cv.All(
cv.COMPONENT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(LD2420Sensor),
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER
),
}
),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
if CONF_MOVING_DISTANCE in config:
sens = await sensor.new_sensor(config[CONF_MOVING_DISTANCE])
cg.add(var.set_distance_sensor(sens))
if CONF_GATE_ENERGY in config:
sens = await sensor.new_sensor(config[CONF_GATE_ENERGY])
cg.add(var.set_energy_sensor(sens))
ld2420 = await cg.get_variable(config[CONF_LD2420_ID])
cg.add(ld2420.register_listener(var))

View File

@ -0,0 +1,16 @@
#include "ld2420_sensor.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ld2420 {
static const char *const TAG = "LD2420.sensor";
void LD2420Sensor::dump_config() {
ESP_LOGCONFIG(TAG, "LD2420 Sensor:");
LOG_SENSOR(" ", "Distance", this->distance_sensor_);
}
} // namespace ld2420
} // namespace esphome

View File

@ -0,0 +1,34 @@
#pragma once
#include "../ld2420.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace ld2420 {
class LD2420Sensor : public LD2420Listener, public Component, sensor::Sensor {
public:
void dump_config() override;
void set_distance_sensor(sensor::Sensor *sensor) { this->distance_sensor_ = sensor; }
void on_distance(uint16_t distance) override {
if (this->distance_sensor_ != nullptr) {
if (this->distance_sensor_->get_state() != distance) {
this->distance_sensor_->publish_state(distance);
}
}
}
void on_energy(uint16_t *gate_energy, size_t size) override {
for (size_t active = 0; active < size; active++) {
if (this->energy_sensors_[active] != nullptr) {
this->energy_sensors_[active]->publish_state(gate_energy[active]);
}
}
}
protected:
sensor::Sensor *distance_sensor_{nullptr};
std::vector<sensor::Sensor *> energy_sensors_ = std::vector<sensor::Sensor *>(LD2420_TOTAL_GATES);
};
} // namespace ld2420
} // namespace esphome

View File

@ -0,0 +1,38 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_CHIP,
)
from .. import ld2420_ns, LD2420Component, CONF_LD2420_ID
LD2420TextSensor = ld2420_ns.class_(
"LD2420TextSensor", text_sensor.TextSensor, cg.Component
)
CONF_FW_VERSION = "fw_version"
CONFIG_SCHEMA = cv.All(
cv.COMPONENT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(LD2420TextSensor),
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
cv.Optional(CONF_FW_VERSION): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP
),
}
),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
if CONF_FW_VERSION in config:
sens = await text_sensor.new_text_sensor(config[CONF_FW_VERSION])
cg.add(var.set_fw_version_text_sensor(sens))
ld2420 = await cg.get_variable(config[CONF_LD2420_ID])
cg.add(ld2420.register_listener(var))

View File

@ -0,0 +1,16 @@
#include "text_sensor.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ld2420 {
static const char *const TAG = "LD2420.text_sensor";
void LD2420TextSensor::dump_config() {
ESP_LOGCONFIG(TAG, "LD2420 TextSensor:");
LOG_TEXT_SENSOR(" ", "Firmware", this->fw_version_text_sensor_);
}
} // namespace ld2420
} // namespace esphome

View File

@ -0,0 +1,24 @@
#pragma once
#include "../ld2420.h"
#include "esphome/components/text_sensor/text_sensor.h"
namespace esphome {
namespace ld2420 {
class LD2420TextSensor : public LD2420Listener, public Component, text_sensor::TextSensor {
public:
void dump_config() override;
void set_fw_version_text_sensor(text_sensor::TextSensor *tsensor) { this->fw_version_text_sensor_ = tsensor; };
void on_fw_version(std::string &fw) override {
if (this->fw_version_text_sensor_ != nullptr) {
this->fw_version_text_sensor_->publish_state(fw);
}
}
protected:
text_sensor::TextSensor *fw_version_text_sensor_{nullptr};
};
} // namespace ld2420
} // namespace esphome

View File

@ -3,8 +3,8 @@
#include <utility>
#include <vector>
#include "light_effect.h"
#include "esphome/core/automation.h"
#include "light_effect.h"
namespace esphome {
namespace light {
@ -27,8 +27,8 @@ class PulseLightEffect : public LightEffect {
auto call = this->state_->turn_on();
float out = this->on_ ? this->max_brightness : this->min_brightness;
call.set_brightness_if_supported(out);
call.set_transition_length_if_supported(this->on_ ? this->transition_on_length_ : this->transition_off_length_);
this->on_ = !this->on_;
call.set_transition_length_if_supported(this->transition_length_);
// don't tell HA every change
call.set_publish(false);
call.set_save(false);
@ -37,7 +37,8 @@ class PulseLightEffect : public LightEffect {
this->last_color_change_ = now;
}
void set_transition_length(uint32_t transition_length) { this->transition_length_ = transition_length; }
void set_transition_on_length(uint32_t transition_length) { this->transition_on_length_ = transition_length; }
void set_transition_off_length(uint32_t transition_length) { this->transition_off_length_ = transition_length; }
void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
@ -49,7 +50,8 @@ class PulseLightEffect : public LightEffect {
protected:
bool on_ = false;
uint32_t last_color_change_{0};
uint32_t transition_length_{};
uint32_t transition_on_length_{};
uint32_t transition_off_length_{};
uint32_t update_interval_{};
float min_brightness{0.0};
float max_brightness{1.0};

View File

@ -76,6 +76,8 @@ CONF_ADDRESSABLE_RANDOM_TWINKLE = "addressable_random_twinkle"
CONF_ADDRESSABLE_FIREWORKS = "addressable_fireworks"
CONF_ADDRESSABLE_FLICKER = "addressable_flicker"
CONF_AUTOMATION = "automation"
CONF_ON_LENGTH = "on_length"
CONF_OFF_LENGTH = "off_length"
BINARY_EFFECTS = []
MONOCHROMATIC_EFFECTS = []
@ -170,9 +172,15 @@ async def automation_effect_to_code(config, effect_id):
PulseLightEffect,
"Pulse",
{
cv.Optional(
CONF_TRANSITION_LENGTH, default="1s"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_TRANSITION_LENGTH, default="1s"): cv.Any(
cv.positive_time_period_milliseconds,
cv.Schema(
{
cv.Required(CONF_ON_LENGTH): cv.positive_time_period_milliseconds,
cv.Required(CONF_OFF_LENGTH): cv.positive_time_period_milliseconds,
}
),
),
cv.Optional(
CONF_UPDATE_INTERVAL, default="1s"
): cv.positive_time_period_milliseconds,
@ -182,7 +190,21 @@ async def automation_effect_to_code(config, effect_id):
)
async def pulse_effect_to_code(config, effect_id):
effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
cg.add(effect.set_transition_length(config[CONF_TRANSITION_LENGTH]))
if isinstance(config[CONF_TRANSITION_LENGTH], dict):
cg.add(
effect.set_transition_on_length(
config[CONF_TRANSITION_LENGTH][CONF_ON_LENGTH]
)
)
cg.add(
effect.set_transition_off_length(
config[CONF_TRANSITION_LENGTH][CONF_OFF_LENGTH]
)
)
else:
transition_length = config[CONF_TRANSITION_LENGTH]
cg.add(effect.set_transition_on_length(transition_length))
cg.add(effect.set_transition_off_length(transition_length))
cg.add(effect.set_update_interval(config[CONF_UPDATE_INTERVAL]))
cg.add(
effect.set_min_max_brightness(

View File

@ -47,7 +47,7 @@ void MAX31855Sensor::read_data_() {
if (mem != 0xFFFFFFFF) {
this->status_clear_error();
} else {
ESP_LOGE(TAG, "No data received from MAX31855 (0x%08X). Check wiring!", mem);
ESP_LOGE(TAG, "No data received from MAX31855 (0x%08" PRIX32 "). Check wiring!", mem);
this->publish_state(NAN);
if (this->temperature_reference_) {
this->temperature_reference_->publish_state(NAN);
@ -69,25 +69,25 @@ void MAX31855Sensor::read_data_() {
// Check thermocouple faults
if (mem & 0x00000001) {
ESP_LOGW(TAG, "Thermocouple open circuit (not connected) fault from MAX31855 (0x%08X)", mem);
ESP_LOGW(TAG, "Thermocouple open circuit (not connected) fault from MAX31855 (0x%08" PRIX32 ")", mem);
this->publish_state(NAN);
this->status_set_warning();
return;
}
if (mem & 0x00000002) {
ESP_LOGW(TAG, "Thermocouple short circuit to ground fault from MAX31855 (0x%08X)", mem);
ESP_LOGW(TAG, "Thermocouple short circuit to ground fault from MAX31855 (0x%08" PRIX32 ")", mem);
this->publish_state(NAN);
this->status_set_warning();
return;
}
if (mem & 0x00000004) {
ESP_LOGW(TAG, "Thermocouple short circuit to VCC fault from MAX31855 (0x%08X)", mem);
ESP_LOGW(TAG, "Thermocouple short circuit to VCC fault from MAX31855 (0x%08" PRIX32 ")", mem);
this->publish_state(NAN);
this->status_set_warning();
return;
}
if (mem & 0x00010000) {
ESP_LOGW(TAG, "Got faulty reading from MAX31855 (0x%08X)", mem);
ESP_LOGW(TAG, "Got faulty reading from MAX31855 (0x%08" PRIX32 ")", mem);
this->publish_state(NAN);
this->status_set_warning();
return;

View File

@ -4,6 +4,8 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/spi/spi.h"
#include <cinttypes>
namespace esphome {
namespace max31855 {

View File

@ -188,7 +188,7 @@ uint32_t MAX31856Sensor::read_register24_(uint8_t reg) {
ESP_LOGVV(TAG, "read_byte lsb=0x%02X", lsb);
this->disable();
const uint32_t value((msb << 16) | (mid << 8) | lsb);
ESP_LOGV(TAG, "read_register_24_ reg=0x%02X: value=0x%06X", reg, value);
ESP_LOGV(TAG, "read_register_24_ reg=0x%02X: value=0x%06" PRIX32, reg, value);
return value;
}

View File

@ -4,6 +4,8 @@
#include "esphome/components/spi/spi.h"
#include "esphome/core/component.h"
#include <cinttypes>
namespace esphome {
namespace max31856 {

View File

@ -10,6 +10,7 @@ CONF_STORE_IN_EEPROM = "store_in_eeprom"
mcp4728_ns = cg.esphome_ns.namespace("mcp4728")
MCP4728Component = mcp4728_ns.class_("MCP4728Component", cg.Component, i2c.I2CDevice)
CONF_MCP4728_ID = "mcp4728_id"
CONFIG_SCHEMA = (
cv.Schema(

View File

@ -1,4 +1,4 @@
#include "mcp4728_output.h"
#include "mcp4728.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@ -110,12 +110,5 @@ void MCP4728Component::select_gain_(MCP4728ChannelIdx channel, MCP4728Gain gain)
this->update_ = true;
}
void MCP4728Channel::write_state(float state) {
const uint16_t max_duty = 4095;
const float duty_rounded = roundf(state * max_duty);
auto duty = static_cast<uint16_t>(duty_rounded);
this->parent_->set_channel_value_(this->channel_, duty);
}
} // namespace mcp4728
} // namespace esphome

View File

@ -1,7 +1,6 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/output/float_output.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
@ -64,28 +63,5 @@ class MCP4728Component : public Component, public i2c::I2CDevice {
bool update_ = false;
};
class MCP4728Channel : public output::FloatOutput {
public:
MCP4728Channel(MCP4728Component *parent, MCP4728ChannelIdx channel, MCP4728Vref vref, MCP4728Gain gain,
MCP4728PwrDown pwrdown)
: parent_(parent), channel_(channel), vref_(vref), gain_(gain), pwrdown_(pwrdown) {
// update VREF
parent->select_vref_(channel, vref_);
// update PD
parent->select_power_down_(channel, pwrdown_);
// update GAIN
parent->select_gain_(channel, gain_);
}
protected:
void write_state(float state) override;
MCP4728Component *parent_;
MCP4728ChannelIdx channel_;
MCP4728Vref vref_;
MCP4728Gain gain_;
MCP4728PwrDown pwrdown_;
};
} // namespace mcp4728
} // namespace esphome

View File

@ -2,12 +2,11 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import output
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_GAIN
from . import MCP4728Component, mcp4728_ns
from .. import MCP4728Component, CONF_MCP4728_ID, mcp4728_ns
DEPENDENCIES = ["mcp4728"]
MCP4728Channel = mcp4728_ns.class_("MCP4728Channel", output.FloatOutput)
CONF_MCP4728_ID = "mcp4728_id"
CONF_VREF = "vref"
CONF_POWER_DOWN = "power_down"

View File

@ -0,0 +1,17 @@
#include "mcp4728_output.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace mcp4728 {
void MCP4728Channel::write_state(float state) {
const uint16_t max_duty = 4095;
const float duty_rounded = roundf(state * max_duty);
auto duty = static_cast<uint16_t>(duty_rounded);
this->parent_->set_channel_value_(this->channel_, duty);
}
} // namespace mcp4728
} // namespace esphome

View File

@ -0,0 +1,32 @@
#pragma once
#include "../mcp4728.h"
#include "esphome/core/component.h"
#include "esphome/components/output/float_output.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace mcp4728 {
class MCP4728Channel : public output::FloatOutput {
public:
MCP4728Channel(MCP4728Component *parent, MCP4728ChannelIdx channel, MCP4728Vref vref, MCP4728Gain gain,
MCP4728PwrDown pwrdown)
: parent_(parent), channel_(channel) {
// update VREF
parent->select_vref_(channel, vref);
// update PD
parent->select_power_down_(channel, pwrdown);
// update GAIN
parent->select_gain_(channel, gain);
}
protected:
void write_state(float state) override;
MCP4728Component *parent_;
MCP4728ChannelIdx channel_;
};
} // namespace mcp4728
} // namespace esphome

View File

@ -88,7 +88,7 @@ async def to_code(config):
add_idf_component(
name="mdns",
repo="https://github.com/espressif/esp-protocols.git",
ref="mdns-v1.2.0",
ref="mdns-v1.2.2",
path="components/mdns",
)

View File

@ -0,0 +1,69 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import uart
from esphome.const import (
CONF_ID,
)
CODEOWNERS = ["@jorre05"]
DEPENDENCIES = ["uart"]
CONF_MICRONOVA_ID = "micronova_id"
CONF_ENABLE_RX_PIN = "enable_rx_pin"
CONF_MEMORY_LOCATION = "memory_location"
CONF_MEMORY_ADDRESS = "memory_address"
micronova_ns = cg.esphome_ns.namespace("micronova")
MicroNovaFunctions = micronova_ns.enum("MicroNovaFunctions", is_class=True)
MICRONOVA_FUNCTIONS_ENUM = {
"STOVE_FUNCTION_SWITCH": MicroNovaFunctions.STOVE_FUNCTION_SWITCH,
"STOVE_FUNCTION_ROOM_TEMPERATURE": MicroNovaFunctions.STOVE_FUNCTION_ROOM_TEMPERATURE,
"STOVE_FUNCTION_THERMOSTAT_TEMPERATURE": MicroNovaFunctions.STOVE_FUNCTION_THERMOSTAT_TEMPERATURE,
"STOVE_FUNCTION_FUMES_TEMPERATURE": MicroNovaFunctions.STOVE_FUNCTION_FUMES_TEMPERATURE,
"STOVE_FUNCTION_STOVE_POWER": MicroNovaFunctions.STOVE_FUNCTION_STOVE_POWER,
"STOVE_FUNCTION_FAN_SPEED": MicroNovaFunctions.STOVE_FUNCTION_FAN_SPEED,
"STOVE_FUNCTION_STOVE_STATE": MicroNovaFunctions.STOVE_FUNCTION_STOVE_STATE,
"STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR": MicroNovaFunctions.STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR,
"STOVE_FUNCTION_WATER_TEMPERATURE": MicroNovaFunctions.STOVE_FUNCTION_WATER_TEMPERATURE,
"STOVE_FUNCTION_WATER_PRESSURE": MicroNovaFunctions.STOVE_FUNCTION_WATER_PRESSURE,
"STOVE_FUNCTION_POWER_LEVEL": MicroNovaFunctions.STOVE_FUNCTION_POWER_LEVEL,
"STOVE_FUNCTION_CUSTOM": MicroNovaFunctions.STOVE_FUNCTION_CUSTOM,
}
MicroNova = micronova_ns.class_("MicroNova", cg.PollingComponent, uart.UARTDevice)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(MicroNova),
cv.Required(CONF_ENABLE_RX_PIN): pins.gpio_output_pin_schema,
}
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.polling_component_schema("60s"))
)
def MICRONOVA_LISTENER_SCHEMA(default_memory_location, default_memory_address):
return cv.Schema(
{
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
cv.Optional(
CONF_MEMORY_LOCATION, default=default_memory_location
): cv.hex_int_range(),
cv.Optional(
CONF_MEMORY_ADDRESS, default=default_memory_address
): cv.hex_int_range(),
}
)
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)
enable_rx_pin = await cg.gpio_pin_expression(config[CONF_ENABLE_RX_PIN])
cg.add(var.set_enable_rx_pin(enable_rx_pin))

View File

@ -0,0 +1,44 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import button
from .. import (
MicroNova,
MicroNovaFunctions,
CONF_MICRONOVA_ID,
CONF_MEMORY_LOCATION,
CONF_MEMORY_ADDRESS,
MICRONOVA_LISTENER_SCHEMA,
micronova_ns,
)
MicroNovaButton = micronova_ns.class_("MicroNovaButton", button.Button, cg.Component)
CONF_CUSTOM_BUTTON = "custom_button"
CONF_MEMORY_DATA = "memory_data"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
cv.Optional(CONF_CUSTOM_BUTTON): button.button_schema(
MicroNovaButton,
)
.extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0xA0, default_memory_address=0x7D
)
)
.extend({cv.Required(CONF_MEMORY_DATA): cv.hex_int_range()}),
}
)
async def to_code(config):
mv = await cg.get_variable(config[CONF_MICRONOVA_ID])
if custom_button_config := config.get(CONF_CUSTOM_BUTTON):
bt = await button.new_button(custom_button_config, mv)
cg.add(bt.set_memory_location(custom_button_config.get(CONF_MEMORY_LOCATION)))
cg.add(bt.set_memory_address(custom_button_config.get(CONF_MEMORY_ADDRESS)))
cg.add(bt.set_memory_data(custom_button_config[CONF_MEMORY_DATA]))
cg.add(bt.set_function(MicroNovaFunctions.STOVE_FUNCTION_CUSTOM))

View File

@ -0,0 +1,18 @@
#include "micronova_button.h"
namespace esphome {
namespace micronova {
void MicroNovaButton::press_action() {
switch (this->get_function()) {
case MicroNovaFunctions::STOVE_FUNCTION_CUSTOM:
this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_);
break;
default:
break;
}
this->micronova_->update();
}
} // namespace micronova
} // namespace esphome

View File

@ -0,0 +1,23 @@
#pragma once
#include "esphome/components/micronova/micronova.h"
#include "esphome/core/component.h"
#include "esphome/components/button/button.h"
namespace esphome {
namespace micronova {
class MicroNovaButton : public Component, public button::Button, public MicroNovaButtonListener {
public:
MicroNovaButton(MicroNova *m) : MicroNovaButtonListener(m) {}
void dump_config() override { LOG_BUTTON("", "Micronova button", this); }
void set_memory_data(uint8_t f) { this->memory_data_ = f; }
uint8_t get_memory_data() { return this->memory_data_; }
protected:
void press_action() override;
};
} // namespace micronova
} // namespace esphome

View File

@ -0,0 +1,148 @@
#include "micronova.h"
#include "esphome/core/log.h"
namespace esphome {
namespace micronova {
void MicroNova::setup() {
if (this->enable_rx_pin_ != nullptr) {
this->enable_rx_pin_->setup();
this->enable_rx_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->enable_rx_pin_->digital_write(false);
}
this->current_transmission_.request_transmission_time = millis();
this->current_transmission_.memory_location = 0;
this->current_transmission_.memory_address = 0;
this->current_transmission_.reply_pending = false;
this->current_transmission_.initiating_listener = nullptr;
}
void MicroNova::dump_config() {
ESP_LOGCONFIG(TAG, "MicroNova:");
if (this->enable_rx_pin_ != nullptr) {
LOG_PIN(" Enable RX Pin: ", this->enable_rx_pin_);
}
for (auto &mv_sensor : this->micronova_listeners_) {
mv_sensor->dump_config();
ESP_LOGCONFIG(TAG, " sensor location:%02X, address:%02X", mv_sensor->get_memory_location(),
mv_sensor->get_memory_address());
}
}
void MicroNova::update() {
ESP_LOGD(TAG, "Schedule sensor update");
for (auto &mv_listener : this->micronova_listeners_) {
mv_listener->set_needs_update(true);
}
}
void MicroNova::loop() {
// Only read one sensor that needs update per loop
// If STOVE_REPLY_DELAY time has passed since last loop()
// check for a reply from the stove
if ((this->current_transmission_.reply_pending) &&
(millis() - this->current_transmission_.request_transmission_time > STOVE_REPLY_DELAY)) {
int stove_reply_value = this->read_stove_reply();
if (this->current_transmission_.initiating_listener != nullptr) {
this->current_transmission_.initiating_listener->process_value_from_stove(stove_reply_value);
this->current_transmission_.initiating_listener = nullptr;
}
this->current_transmission_.reply_pending = false;
return;
} else if (!this->current_transmission_.reply_pending) {
for (auto &mv_listener : this->micronova_listeners_) {
if (mv_listener->get_needs_update()) {
mv_listener->set_needs_update(false);
this->current_transmission_.initiating_listener = mv_listener;
mv_listener->request_value_from_stove();
return;
}
}
}
}
void MicroNova::request_address(uint8_t location, uint8_t address, MicroNovaSensorListener *listener) {
uint8_t write_data[2] = {0, 0};
uint8_t trash_rx;
if (this->reply_pending_mutex_.try_lock()) {
// clear rx buffer.
// Stove hickups may cause late replies in the rx
while (this->available()) {
this->read_byte(&trash_rx);
ESP_LOGW(TAG, "Reading excess byte 0x%02X", trash_rx);
}
write_data[0] = location;
write_data[1] = address;
ESP_LOGV(TAG, "Request from stove [%02X,%02X]", write_data[0], write_data[1]);
this->enable_rx_pin_->digital_write(true);
this->write_array(write_data, 2);
this->flush();
this->enable_rx_pin_->digital_write(false);
this->current_transmission_.request_transmission_time = millis();
this->current_transmission_.memory_location = location;
this->current_transmission_.memory_address = address;
this->current_transmission_.reply_pending = true;
this->current_transmission_.initiating_listener = listener;
} else {
ESP_LOGE(TAG, "Reply is pending, skipping read request");
}
}
int MicroNova::read_stove_reply() {
uint8_t reply_data[2] = {0, 0};
uint8_t checksum = 0;
// assert enable_rx_pin is false
this->read_array(reply_data, 2);
this->reply_pending_mutex_.unlock();
ESP_LOGV(TAG, "Reply from stove [%02X,%02X]", reply_data[0], reply_data[1]);
checksum = ((uint16_t) this->current_transmission_.memory_location +
(uint16_t) this->current_transmission_.memory_address + (uint16_t) reply_data[1]) &
0xFF;
if (reply_data[0] != checksum) {
ESP_LOGE(TAG, "Checksum missmatch! From [0x%02X:0x%02X] received [0x%02X,0x%02X]. Expected 0x%02X, got 0x%02X",
this->current_transmission_.memory_location, this->current_transmission_.memory_address, reply_data[0],
reply_data[1], checksum, reply_data[0]);
return -1;
}
return ((int) reply_data[1]);
}
void MicroNova::write_address(uint8_t location, uint8_t address, uint8_t data) {
uint8_t write_data[4] = {0, 0, 0, 0};
uint16_t checksum = 0;
if (this->reply_pending_mutex_.try_lock()) {
write_data[0] = location;
write_data[1] = address;
write_data[2] = data;
checksum = ((uint16_t) write_data[0] + (uint16_t) write_data[1] + (uint16_t) write_data[2]) & 0xFF;
write_data[3] = checksum;
ESP_LOGV(TAG, "Write 4 bytes [%02X,%02X,%02X,%02X]", write_data[0], write_data[1], write_data[2], write_data[3]);
this->enable_rx_pin_->digital_write(true);
this->write_array(write_data, 4);
this->flush();
this->enable_rx_pin_->digital_write(false);
this->current_transmission_.request_transmission_time = millis();
this->current_transmission_.memory_location = location;
this->current_transmission_.memory_address = address;
this->current_transmission_.reply_pending = true;
this->current_transmission_.initiating_listener = nullptr;
} else {
ESP_LOGE(TAG, "Reply is pending, skipping write");
}
}
} // namespace micronova
} // namespace esphome

View File

@ -0,0 +1,164 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/log.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include <vector>
namespace esphome {
namespace micronova {
static const char *const TAG = "micronova";
static const int STOVE_REPLY_DELAY = 60;
static const std::string STOVE_STATES[11] = {"Off",
"Start",
"Pellets loading",
"Ignition",
"Working",
"Brazier Cleaning",
"Final Cleaning",
"Standby",
"No pellets alarm",
"No ignition alarm",
"Undefined alarm"};
enum class MicroNovaFunctions {
STOVE_FUNCTION_VOID = 0,
STOVE_FUNCTION_SWITCH = 1,
STOVE_FUNCTION_ROOM_TEMPERATURE = 2,
STOVE_FUNCTION_THERMOSTAT_TEMPERATURE = 3,
STOVE_FUNCTION_FUMES_TEMPERATURE = 4,
STOVE_FUNCTION_STOVE_POWER = 5,
STOVE_FUNCTION_FAN_SPEED = 6,
STOVE_FUNCTION_STOVE_STATE = 7,
STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR = 8,
STOVE_FUNCTION_WATER_TEMPERATURE = 9,
STOVE_FUNCTION_WATER_PRESSURE = 10,
STOVE_FUNCTION_POWER_LEVEL = 11,
STOVE_FUNCTION_CUSTOM = 12
};
class MicroNova;
//////////////////////////////////////////////////////////////////////
// Interface classes.
class MicroNovaBaseListener {
public:
MicroNovaBaseListener() {}
MicroNovaBaseListener(MicroNova *m) { this->micronova_ = m; }
virtual void dump_config();
void set_micronova_object(MicroNova *m) { this->micronova_ = m; }
void set_function(MicroNovaFunctions f) { this->function_ = f; }
MicroNovaFunctions get_function() { return this->function_; }
void set_memory_location(uint8_t l) { this->memory_location_ = l; }
uint8_t get_memory_location() { return this->memory_location_; }
void set_memory_address(uint8_t a) { this->memory_address_ = a; }
uint8_t get_memory_address() { return this->memory_address_; }
protected:
MicroNova *micronova_{nullptr};
MicroNovaFunctions function_ = MicroNovaFunctions::STOVE_FUNCTION_VOID;
uint8_t memory_location_ = 0;
uint8_t memory_address_ = 0;
};
class MicroNovaSensorListener : public MicroNovaBaseListener {
public:
MicroNovaSensorListener() {}
MicroNovaSensorListener(MicroNova *m) : MicroNovaBaseListener(m) {}
virtual void request_value_from_stove() = 0;
virtual void process_value_from_stove(int value_from_stove) = 0;
void set_needs_update(bool u) { this->needs_update_ = u; }
bool get_needs_update() { return this->needs_update_; }
protected:
bool needs_update_ = false;
};
class MicroNovaNumberListener : public MicroNovaBaseListener {
public:
MicroNovaNumberListener(MicroNova *m) : MicroNovaBaseListener(m) {}
virtual void request_value_from_stove() = 0;
virtual void process_value_from_stove(int value_from_stove) = 0;
void set_needs_update(bool u) { this->needs_update_ = u; }
bool get_needs_update() { return this->needs_update_; }
protected:
bool needs_update_ = false;
};
class MicroNovaSwitchListener : public MicroNovaBaseListener {
public:
MicroNovaSwitchListener(MicroNova *m) : MicroNovaBaseListener(m) {}
virtual void set_stove_state(bool v) = 0;
virtual bool get_stove_state() = 0;
protected:
uint8_t memory_data_on_ = 0;
uint8_t memory_data_off_ = 0;
};
class MicroNovaButtonListener : public MicroNovaBaseListener {
public:
MicroNovaButtonListener(MicroNova *m) : MicroNovaBaseListener(m) {}
protected:
uint8_t memory_data_ = 0;
};
/////////////////////////////////////////////////////////////////////
// Main component class
class MicroNova : public PollingComponent, public uart::UARTDevice {
public:
MicroNova() {}
void setup() override;
void loop() override;
void update() override;
void dump_config() override;
void register_micronova_listener(MicroNovaSensorListener *l) { this->micronova_listeners_.push_back(l); }
void request_address(uint8_t location, uint8_t address, MicroNovaSensorListener *listener);
void write_address(uint8_t location, uint8_t address, uint8_t data);
int read_stove_reply();
void set_enable_rx_pin(GPIOPin *enable_rx_pin) { this->enable_rx_pin_ = enable_rx_pin; }
void set_current_stove_state(uint8_t s) { this->current_stove_state_ = s; }
uint8_t get_current_stove_state() { return this->current_stove_state_; }
void set_stove(MicroNovaSwitchListener *s) { this->stove_switch_ = s; }
MicroNovaSwitchListener *get_stove_switch() { return this->stove_switch_; }
protected:
uint8_t current_stove_state_ = 0;
GPIOPin *enable_rx_pin_{nullptr};
struct MicroNovaSerialTransmission {
uint32_t request_transmission_time;
uint8_t memory_location;
uint8_t memory_address;
bool reply_pending;
MicroNovaSensorListener *initiating_listener;
};
Mutex reply_pending_mutex_;
MicroNovaSerialTransmission current_transmission_;
std::vector<MicroNovaSensorListener *> micronova_listeners_{};
MicroNovaSwitchListener *stove_switch_{nullptr};
};
} // namespace micronova
} // namespace esphome

View File

@ -0,0 +1,110 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import number
from esphome.const import (
DEVICE_CLASS_TEMPERATURE,
UNIT_CELSIUS,
CONF_STEP,
)
from .. import (
MicroNova,
MicroNovaFunctions,
CONF_MICRONOVA_ID,
CONF_MEMORY_LOCATION,
CONF_MEMORY_ADDRESS,
MICRONOVA_LISTENER_SCHEMA,
micronova_ns,
)
ICON_FLASH = "mdi:flash"
CONF_THERMOSTAT_TEMPERATURE = "thermostat_temperature"
CONF_POWER_LEVEL = "power_level"
CONF_MEMORY_WRITE_LOCATION = "memory_write_location"
MicroNovaNumber = micronova_ns.class_("MicroNovaNumber", number.Number, cg.Component)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
cv.Optional(CONF_THERMOSTAT_TEMPERATURE): number.number_schema(
MicroNovaNumber,
unit_of_measurement=UNIT_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
)
.extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x20, default_memory_address=0x7D
)
)
.extend(
{
cv.Optional(
CONF_MEMORY_WRITE_LOCATION, default=0xA0
): cv.hex_int_range(),
cv.Optional(CONF_STEP, default=1.0): cv.float_range(min=0.1, max=10.0),
}
),
cv.Optional(CONF_POWER_LEVEL): number.number_schema(
MicroNovaNumber,
icon=ICON_FLASH,
)
.extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x20, default_memory_address=0x7F
)
)
.extend(
{cv.Optional(CONF_MEMORY_WRITE_LOCATION, default=0xA0): cv.hex_int_range()}
),
}
)
async def to_code(config):
mv = await cg.get_variable(config[CONF_MICRONOVA_ID])
if thermostat_temperature_config := config.get(CONF_THERMOSTAT_TEMPERATURE):
numb = await number.new_number(
thermostat_temperature_config,
min_value=0,
max_value=40,
step=thermostat_temperature_config.get(CONF_STEP),
)
cg.add(numb.set_micronova_object(mv))
cg.add(mv.register_micronova_listener(numb))
cg.add(
numb.set_memory_location(
thermostat_temperature_config[CONF_MEMORY_LOCATION]
)
)
cg.add(
numb.set_memory_address(thermostat_temperature_config[CONF_MEMORY_ADDRESS])
)
cg.add(
numb.set_memory_write_location(
thermostat_temperature_config.get(CONF_MEMORY_WRITE_LOCATION)
)
)
cg.add(
numb.set_function(MicroNovaFunctions.STOVE_FUNCTION_THERMOSTAT_TEMPERATURE)
)
if power_level_config := config.get(CONF_POWER_LEVEL):
numb = await number.new_number(
power_level_config,
min_value=1,
max_value=5,
step=1,
)
cg.add(numb.set_micronova_object(mv))
cg.add(mv.register_micronova_listener(numb))
cg.add(numb.set_memory_location(power_level_config[CONF_MEMORY_LOCATION]))
cg.add(numb.set_memory_address(power_level_config[CONF_MEMORY_ADDRESS]))
cg.add(
numb.set_memory_write_location(
power_level_config.get(CONF_MEMORY_WRITE_LOCATION)
)
)
cg.add(numb.set_function(MicroNovaFunctions.STOVE_FUNCTION_POWER_LEVEL))

View File

@ -0,0 +1,45 @@
#include "micronova_number.h"
namespace esphome {
namespace micronova {
void MicroNovaNumber::process_value_from_stove(int value_from_stove) {
float new_sensor_value = 0;
if (value_from_stove == -1) {
this->publish_state(NAN);
return;
}
switch (this->get_function()) {
case MicroNovaFunctions::STOVE_FUNCTION_THERMOSTAT_TEMPERATURE:
new_sensor_value = ((float) value_from_stove) * this->traits.get_step();
break;
case MicroNovaFunctions::STOVE_FUNCTION_POWER_LEVEL:
new_sensor_value = (float) value_from_stove;
break;
default:
break;
}
this->publish_state(new_sensor_value);
}
void MicroNovaNumber::control(float value) {
uint8_t new_number = 0;
switch (this->get_function()) {
case MicroNovaFunctions::STOVE_FUNCTION_THERMOSTAT_TEMPERATURE:
new_number = (uint8_t) (value / this->traits.get_step());
break;
case MicroNovaFunctions::STOVE_FUNCTION_POWER_LEVEL:
new_number = (uint8_t) value;
break;
default:
break;
}
this->micronova_->write_address(this->memory_write_location_, this->memory_address_, new_number);
this->micronova_->update();
}
} // namespace micronova
} // namespace esphome

View File

@ -0,0 +1,28 @@
#pragma once
#include "esphome/components/micronova/micronova.h"
#include "esphome/components/number/number.h"
namespace esphome {
namespace micronova {
class MicroNovaNumber : public number::Number, public MicroNovaSensorListener {
public:
MicroNovaNumber() {}
MicroNovaNumber(MicroNova *m) : MicroNovaSensorListener(m) {}
void dump_config() override { LOG_NUMBER("", "Micronova number", this); }
void control(float value) override;
void request_value_from_stove() override {
this->micronova_->request_address(this->memory_location_, this->memory_address_, this);
}
void process_value_from_stove(int value_from_stove) override;
void set_memory_write_location(uint8_t l) { this->memory_write_location_ = l; }
uint8_t get_memory_write_location() { return this->memory_write_location_; }
protected:
uint8_t memory_write_location_ = 0;
};
} // namespace micronova
} // namespace esphome

View File

@ -0,0 +1,172 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_REVOLUTIONS_PER_MINUTE,
)
from .. import (
MicroNova,
MicroNovaFunctions,
CONF_MICRONOVA_ID,
CONF_MEMORY_LOCATION,
CONF_MEMORY_ADDRESS,
MICRONOVA_LISTENER_SCHEMA,
micronova_ns,
)
UNIT_BAR = "bar"
MicroNovaSensor = micronova_ns.class_("MicroNovaSensor", sensor.Sensor, cg.Component)
CONF_ROOM_TEMPERATURE = "room_temperature"
CONF_FUMES_TEMPERATURE = "fumes_temperature"
CONF_STOVE_POWER = "stove_power"
CONF_FAN_SPEED = "fan_speed"
CONF_WATER_TEMPERATURE = "water_temperature"
CONF_WATER_PRESSURE = "water_pressure"
CONF_MEMORY_ADDRESS_SENSOR = "memory_address_sensor"
CONF_FAN_RPM_OFFSET = "fan_rpm_offset"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
cv.Optional(CONF_ROOM_TEMPERATURE): sensor.sensor_schema(
MicroNovaSensor,
unit_of_measurement=UNIT_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=1,
).extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x00, default_memory_address=0x01
)
),
cv.Optional(CONF_FUMES_TEMPERATURE): sensor.sensor_schema(
MicroNovaSensor,
unit_of_measurement=UNIT_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=1,
).extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x00, default_memory_address=0x5A
)
),
cv.Optional(CONF_STOVE_POWER): sensor.sensor_schema(
MicroNovaSensor,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=0,
).extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x00, default_memory_address=0x34
)
),
cv.Optional(CONF_FAN_SPEED): sensor.sensor_schema(
MicroNovaSensor,
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE,
)
.extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x00, default_memory_address=0x37
)
)
.extend(
{cv.Optional(CONF_FAN_RPM_OFFSET, default=0): cv.int_range(min=0, max=255)}
),
cv.Optional(CONF_WATER_TEMPERATURE): sensor.sensor_schema(
MicroNovaSensor,
unit_of_measurement=UNIT_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=1,
).extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x00, default_memory_address=0x3B
)
),
cv.Optional(CONF_WATER_PRESSURE): sensor.sensor_schema(
MicroNovaSensor,
unit_of_measurement=UNIT_BAR,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
accuracy_decimals=1,
).extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x00, default_memory_address=0x3C
)
),
cv.Optional(CONF_MEMORY_ADDRESS_SENSOR): sensor.sensor_schema(
MicroNovaSensor,
).extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x00, default_memory_address=0x00
)
),
}
)
async def to_code(config):
mv = await cg.get_variable(config[CONF_MICRONOVA_ID])
if room_temperature_config := config.get(CONF_ROOM_TEMPERATURE):
sens = await sensor.new_sensor(room_temperature_config, mv)
cg.add(mv.register_micronova_listener(sens))
cg.add(sens.set_memory_location(room_temperature_config[CONF_MEMORY_LOCATION]))
cg.add(sens.set_memory_address(room_temperature_config[CONF_MEMORY_ADDRESS]))
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_ROOM_TEMPERATURE))
if fumes_temperature_config := config.get(CONF_FUMES_TEMPERATURE):
sens = await sensor.new_sensor(fumes_temperature_config, mv)
cg.add(mv.register_micronova_listener(sens))
cg.add(sens.set_memory_location(fumes_temperature_config[CONF_MEMORY_LOCATION]))
cg.add(sens.set_memory_address(fumes_temperature_config[CONF_MEMORY_ADDRESS]))
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_FUMES_TEMPERATURE))
if stove_power_config := config.get(CONF_STOVE_POWER):
sens = await sensor.new_sensor(stove_power_config, mv)
cg.add(mv.register_micronova_listener(sens))
cg.add(sens.set_memory_location(stove_power_config[CONF_MEMORY_LOCATION]))
cg.add(sens.set_memory_address(stove_power_config[CONF_MEMORY_ADDRESS]))
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_STOVE_POWER))
if fan_speed_config := config.get(CONF_FAN_SPEED):
sens = await sensor.new_sensor(fan_speed_config, mv)
cg.add(mv.register_micronova_listener(sens))
cg.add(sens.set_memory_location(fan_speed_config[CONF_MEMORY_LOCATION]))
cg.add(sens.set_memory_address(fan_speed_config[CONF_MEMORY_ADDRESS]))
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_FAN_SPEED))
cg.add(sens.set_fan_speed_offset(fan_speed_config[CONF_FAN_RPM_OFFSET]))
if memory_address_sensor_config := config.get(CONF_MEMORY_ADDRESS_SENSOR):
sens = await sensor.new_sensor(memory_address_sensor_config, mv)
cg.add(mv.register_micronova_listener(sens))
cg.add(
sens.set_memory_location(memory_address_sensor_config[CONF_MEMORY_LOCATION])
)
cg.add(
sens.set_memory_address(memory_address_sensor_config[CONF_MEMORY_ADDRESS])
)
cg.add(
sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR)
)
if water_temperature_config := config.get(CONF_WATER_TEMPERATURE):
sens = await sensor.new_sensor(water_temperature_config, mv)
cg.add(mv.register_micronova_listener(sens))
cg.add(sens.set_memory_location(water_temperature_config[CONF_MEMORY_LOCATION]))
cg.add(sens.set_memory_address(water_temperature_config[CONF_MEMORY_ADDRESS]))
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_WATER_TEMPERATURE))
if water_pressure_config := config.get(CONF_WATER_PRESSURE):
sens = await sensor.new_sensor(water_pressure_config, mv)
cg.add(mv.register_micronova_listener(sens))
cg.add(sens.set_memory_location(water_pressure_config[CONF_MEMORY_LOCATION]))
cg.add(sens.set_memory_address(water_pressure_config[CONF_MEMORY_ADDRESS]))
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_WATER_PRESSURE))

View File

@ -0,0 +1,35 @@
#include "micronova_sensor.h"
namespace esphome {
namespace micronova {
void MicroNovaSensor::process_value_from_stove(int value_from_stove) {
if (value_from_stove == -1) {
this->publish_state(NAN);
return;
}
float new_sensor_value = (float) value_from_stove;
switch (this->get_function()) {
case MicroNovaFunctions::STOVE_FUNCTION_ROOM_TEMPERATURE:
new_sensor_value = new_sensor_value / 2;
break;
case MicroNovaFunctions::STOVE_FUNCTION_THERMOSTAT_TEMPERATURE:
break;
case MicroNovaFunctions::STOVE_FUNCTION_FAN_SPEED:
new_sensor_value = new_sensor_value == 0 ? 0 : (new_sensor_value * 10) + this->fan_speed_offset_;
break;
case MicroNovaFunctions::STOVE_FUNCTION_WATER_TEMPERATURE:
new_sensor_value = new_sensor_value / 2;
break;
case MicroNovaFunctions::STOVE_FUNCTION_WATER_PRESSURE:
new_sensor_value = new_sensor_value / 10;
break;
default:
break;
}
this->publish_state(new_sensor_value);
}
} // namespace micronova
} // namespace esphome

View File

@ -0,0 +1,27 @@
#pragma once
#include "esphome/components/micronova/micronova.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace micronova {
class MicroNovaSensor : public sensor::Sensor, public MicroNovaSensorListener {
public:
MicroNovaSensor(MicroNova *m) : MicroNovaSensorListener(m) {}
void dump_config() override { LOG_SENSOR("", "Micronova sensor", this); }
void request_value_from_stove() override {
this->micronova_->request_address(this->memory_location_, this->memory_address_, this);
}
void process_value_from_stove(int value_from_stove) override;
void set_fan_speed_offset(uint8_t f) { this->fan_speed_offset_ = f; }
uint8_t get_set_fan_speed_offset() { return this->fan_speed_offset_; }
protected:
int fan_speed_offset_ = 0;
};
} // namespace micronova
} // namespace esphome

View File

@ -0,0 +1,56 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import switch
from esphome.const import (
ICON_POWER,
)
from .. import (
MicroNova,
MicroNovaFunctions,
CONF_MICRONOVA_ID,
CONF_MEMORY_LOCATION,
CONF_MEMORY_ADDRESS,
MICRONOVA_LISTENER_SCHEMA,
micronova_ns,
)
CONF_STOVE = "stove"
CONF_MEMORY_DATA_ON = "memory_data_on"
CONF_MEMORY_DATA_OFF = "memory_data_off"
MicroNovaSwitch = micronova_ns.class_("MicroNovaSwitch", switch.Switch, cg.Component)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
cv.Optional(CONF_STOVE): switch.switch_schema(
MicroNovaSwitch,
icon=ICON_POWER,
)
.extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x80, default_memory_address=0x21
)
)
.extend(
{
cv.Optional(CONF_MEMORY_DATA_OFF, default=0x06): cv.hex_int_range(),
cv.Optional(CONF_MEMORY_DATA_ON, default=0x01): cv.hex_int_range(),
}
),
}
)
async def to_code(config):
mv = await cg.get_variable(config[CONF_MICRONOVA_ID])
if stove_config := config.get(CONF_STOVE):
sw = await switch.new_switch(stove_config, mv)
cg.add(mv.set_stove(sw))
cg.add(sw.set_memory_location(stove_config[CONF_MEMORY_LOCATION]))
cg.add(sw.set_memory_address(stove_config[CONF_MEMORY_ADDRESS]))
cg.add(sw.set_memory_data_on(stove_config[CONF_MEMORY_DATA_ON]))
cg.add(sw.set_memory_data_off(stove_config[CONF_MEMORY_DATA_OFF]))
cg.add(sw.set_function(MicroNovaFunctions.STOVE_FUNCTION_SWITCH))

View File

@ -0,0 +1,33 @@
#include "micronova_switch.h"
namespace esphome {
namespace micronova {
void MicroNovaSwitch::write_state(bool state) {
switch (this->get_function()) {
case MicroNovaFunctions::STOVE_FUNCTION_SWITCH:
if (state) {
// Only send power-on when current state is Off
if (this->micronova_->get_current_stove_state() == 0) {
this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_on_);
this->publish_state(true);
} else
ESP_LOGW(TAG, "Unable to turn stove on, invalid state: %d", micronova_->get_current_stove_state());
} else {
// don't send power-off when status is Off or Final cleaning
if (this->micronova_->get_current_stove_state() != 0 && micronova_->get_current_stove_state() != 6) {
this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_off_);
this->publish_state(false);
} else
ESP_LOGW(TAG, "Unable to turn stove off, invalid state: %d", micronova_->get_current_stove_state());
}
this->micronova_->update();
break;
default:
break;
}
}
} // namespace micronova
} // namespace esphome

View File

@ -0,0 +1,29 @@
#pragma once
#include "esphome/components/micronova/micronova.h"
#include "esphome/core/component.h"
#include "esphome/components/switch/switch.h"
namespace esphome {
namespace micronova {
class MicroNovaSwitch : public Component, public switch_::Switch, public MicroNovaSwitchListener {
public:
MicroNovaSwitch(MicroNova *m) : MicroNovaSwitchListener(m) {}
void dump_config() override { LOG_SWITCH("", "Micronova switch", this); }
void set_stove_state(bool v) override { this->publish_state(v); }
bool get_stove_state() override { return this->state; }
void set_memory_data_on(uint8_t f) { this->memory_data_on_ = f; }
uint8_t get_memory_data_on() { return this->memory_data_on_; }
void set_memory_data_off(uint8_t f) { this->memory_data_off_ = f; }
uint8_t get_memory_data_off() { return this->memory_data_off_; }
protected:
void write_state(bool state) override;
};
} // namespace micronova
} // namespace esphome

View File

@ -0,0 +1,43 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from .. import (
MicroNova,
MicroNovaFunctions,
CONF_MICRONOVA_ID,
CONF_MEMORY_LOCATION,
CONF_MEMORY_ADDRESS,
MICRONOVA_LISTENER_SCHEMA,
micronova_ns,
)
CONF_STOVE_STATE = "stove_state"
MicroNovaTextSensor = micronova_ns.class_(
"MicroNovaTextSensor", text_sensor.TextSensor, cg.Component
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
cv.Optional(CONF_STOVE_STATE): text_sensor.text_sensor_schema(
MicroNovaTextSensor
).extend(
MICRONOVA_LISTENER_SCHEMA(
default_memory_location=0x00, default_memory_address=0x21
)
),
}
)
async def to_code(config):
mv = await cg.get_variable(config[CONF_MICRONOVA_ID])
if stove_state_config := config.get(CONF_STOVE_STATE):
sens = await text_sensor.new_text_sensor(stove_state_config, mv)
cg.add(mv.register_micronova_listener(sens))
cg.add(sens.set_memory_location(stove_state_config[CONF_MEMORY_LOCATION]))
cg.add(sens.set_memory_address(stove_state_config[CONF_MEMORY_ADDRESS]))
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_STOVE_STATE))

View File

@ -0,0 +1,31 @@
#include "micronova_text_sensor.h"
namespace esphome {
namespace micronova {
void MicroNovaTextSensor::process_value_from_stove(int value_from_stove) {
if (value_from_stove == -1) {
this->publish_state("unknown");
return;
}
switch (this->get_function()) {
case MicroNovaFunctions::STOVE_FUNCTION_STOVE_STATE:
this->micronova_->set_current_stove_state(value_from_stove);
this->publish_state(STOVE_STATES[value_from_stove]);
// set the stove switch to on for any value but 0
if (value_from_stove != 0 && this->micronova_->get_stove_switch() != nullptr &&
!this->micronova_->get_stove_switch()->get_stove_state()) {
this->micronova_->get_stove_switch()->set_stove_state(true);
} else if (value_from_stove == 0 && this->micronova_->get_stove_switch() != nullptr &&
this->micronova_->get_stove_switch()->get_stove_state()) {
this->micronova_->get_stove_switch()->set_stove_state(false);
}
break;
default:
break;
}
}
} // namespace micronova
} // namespace esphome

View File

@ -0,0 +1,20 @@
#pragma once
#include "esphome/components/micronova/micronova.h"
#include "esphome/components/text_sensor/text_sensor.h"
namespace esphome {
namespace micronova {
class MicroNovaTextSensor : public text_sensor::TextSensor, public MicroNovaSensorListener {
public:
MicroNovaTextSensor(MicroNova *m) : MicroNovaSensorListener(m) {}
void dump_config() override { LOG_TEXT_SENSOR("", "Micronova text sensor", this); }
void request_value_from_stove() override {
this->micronova_->request_address(this->memory_location_, this->memory_address_, this);
}
void process_value_from_stove(int value_from_stove) override;
};
} // namespace micronova
} // namespace esphome

View File

@ -45,8 +45,8 @@ void MitsubishiClimate::transmit_state() {
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: %02X mode: %02X temp: %02X", this->target_temperature,
remote_state[5], remote_state[6], remote_state[7]);
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]);
// Checksum
for (int i = 0; i < 17; i++) {

View File

@ -2,6 +2,8 @@
#include "esphome/components/climate_ir/climate_ir.h"
#include <cinttypes>
namespace esphome {
namespace mitsubishi {

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