Merge branch 'dev' into features/dfrobot_sn0395/targets-tracking

This commit is contained in:
Robin Thoni 2024-07-31 08:40:48 +02:00 committed by GitHub
commit 86fd9b417d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
391 changed files with 12036 additions and 17389 deletions

View file

@ -1,7 +1,9 @@
{ {
"name": "ESPHome Dev", "name": "ESPHome Dev",
"image": "ghcr.io/esphome/esphome-lint:dev", "image": "ghcr.io/esphome/esphome-lint:dev",
"postCreateCommand": ["script/devcontainer-post-create"], "postCreateCommand": [
"script/devcontainer-post-create"
],
"containerEnv": { "containerEnv": {
"DEVCONTAINER": "1", "DEVCONTAINER": "1",
"PIP_BREAK_SYSTEM_PACKAGES": "1", "PIP_BREAK_SYSTEM_PACKAGES": "1",
@ -27,6 +29,9 @@
"extensions": [ "extensions": [
// python // python
"ms-python.python", "ms-python.python",
"ms-python.pylint",
"ms-python.flake8",
"ms-python.black-formatter",
"visualstudioexptteam.vscodeintellicode", "visualstudioexptteam.vscodeintellicode",
// yaml // yaml
"redhat.vscode-yaml", "redhat.vscode-yaml",
@ -38,9 +43,21 @@
"settings": { "settings": {
"python.languageServer": "Pylance", "python.languageServer": "Pylance",
"python.pythonPath": "/usr/bin/python3", "python.pythonPath": "/usr/bin/python3",
"python.linting.pylintEnabled": true, "pylint.args": [
"python.linting.enabled": true, "--rcfile=${workspaceFolder}/pyproject.toml"
"python.formatting.provider": "black", ],
"flake8.args": [
"--config=${workspaceFolder}/.flake8"
],
"black-formatter.args": [
"--config",
"${workspaceFolder}/pyproject.toml"
],
"[python]": {
// VS will say "Value is not accepted" before building the devcontainer, but the warning
// should go away after build is completed.
"editor.defaultFormatter": "ms-python.black-formatter"
},
"editor.formatOnPaste": false, "editor.formatOnPaste": false,
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.formatOnType": true, "editor.formatOnType": true,

View file

@ -46,7 +46,7 @@ runs:
- name: Build and push to ghcr by digest - name: Build and push to ghcr by digest
id: build-ghcr id: build-ghcr
uses: docker/build-push-action@v6.1.0 uses: docker/build-push-action@v6.5.0
with: with:
context: . context: .
file: ./docker/Dockerfile file: ./docker/Dockerfile
@ -69,7 +69,7 @@ runs:
- name: Build and push to dockerhub by digest - name: Build and push to dockerhub by digest
id: build-dockerhub id: build-dockerhub
uses: docker/build-push-action@v6.1.0 uses: docker/build-push-action@v6.5.0
with: with:
context: . context: .
file: ./docker/Dockerfile file: ./docker/Dockerfile

View file

@ -17,7 +17,7 @@ runs:
steps: steps:
- name: Set up Python ${{ inputs.python-version }} - name: Set up Python ${{ inputs.python-version }}
id: python id: python
uses: actions/setup-python@v5.1.0 uses: actions/setup-python@v5.1.1
with: with:
python-version: ${{ inputs.python-version }} python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment - name: Restore Python virtual environment

View file

@ -13,6 +13,13 @@ updates:
schedule: schedule:
interval: daily interval: daily
open-pull-requests-limit: 10 open-pull-requests-limit: 10
groups:
docker-actions:
applies-to: version-updates
patterns:
- "docker/setup-qemu-action"
- "docker/login-action"
- "docker/setup-buildx-action"
- package-ecosystem: github-actions - package-ecosystem: github-actions
directory: "/.github/actions/build-image" directory: "/.github/actions/build-image"
schedule: schedule:

View file

@ -46,9 +46,9 @@ jobs:
with: with:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.3.0 uses: docker/setup-buildx-action@v3.6.1
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3.0.0 uses: docker/setup-qemu-action@v3.2.0
- name: Set TAG - name: Set TAG
run: | run: |

View file

@ -248,72 +248,6 @@ jobs:
run: script/ci-suggest-changes run: script/ci-suggest-changes
if: always() if: always()
compile-tests-list:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
- 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.7
- 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
needs:
- common
- black
- ci-custom
- clang-format
- flake8
- pylint
- pytest
- pyupgrade
- compile-tests-list
- validate-tests
strategy:
fail-fast: false
max-parallel: 2
matrix:
file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run esphome compile ${{ matrix.file }}
run: |
. venv/bin/activate
esphome compile ${{ matrix.file }}
clang-tidy: clang-tidy:
name: ${{ matrix.name }} name: ${{ matrix.name }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -534,6 +468,8 @@ jobs:
- name: Compile config - name: Compile config
run: | run: |
. venv/bin/activate . venv/bin/activate
mkdir build_cache
export PLATFORMIO_BUILD_CACHE_DIR=$PWD/build_cache
for component in ${{ matrix.components }}; do for component in ${{ matrix.components }}; do
./script/test_build_components -e compile -c $component ./script/test_build_components -e compile -c $component
done done
@ -550,7 +486,6 @@ jobs:
- pylint - pylint
- pytest - pytest
- pyupgrade - pyupgrade
- compile-tests
- clang-tidy - clang-tidy
- list-components - list-components
- test-build-components - test-build-components

View file

@ -90,18 +90,18 @@ jobs:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.3.0 uses: docker/setup-buildx-action@v3.6.1
- name: Set up QEMU - name: Set up QEMU
if: matrix.platform != 'linux/amd64' if: matrix.platform != 'linux/amd64'
uses: docker/setup-qemu-action@v3.0.0 uses: docker/setup-qemu-action@v3.2.0
- name: Log in to docker hub - name: Log in to docker hub
uses: docker/login-action@v3.2.0 uses: docker/login-action@v3.3.0
with: with:
username: ${{ secrets.DOCKER_USER }} username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry - name: Log in to the GitHub container registry
uses: docker/login-action@v3.2.0 uses: docker/login-action@v3.3.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@ -141,7 +141,7 @@ jobs:
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
- name: Upload digests - name: Upload digests
uses: actions/upload-artifact@v4.3.3 uses: actions/upload-artifact@v4.3.4
with: with:
name: digests-${{ steps.sanitize.outputs.name }} name: digests-${{ steps.sanitize.outputs.name }}
path: /tmp/digests path: /tmp/digests
@ -177,24 +177,24 @@ jobs:
- uses: actions/checkout@v4.1.7 - uses: actions/checkout@v4.1.7
- name: Download digests - name: Download digests
uses: actions/download-artifact@v4.1.7 uses: actions/download-artifact@v4.1.8
with: with:
pattern: digests-* pattern: digests-*
path: /tmp/digests path: /tmp/digests
merge-multiple: true merge-multiple: true
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.3.0 uses: docker/setup-buildx-action@v3.6.1
- name: Log in to docker hub - name: Log in to docker hub
if: matrix.registry == 'dockerhub' if: matrix.registry == 'dockerhub'
uses: docker/login-action@v3.2.0 uses: docker/login-action@v3.3.0
with: with:
username: ${{ secrets.DOCKER_USER }} username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the GitHub container registry - name: Log in to the GitHub container registry
if: matrix.registry == 'ghcr' if: matrix.registry == 'ghcr'
uses: docker/login-action@v3.2.0 uses: docker/login-action@v3.3.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}

View file

@ -2,6 +2,15 @@
# See https://pre-commit.com for more information # See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks # See https://pre-commit.com/hooks.html for more hooks
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.5.4
hooks:
# Run the linter.
- id: ruff
args: [--fix]
# Run the formatter.
- id: ruff-format
- repo: https://github.com/psf/black-pre-commit-mirror - repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.4.2 rev: 24.4.2
hooks: hooks:

View file

@ -37,6 +37,7 @@ esphome/components/am43/sensor/* @buxtronix
esphome/components/analog_threshold/* @ianchi esphome/components/analog_threshold/* @ianchi
esphome/components/animation/* @syndlex esphome/components/animation/* @syndlex
esphome/components/anova/* @buxtronix esphome/components/anova/* @buxtronix
esphome/components/apds9306/* @aodrenah
esphome/components/api/* @OttoWinter esphome/components/api/* @OttoWinter
esphome/components/as5600/* @ammmze esphome/components/as5600/* @ammmze
esphome/components/as5600/sensor/* @ammmze esphome/components/as5600/sensor/* @ammmze
@ -214,8 +215,10 @@ esphome/components/lightwaverf/* @max246
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
esphome/components/lock/* @esphome/core esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core esphome/components/logger/* @esphome/core
esphome/components/ltr390/* @sjtrny esphome/components/ltr390/* @latonita @sjtrny
esphome/components/ltr_als_ps/* @latonita esphome/components/ltr_als_ps/* @latonita
esphome/components/lvgl/* @clydebarrow
esphome/components/m5stack_8angle/* @rnauber
esphome/components/matrix_keypad/* @ssieb esphome/components/matrix_keypad/* @ssieb
esphome/components/max31865/* @DAVe3283 esphome/components/max31865/* @DAVe3283
esphome/components/max44009/* @berfenger esphome/components/max44009/* @berfenger
@ -424,6 +427,7 @@ esphome/components/veml7700/* @latonita
esphome/components/version/* @esphome/core esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz esphome/components/voice_assistant/* @jesserockz
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54 esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
esphome/components/watchdog/* @oarcher
esphome/components/waveshare_epaper/* @clydebarrow esphome/components/waveshare_epaper/* @clydebarrow
esphome/components/web_server_base/* @OttoWinter esphome/components/web_server_base/* @OttoWinter
esphome/components/web_server_idf/* @dentra esphome/components/web_server_idf/* @dentra

View file

@ -34,28 +34,32 @@ RUN \
python3-wheel=0.38.4-2 \ python3-wheel=0.38.4-2 \
iputils-ping=3:20221126-1 \ iputils-ping=3:20221126-1 \
git=1:2.39.2-1.1 \ git=1:2.39.2-1.1 \
curl=7.88.1-10+deb12u5 \ curl=7.88.1-10+deb12u6 \
openssh-client=1:9.2p1-2+deb12u2 \ openssh-client=1:9.2p1-2+deb12u2 \
python3-cffi=1.15.1-5 \ python3-cffi=1.15.1-5 \
libcairo2=1.16.0-7 \ libcairo2=1.16.0-7 \
libmagic1=1:5.44-3 \ libmagic1=1:5.44-3 \
patch=2.7.6-7; \ patch=2.7.6-7 \
if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \ && ( \
( \
[ "$TARGETARCH$TARGETVARIANT" = "armv7" ] && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
build-essential=12.9 \ build-essential=12.9 \
python3-dev=3.11.2-1+b1 \ python3-dev=3.11.2-1+b1 \
zlib1g-dev=1:1.2.13.dfsg-1 \ zlib1g-dev=1:1.2.13.dfsg-1 \
libjpeg-dev=1:2.1.5-2 \ libjpeg-dev=1:2.1.5-2 \
libfreetype-dev=2.12.1+dfsg-5 \ libfreetype-dev=2.12.1+dfsg-5+deb12u3 \
libssl-dev=3.0.11-1~deb12u2 \ libssl-dev=3.0.13-1~deb12u1 \
libffi-dev=3.4.4-1 \ libffi-dev=3.4.4-1 \
libopenjp2-7=2.5.0-2 \ libopenjp2-7=2.5.0-2 \
libtiff6=4.5.0-6+deb12u1 \ libtiff6=4.5.0-6+deb12u1 \
cargo=0.66.0+ds1-1 \ cargo=0.66.0+ds1-1 \
pkg-config=1.8.1-1 \ pkg-config=1.8.1-1 \
gcc-arm-linux-gnueabihf=4:12.2.0-3; \ gcc-arm-linux-gnueabihf=4:12.2.0-3 \
fi; \ ) \
rm -rf \ || [ "$TARGETARCH$TARGETVARIANT" != "armv7" ] \
) \
&& rm -rf \
/tmp/* \ /tmp/* \
/var/{cache,log}/* \ /var/{cache,log}/* \
/var/lib/apt/lists/* /var/lib/apt/lists/*
@ -190,8 +194,8 @@ RUN \
clang-format-13=1:13.0.1-11+b2 \ clang-format-13=1:13.0.1-11+b2 \
clang-tidy-14=1:14.0.6-12 \ clang-tidy-14=1:14.0.6-12 \
patch=2.7.6-7 \ patch=2.7.6-7 \
software-properties-common=0.99.30-4 \ software-properties-common=0.99.30-4.1~deb12u1 \
nano=7.2-1 \ nano=7.2-1+deb12u1 \
build-essential=12.9 \ build-essential=12.9 \
python3-dev=3.11.2-1+b1 \ python3-dev=3.11.2-1+b1 \
&& rm -rf \ && rm -rf \

View file

@ -1,12 +1,12 @@
# PYTHON_ARGCOMPLETE_OK # PYTHON_ARGCOMPLETE_OK
import argparse import argparse
from datetime import datetime
import functools import functools
import logging import logging
import os import os
import re import re
import sys import sys
import time import time
from datetime import datetime
import argcomplete import argcomplete
@ -39,14 +39,14 @@ from esphome.const import (
) )
from esphome.core import CORE, EsphomeError, coroutine from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent, is_ip_address from esphome.helpers import indent, is_ip_address
from esphome.log import Fore, color, setup_log
from esphome.util import ( from esphome.util import (
get_serial_ports,
list_yaml_files,
run_external_command, run_external_command,
run_external_process, run_external_process,
safe_print, safe_print,
list_yaml_files,
get_serial_ports,
) )
from esphome.log import color, setup_log, Fore
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -116,6 +116,7 @@ def get_port_type(port):
def run_miniterm(config, port): def run_miniterm(config, port):
import serial import serial
from esphome import platformio_api from esphome import platformio_api
if CONF_LOGGER not in config: if CONF_LOGGER not in config:
@ -596,9 +597,10 @@ def command_update_all(args):
def command_idedata(args, config): def command_idedata(args, config):
from esphome import platformio_api
import json import json
from esphome import platformio_api
logging.disable(logging.INFO) logging.disable(logging.INFO)
logging.disable(logging.WARNING) logging.disable(logging.WARNING)
@ -695,6 +697,7 @@ def command_rename(args, config):
os.remove(new_path) os.remove(new_path)
return 1 return 1
if CORE.config_path != new_path:
os.remove(CORE.config_path) os.remove(CORE.config_path)
print(color(Fore.BOLD_GREEN, "SUCCESS")) print(color(Fore.BOLD_GREEN, "SUCCESS"))
@ -746,7 +749,14 @@ def parse_args(argv):
) )
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description=f"ESPHome v{const.__version__}", parents=[options_parser] description=f"ESPHome {const.__version__}", parents=[options_parser]
)
parser.add_argument(
"--version",
action="version",
version=f"Version: {const.__version__}",
help="Print the ESPHome version and exit.",
) )
mqtt_options = argparse.ArgumentParser(add_help=False) mqtt_options = argparse.ArgumentParser(add_help=False)
@ -947,67 +957,6 @@ def parse_args(argv):
# a deprecation warning). # a deprecation warning).
arguments = argv[1:] arguments = argv[1:]
# On Python 3.9+ we can simply set exit_on_error=False in the constructor
def _raise(x):
raise argparse.ArgumentError(None, x)
# First, try new-style parsing, but don't exit in case of failure
try:
# duplicate parser so that we can use the original one to raise errors later on
current_parser = argparse.ArgumentParser(add_help=False, parents=[parser])
current_parser.set_defaults(deprecated_argv_suggestion=None)
current_parser.error = _raise
return current_parser.parse_args(arguments)
except argparse.ArgumentError:
pass
# Second, try compat parsing and rearrange the command-line if it succeeds
# Disable argparse's built-in help option and add it manually to prevent this
# parser from printing the help messagefor the old format when invoked with -h.
compat_parser = argparse.ArgumentParser(parents=[options_parser], add_help=False)
compat_parser.add_argument("-h", "--help", action="store_true")
compat_parser.add_argument("configuration", nargs="*")
compat_parser.add_argument(
"command",
choices=[
"config",
"compile",
"upload",
"logs",
"run",
"clean-mqtt",
"wizard",
"mqtt-fingerprint",
"version",
"clean",
"dashboard",
"vscode",
"update-all",
],
)
try:
compat_parser.error = _raise
result, unparsed = compat_parser.parse_known_args(argv[1:])
last_option = len(arguments) - len(unparsed) - 1 - len(result.configuration)
unparsed = [
"--device" if arg in ("--upload-port", "--serial-port") else arg
for arg in unparsed
]
arguments = (
arguments[0:last_option]
+ [result.command]
+ result.configuration
+ unparsed
)
deprecated_argv_suggestion = arguments
except argparse.ArgumentError:
# old-style parsing failed, don't suggest any argument
deprecated_argv_suggestion = None
# 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) argcomplete.autocomplete(parser)
return parser.parse_args(arguments) return parser.parse_args(arguments)
@ -1022,13 +971,6 @@ def run_esphome(argv):
# Show timestamp for dashboard access logs # Show timestamp for dashboard access logs
args.command == "dashboard", args.command == "dashboard",
) )
if args.deprecated_argv_suggestion is not None and args.command != "vscode":
_LOGGER.warning(
"Calling ESPHome with the configuration before the command is deprecated "
"and will be removed in the future. "
)
_LOGGER.warning("Please instead use:")
_LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion))
if sys.version_info < (3, 8, 0): if sys.version_info < (3, 8, 0):
_LOGGER.error( _LOGGER.error(

View file

@ -7,10 +7,10 @@ from esphome.const import (
CONF_ELSE, CONF_ELSE,
CONF_ID, CONF_ID,
CONF_THEN, CONF_THEN,
CONF_TIME,
CONF_TIMEOUT, CONF_TIMEOUT,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_TYPE_ID, CONF_TYPE_ID,
CONF_TIME,
CONF_UPDATE_INTERVAL, CONF_UPDATE_INTERVAL,
) )
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor

View file

@ -8,85 +8,86 @@
# want to break suddenly due to a rename (this file will get backports for features). # want to break suddenly due to a rename (this file will get backports for features).
# pylint: disable=unused-import # pylint: disable=unused-import
from esphome.cpp_generator import ( # noqa from esphome.cpp_generator import ( # noqa: F401
ArrayInitializer,
Expression, Expression,
LineComment,
MockObj,
MockObjClass,
Pvariable,
RawExpression, RawExpression,
RawStatement, RawStatement,
TemplateArguments,
StructInitializer,
ArrayInitializer,
safe_exp,
Statement, Statement,
LineComment, StructInitializer,
progmem_array, TemplateArguments,
static_const_array,
statement,
variable,
with_local_variable,
new_variable,
Pvariable,
new_Pvariable,
add, add,
add_global,
add_library,
add_build_flag, add_build_flag,
add_define, add_define,
add_global,
add_library,
add_platformio_option, add_platformio_option,
get_variable, get_variable,
get_variable_with_full_id, get_variable_with_full_id,
process_lambda,
is_template, is_template,
new_Pvariable,
new_variable,
process_lambda,
progmem_array,
safe_exp,
statement,
static_const_array,
templatable, templatable,
MockObj, variable,
MockObjClass, with_local_variable,
) )
from esphome.cpp_helpers import ( # noqa from esphome.cpp_helpers import ( # noqa: F401
gpio_pin_expression,
register_component,
build_registry_entry, build_registry_entry,
build_registry_list, build_registry_list,
extract_registry_entry_config, extract_registry_entry_config,
register_parented, gpio_pin_expression,
past_safe_mode, past_safe_mode,
register_component,
register_parented,
) )
from esphome.cpp_types import ( # noqa from esphome.cpp_types import ( # noqa: F401
global_ns, NAN,
void, App,
nullptr, Application,
float_, Component,
double, ComponentPtr,
Controller,
EntityBase,
EntityCategory,
ESPTime,
GPIOPin,
InternalGPIOPin,
JsonObject,
JsonObjectConst,
Parented,
PollingComponent,
arduino_json_ns,
bool_, bool_,
const_char_ptr,
double,
esphome_ns,
float_,
global_ns,
gpio_Flags,
int16,
int32,
int64,
int_, int_,
nullptr,
optional,
size_t,
std_ns, std_ns,
std_shared_ptr, std_shared_ptr,
std_string, std_string,
std_string_ref,
std_vector, std_vector,
uint8, uint8,
uint16, uint16,
uint32, uint32,
uint64, uint64,
int16, void,
int32,
int64,
size_t,
const_char_ptr,
NAN,
esphome_ns,
App,
EntityBase,
Component,
ComponentPtr,
PollingComponent,
Application,
optional,
arduino_json_ns,
JsonObject,
JsonObjectConst,
Controller,
GPIOPin,
InternalGPIOPin,
gpio_Flags,
EntityCategory,
Parented,
ESPTime,
) )

View file

@ -1,5 +1,5 @@
import esphome.config_validation as cv import esphome.config_validation as cv
CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( CONFIG_SCHEMA = cv.invalid(
"The ade7953 sensor component has been renamed to ade7953_i2c." "The ade7953 sensor component has been renamed to ade7953_i2c."
) )

View file

@ -93,8 +93,9 @@ void AHT10Component::restart_read_() {
void AHT10Component::read_data_() { void AHT10Component::read_data_() {
uint8_t data[6]; uint8_t data[6];
if (this->read_count_ > 1) if (this->read_count_ > 1) {
ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_)); ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
}
if (this->read(data, 6) != i2c::ERROR_OK) { if (this->read(data, 6) != i2c::ERROR_OK) {
this->status_set_warning("AHT10 read failed, retrying soon"); this->status_set_warning("AHT10 read failed, retrying soon");
this->restart_read_(); this->restart_read_();
@ -119,8 +120,9 @@ void AHT10Component::read_data_() {
return; return;
} }
} }
if (this->read_count_ > 1) if (this->read_count_ > 1) {
ESP_LOGD(TAG, "Success at %ums", (unsigned) (millis() - this->start_time_)); ESP_LOGD(TAG, "Success at %ums", (unsigned) (millis() - this->start_time_));
}
uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4; uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;

View file

@ -0,0 +1,4 @@
# Based on this datasheet:
# https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
CODEOWNERS = ["@aodrenah"]

View file

@ -0,0 +1,151 @@
// Based on this datasheet:
// https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
#include "apds9306.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace apds9306 {
static const char *const TAG = "apds9306";
enum { // APDS9306 registers
APDS9306_MAIN_CTRL = 0x00,
APDS9306_ALS_MEAS_RATE = 0x04,
APDS9306_ALS_GAIN = 0x05,
APDS9306_PART_ID = 0x06,
APDS9306_MAIN_STATUS = 0x07,
APDS9306_CLEAR_DATA_0 = 0x0A, // LSB
APDS9306_CLEAR_DATA_1 = 0x0B,
APDS9306_CLEAR_DATA_2 = 0x0C, // MSB
APDS9306_ALS_DATA_0 = 0x0D, // LSB
APDS9306_ALS_DATA_1 = 0x0E,
APDS9306_ALS_DATA_2 = 0x0F, // MSB
APDS9306_INT_CFG = 0x19,
APDS9306_INT_PERSISTENCE = 0x1A,
APDS9306_ALS_THRES_UP_0 = 0x21, // LSB
APDS9306_ALS_THRES_UP_1 = 0x22,
APDS9306_ALS_THRES_UP_2 = 0x23, // MSB
APDS9306_ALS_THRES_LOW_0 = 0x24, // LSB
APDS9306_ALS_THRES_LOW_1 = 0x25,
APDS9306_ALS_THRES_LOW_2 = 0x26, // MSB
APDS9306_ALS_THRES_VAR = 0x27
};
#define APDS9306_ERROR_CHECK(func, error) \
if (!(func)) { \
ESP_LOGE(TAG, error); \
this->mark_failed(); \
return; \
}
#define APDS9306_WARNING_CHECK(func, warning) \
if (!(func)) { \
ESP_LOGW(TAG, warning); \
this->status_set_warning(); \
return; \
}
#define APDS9306_WRITE_BYTE(reg, value) \
ESP_LOGV(TAG, "Writing 0x%02x to 0x%02x", value, reg); \
if (!this->write_byte(reg, value)) { \
ESP_LOGE(TAG, "Failed writing 0x%02x to 0x%02x", value, reg); \
this->mark_failed(); \
return; \
}
void APDS9306::setup() {
ESP_LOGCONFIG(TAG, "Setting up APDS9306...");
uint8_t id;
if (!this->read_byte(APDS9306_PART_ID, &id)) { // Part ID register
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
}
if (id != 0xB1 && id != 0xB3) { // 0xB1 for APDS9306 0xB3 for APDS9306-065
this->error_code_ = WRONG_ID;
this->mark_failed();
return;
}
// ALS resolution and measurement, see datasheet or init.py for options
uint8_t als_meas_rate = ((this->bit_width_ & 0x07) << 4) | (this->measurement_rate_ & 0x07);
APDS9306_WRITE_BYTE(APDS9306_ALS_MEAS_RATE, als_meas_rate);
// ALS gain, see datasheet or init.py for options
uint8_t als_gain = (this->gain_ & 0x07);
APDS9306_WRITE_BYTE(APDS9306_ALS_GAIN, als_gain);
// Set to standby mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x00);
// Check for data, clear main status
uint8_t status;
APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed.");
// Set to active mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02);
ESP_LOGCONFIG(TAG, "APDS9306 setup complete");
}
void APDS9306::dump_config() {
LOG_SENSOR("", "APDS9306", this);
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGE(TAG, "Communication with APDS9306 failed!");
break;
case WRONG_ID:
ESP_LOGE(TAG, "APDS9306 has invalid id!");
break;
default:
ESP_LOGE(TAG, "Setting up APDS9306 registers failed!");
break;
}
}
ESP_LOGCONFIG(TAG, " Gain: %u", AMBIENT_LIGHT_GAIN_VALUES[this->gain_]);
ESP_LOGCONFIG(TAG, " Measurement rate: %u", MEASUREMENT_RATE_VALUES[this->measurement_rate_]);
ESP_LOGCONFIG(TAG, " Measurement Resolution/Bit width: %d", MEASUREMENT_BIT_WIDTH_VALUES[this->bit_width_]);
LOG_UPDATE_INTERVAL(this);
}
void APDS9306::update() {
// Check for new data
uint8_t status;
APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed.");
this->status_clear_warning();
if (!(status &= 0b00001000)) { // No new data
return;
}
// Set to standby mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x00);
// Clear MAIN STATUS
APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed.");
uint8_t als_data[3];
APDS9306_WARNING_CHECK(this->read_bytes(APDS9306_ALS_DATA_0, als_data, 3), "Reading ALS data has failed.");
// Set to active mode
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02);
uint32_t light_level = 0x00 | encode_uint24(als_data[2], als_data[1], als_data[0]);
float lux = ((float) light_level / AMBIENT_LIGHT_GAIN_VALUES[this->gain_]) *
(100.0f / MEASUREMENT_RATE_VALUES[this->measurement_rate_]);
ESP_LOGD(TAG, "Got illuminance=%.1flx from", lux);
this->publish_state(lux);
}
} // namespace apds9306
} // namespace esphome

View file

@ -0,0 +1,66 @@
// Based on this datasheet:
// https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
namespace esphome {
namespace apds9306 {
enum MeasurementBitWidth : uint8_t {
MEASUREMENT_BIT_WIDTH_20 = 0,
MEASUREMENT_BIT_WIDTH_19 = 1,
MEASUREMENT_BIT_WIDTH_18 = 2,
MEASUREMENT_BIT_WIDTH_17 = 3,
MEASUREMENT_BIT_WIDTH_16 = 4,
MEASUREMENT_BIT_WIDTH_13 = 5,
};
static const uint8_t MEASUREMENT_BIT_WIDTH_VALUES[] = {20, 19, 18, 17, 16, 13};
enum MeasurementRate : uint8_t {
MEASUREMENT_RATE_25 = 0,
MEASUREMENT_RATE_50 = 1,
MEASUREMENT_RATE_100 = 2,
MEASUREMENT_RATE_200 = 3,
MEASUREMENT_RATE_500 = 4,
MEASUREMENT_RATE_1000 = 5,
MEASUREMENT_RATE_2000 = 6,
};
static const uint16_t MEASUREMENT_RATE_VALUES[] = {25, 50, 100, 200, 500, 1000, 2000};
enum AmbientLightGain : uint8_t {
AMBIENT_LIGHT_GAIN_1 = 0,
AMBIENT_LIGHT_GAIN_3 = 1,
AMBIENT_LIGHT_GAIN_6 = 2,
AMBIENT_LIGHT_GAIN_9 = 3,
AMBIENT_LIGHT_GAIN_18 = 4,
};
static const uint8_t AMBIENT_LIGHT_GAIN_VALUES[] = {1, 3, 6, 9, 18};
class APDS9306 : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
public:
void setup() override;
float get_setup_priority() const override { return setup_priority::BUS; }
void dump_config() override;
void update() override;
void set_bit_width(MeasurementBitWidth bit_width) { this->bit_width_ = bit_width; }
void set_measurement_rate(MeasurementRate measurement_rate) { this->measurement_rate_ = measurement_rate; }
void set_ambient_light_gain(AmbientLightGain gain) { this->gain_ = gain; }
protected:
enum ErrorCode {
NONE = 0,
COMMUNICATION_FAILED,
WRONG_ID,
} error_code_{NONE};
MeasurementBitWidth bit_width_;
MeasurementRate measurement_rate_;
AmbientLightGain gain_;
};
} // namespace apds9306
} // namespace esphome

View file

@ -0,0 +1,95 @@
# Based on this datasheet:
# https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_GAIN,
DEVICE_CLASS_ILLUMINANCE,
ICON_LIGHTBULB,
STATE_CLASS_MEASUREMENT,
UNIT_LUX,
)
DEPENDENCIES = ["i2c"]
CONF_APDS9306_ID = "apds9306_id"
CONF_BIT_WIDTH = "bit_width"
CONF_MEASUREMENT_RATE = "measurement_rate"
apds9306_ns = cg.esphome_ns.namespace("apds9306")
APDS9306 = apds9306_ns.class_(
"APDS9306", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
)
MeasurementBitWidth = apds9306_ns.enum("MeasurementBitWidth")
MeasurementRate = apds9306_ns.enum("MeasurementRate")
AmbientLightGain = apds9306_ns.enum("AmbientLightGain")
MEASUREMENT_BIT_WIDTHS = {
20: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_20,
19: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_19,
18: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_18,
17: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_17,
16: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_16,
13: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_13,
}
MEASUREMENT_RATES = {
25: MeasurementRate.MEASUREMENT_RATE_25,
50: MeasurementRate.MEASUREMENT_RATE_50,
100: MeasurementRate.MEASUREMENT_RATE_100,
200: MeasurementRate.MEASUREMENT_RATE_200,
500: MeasurementRate.MEASUREMENT_RATE_500,
1000: MeasurementRate.MEASUREMENT_RATE_1000,
2000: MeasurementRate.MEASUREMENT_RATE_2000,
}
AMBIENT_LIGHT_GAINS = {
1: AmbientLightGain.AMBIENT_LIGHT_GAIN_1,
3: AmbientLightGain.AMBIENT_LIGHT_GAIN_3,
6: AmbientLightGain.AMBIENT_LIGHT_GAIN_6,
9: AmbientLightGain.AMBIENT_LIGHT_GAIN_9,
18: AmbientLightGain.AMBIENT_LIGHT_GAIN_18,
}
def _validate_measurement_rate(value):
value = cv.positive_time_period_milliseconds(value)
return cv.enum(MEASUREMENT_RATES, int=True)(value.total_milliseconds)
CONFIG_SCHEMA = (
sensor.sensor_schema(
APDS9306,
unit_of_measurement=UNIT_LUX,
accuracy_decimals=1,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
icon=ICON_LIGHTBULB,
)
.extend(
{
cv.Optional(CONF_GAIN, default="1"): cv.enum(AMBIENT_LIGHT_GAINS, int=True),
cv.Optional(CONF_BIT_WIDTH, default="18"): cv.enum(
MEASUREMENT_BIT_WIDTHS, int=True
),
cv.Optional(
CONF_MEASUREMENT_RATE, default="100ms"
): _validate_measurement_rate,
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x52))
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_bit_width(config[CONF_BIT_WIDTH]))
cg.add(var.set_measurement_rate(config[CONF_MEASUREMENT_RATE]))
cg.add(var.set_ambient_light_gain(config[CONF_GAIN]))

View file

@ -1,7 +1,8 @@
import esphome.codegen as cg from esphome import automation
import esphome.config_validation as cv
from esphome.automation import maybe_simple_id from esphome.automation import maybe_simple_id
from esphome.components import esp32_ble_tracker, esp32_ble_client import esphome.codegen as cg
from esphome.components import esp32_ble_client, esp32_ble_tracker
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_CHARACTERISTIC_UUID, CONF_CHARACTERISTIC_UUID,
CONF_ID, CONF_ID,
@ -13,7 +14,6 @@ from esphome.const import (
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_VALUE, CONF_VALUE,
) )
from esphome import automation
AUTO_LOAD = ["esp32_ble_client"] AUTO_LOAD = ["esp32_ble_client"]
CODEOWNERS = ["@buxtronix", "@clydebarrow"] CODEOWNERS = ["@buxtronix", "@clydebarrow"]

View file

@ -1,6 +1,6 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import ble_client, esp32_ble_tracker, output from esphome.components import ble_client, esp32_ble_tracker, output
import esphome.config_validation as cv
from esphome.const import CONF_CHARACTERISTIC_UUID, CONF_ID, CONF_SERVICE_UUID from esphome.const import CONF_CHARACTERISTIC_UUID, CONF_ID, CONF_SERVICE_UUID
from .. import ble_client_ns from .. import ble_client_ns

View file

@ -1,17 +1,18 @@
from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import ble_client, esp32_ble_tracker, sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import sensor, ble_client, esp32_ble_tracker
from esphome.const import ( from esphome.const import (
CONF_CHARACTERISTIC_UUID, CONF_CHARACTERISTIC_UUID,
CONF_LAMBDA, CONF_LAMBDA,
CONF_SERVICE_UUID,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_TYPE, CONF_TYPE,
CONF_SERVICE_UUID,
DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_SIGNAL_STRENGTH,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_DECIBEL_MILLIWATT, UNIT_DECIBEL_MILLIWATT,
) )
from esphome import automation
from .. import ble_client_ns from .. import ble_client_ns
DEPENDENCIES = ["ble_client"] DEPENDENCIES = ["ble_client"]

View file

@ -1,7 +1,8 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import ble_client, switch
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import switch, ble_client
from esphome.const import ICON_BLUETOOTH from esphome.const import ICON_BLUETOOTH
from .. import ble_client_ns from .. import ble_client_ns
BLEClientSwitch = ble_client_ns.class_( BLEClientSwitch = ble_client_ns.class_(

View file

@ -1,13 +1,14 @@
from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import ble_client, esp32_ble_tracker, text_sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import text_sensor, ble_client, esp32_ble_tracker
from esphome.const import ( from esphome.const import (
CONF_CHARACTERISTIC_UUID, CONF_CHARACTERISTIC_UUID,
CONF_ID, CONF_ID,
CONF_TRIGGER_ID,
CONF_SERVICE_UUID, CONF_SERVICE_UUID,
CONF_TRIGGER_ID,
) )
from esphome import automation
from .. import ble_client_ns from .. import ble_client_ns
DEPENDENCIES = ["ble_client"] DEPENDENCIES = ["ble_client"]

View file

@ -1,13 +1,13 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor, esp32_ble_tracker from esphome.components import binary_sensor, esp32_ble_tracker
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_MAC_ADDRESS,
CONF_SERVICE_UUID,
CONF_IBEACON_MAJOR, CONF_IBEACON_MAJOR,
CONF_IBEACON_MINOR, CONF_IBEACON_MINOR,
CONF_IBEACON_UUID, CONF_IBEACON_UUID,
CONF_MAC_ADDRESS,
CONF_MIN_RSSI, CONF_MIN_RSSI,
CONF_SERVICE_UUID,
CONF_TIMEOUT, CONF_TIMEOUT,
) )

View file

@ -1,12 +1,12 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32_ble_tracker, sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import sensor, esp32_ble_tracker
from esphome.const import ( from esphome.const import (
CONF_IBEACON_MAJOR, CONF_IBEACON_MAJOR,
CONF_IBEACON_MINOR, CONF_IBEACON_MINOR,
CONF_IBEACON_UUID, CONF_IBEACON_UUID,
CONF_SERVICE_UUID,
CONF_MAC_ADDRESS, CONF_MAC_ADDRESS,
CONF_SERVICE_UUID,
DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_SIGNAL_STRENGTH,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_DECIBEL_MILLIWATT, UNIT_DECIBEL_MILLIWATT,

View file

@ -1,6 +1,6 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32_ble_tracker, text_sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import text_sensor, esp32_ble_tracker
DEPENDENCIES = ["esp32_ble_tracker"] DEPENDENCIES = ["esp32_ble_tracker"]

View file

@ -1,8 +1,8 @@
from esphome.components import esp32_ble_tracker, esp32_ble_client
import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.const import CONF_ACTIVE, CONF_ID from esphome.components import esp32_ble_client, esp32_ble_tracker
from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.components.esp32 import add_idf_sdkconfig_option
import esphome.config_validation as cv
from esphome.const import CONF_ACTIVE, CONF_ID
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"] AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
DEPENDENCIES = ["api", "esp32"] DEPENDENCIES = ["api", "esp32"]

View file

@ -1,7 +1,7 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import i2c, esp32 from esphome.components import i2c, esp32
from esphome.const import CONF_ID, CONF_TEMPERATURE_OFFSET from esphome.const import CONF_ID, CONF_SAMPLE_RATE, CONF_TEMPERATURE_OFFSET
CODEOWNERS = ["@trvrnrth"] CODEOWNERS = ["@trvrnrth"]
DEPENDENCIES = ["i2c"] DEPENDENCIES = ["i2c"]
@ -11,7 +11,6 @@ MULTI_CONF = True
CONF_BME680_BSEC_ID = "bme680_bsec_id" CONF_BME680_BSEC_ID = "bme680_bsec_id"
CONF_IAQ_MODE = "iaq_mode" CONF_IAQ_MODE = "iaq_mode"
CONF_SUPPLY_VOLTAGE = "supply_voltage" CONF_SUPPLY_VOLTAGE = "supply_voltage"
CONF_SAMPLE_RATE = "sample_rate"
CONF_STATE_SAVE_INTERVAL = "state_save_interval" CONF_STATE_SAVE_INTERVAL = "state_save_interval"
bme680_bsec_ns = cg.esphome_ns.namespace("bme680_bsec") bme680_bsec_ns = cg.esphome_ns.namespace("bme680_bsec")

View file

@ -4,33 +4,33 @@ from esphome.components import sensor
from esphome.const import ( from esphome.const import (
CONF_GAS_RESISTANCE, CONF_GAS_RESISTANCE,
CONF_HUMIDITY, CONF_HUMIDITY,
CONF_IAQ_ACCURACY,
CONF_PRESSURE, CONF_PRESSURE,
CONF_SAMPLE_RATE,
CONF_TEMPERATURE, CONF_TEMPERATURE,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_CARBON_DIOXIDE,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
ICON_GAS_CYLINDER,
ICON_GAUGE,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS, UNIT_CELSIUS,
UNIT_HECTOPASCAL, UNIT_HECTOPASCAL,
UNIT_OHM, UNIT_OHM,
UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_MILLION,
UNIT_PERCENT, UNIT_PERCENT,
ICON_GAS_CYLINDER,
ICON_GAUGE,
) )
from . import ( from . import (
BME680BSECComponent, BME680BSECComponent,
CONF_BME680_BSEC_ID, CONF_BME680_BSEC_ID,
CONF_SAMPLE_RATE,
SAMPLE_RATE_OPTIONS, SAMPLE_RATE_OPTIONS,
) )
DEPENDENCIES = ["bme680_bsec"] DEPENDENCIES = ["bme680_bsec"]
CONF_IAQ = "iaq" CONF_IAQ = "iaq"
CONF_IAQ_ACCURACY = "iaq_accuracy"
CONF_CO2_EQUIVALENT = "co2_equivalent" CONF_CO2_EQUIVALENT = "co2_equivalent"
CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent" CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent"
UNIT_IAQ = "IAQ" UNIT_IAQ = "IAQ"

View file

@ -1,11 +1,11 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import text_sensor from esphome.components import text_sensor
from esphome.const import CONF_IAQ_ACCURACY
from . import BME680BSECComponent, CONF_BME680_BSEC_ID from . import BME680BSECComponent, CONF_BME680_BSEC_ID
DEPENDENCIES = ["bme680_bsec"] DEPENDENCIES = ["bme680_bsec"]
CONF_IAQ_ACCURACY = "iaq_accuracy"
ICON_ACCURACY = "mdi:checkbox-marked-circle-outline" ICON_ACCURACY = "mdi:checkbox-marked-circle-outline"
TYPES = [CONF_IAQ_ACCURACY] TYPES = [CONF_IAQ_ACCURACY]

View file

@ -2,6 +2,6 @@ import esphome.config_validation as cv
CODEOWNERS = ["@latonita"] CODEOWNERS = ["@latonita"]
CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( CONFIG_SCHEMA = cv.invalid(
"The bmp3xx sensor component has been renamed to bmp3xx_i2c." "The bmp3xx sensor component has been renamed to bmp3xx_i2c."
) )

View file

@ -574,21 +574,25 @@ void Climate::dump_traits_(const char *tag) {
ESP_LOGCONFIG(tag, " - Max temperature: %.1f", traits.get_visual_max_temperature()); ESP_LOGCONFIG(tag, " - Max temperature: %.1f", traits.get_visual_max_temperature());
ESP_LOGCONFIG(tag, " - Temperature step:"); ESP_LOGCONFIG(tag, " - Temperature step:");
ESP_LOGCONFIG(tag, " Target: %.1f", traits.get_visual_target_temperature_step()); ESP_LOGCONFIG(tag, " Target: %.1f", traits.get_visual_target_temperature_step());
if (traits.get_supports_current_temperature()) {
ESP_LOGCONFIG(tag, " Current: %.1f", traits.get_visual_current_temperature_step()); ESP_LOGCONFIG(tag, " Current: %.1f", traits.get_visual_current_temperature_step());
}
if (traits.get_supports_target_humidity() || traits.get_supports_current_humidity()) {
ESP_LOGCONFIG(tag, " - Min humidity: %.0f", traits.get_visual_min_humidity()); ESP_LOGCONFIG(tag, " - Min humidity: %.0f", traits.get_visual_min_humidity());
ESP_LOGCONFIG(tag, " - Max humidity: %.0f", traits.get_visual_max_humidity()); ESP_LOGCONFIG(tag, " - Max humidity: %.0f", traits.get_visual_max_humidity());
if (traits.get_supports_current_temperature()) {
ESP_LOGCONFIG(tag, " [x] Supports current temperature");
}
if (traits.get_supports_current_humidity()) {
ESP_LOGCONFIG(tag, " [x] Supports current humidity");
} }
if (traits.get_supports_two_point_target_temperature()) { if (traits.get_supports_two_point_target_temperature()) {
ESP_LOGCONFIG(tag, " [x] Supports two-point target temperature"); ESP_LOGCONFIG(tag, " [x] Supports two-point target temperature");
} }
if (traits.get_supports_current_temperature()) {
ESP_LOGCONFIG(tag, " [x] Supports current temperature");
}
if (traits.get_supports_target_humidity()) { if (traits.get_supports_target_humidity()) {
ESP_LOGCONFIG(tag, " [x] Supports target humidity"); ESP_LOGCONFIG(tag, " [x] Supports target humidity");
} }
if (traits.get_supports_current_humidity()) {
ESP_LOGCONFIG(tag, " [x] Supports current humidity");
}
if (traits.get_supports_action()) { if (traits.get_supports_action()) {
ESP_LOGCONFIG(tag, " [x] Supports action"); ESP_LOGCONFIG(tag, " [x] Supports action");
} }

View file

@ -73,7 +73,7 @@ class ClimateTraits {
ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20") ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20")
void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); } void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); }
bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); } bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); }
std::set<ClimateMode> get_supported_modes() const { return supported_modes_; } const std::set<ClimateMode> &get_supported_modes() const { return supported_modes_; }
void set_supports_action(bool supports_action) { supports_action_ = supports_action; } void set_supports_action(bool supports_action) { supports_action_ = supports_action; }
bool get_supports_action() const { return supports_action_; } bool get_supports_action() const { return supports_action_; }
@ -101,7 +101,7 @@ class ClimateTraits {
void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); } void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); }
bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); } bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); }
bool get_supports_fan_modes() const { return !supported_fan_modes_.empty() || !supported_custom_fan_modes_.empty(); } bool get_supports_fan_modes() const { return !supported_fan_modes_.empty() || !supported_custom_fan_modes_.empty(); }
std::set<ClimateFanMode> get_supported_fan_modes() const { return supported_fan_modes_; } const std::set<ClimateFanMode> &get_supported_fan_modes() const { return supported_fan_modes_; }
void set_supported_custom_fan_modes(std::set<std::string> supported_custom_fan_modes) { void set_supported_custom_fan_modes(std::set<std::string> supported_custom_fan_modes) {
supported_custom_fan_modes_ = std::move(supported_custom_fan_modes); supported_custom_fan_modes_ = std::move(supported_custom_fan_modes);
@ -140,7 +140,7 @@ class ClimateTraits {
} }
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); } bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); }
bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); } bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); }
std::set<ClimateSwingMode> get_supported_swing_modes() const { return supported_swing_modes_; } const std::set<ClimateSwingMode> &get_supported_swing_modes() const { return supported_swing_modes_; }
float get_visual_min_temperature() const { return visual_min_temperature_; } float get_visual_min_temperature() const { return visual_min_temperature_; }
void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; } void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }

View file

@ -145,10 +145,8 @@ bool DallasTemperatureSensor::check_scratch_pad_() {
float DallasTemperatureSensor::get_temp_c_() { float DallasTemperatureSensor::get_temp_c_() {
int16_t temp = (this->scratch_pad_[1] << 8) | this->scratch_pad_[0]; int16_t temp = (this->scratch_pad_[1] << 8) | this->scratch_pad_[0];
if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) { if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) {
if (this->scratch_pad_[7] != 0x10) return (temp >> 1) + (this->scratch_pad_[7] - this->scratch_pad_[6]) / float(this->scratch_pad_[7]) - 0.25;
ESP_LOGE(TAG, "unexpected COUNT_PER_C value: %u", this->scratch_pad_[7]); }
temp = ((temp & 0xfff7) << 3) + (0x10 - this->scratch_pad_[6]) - 4;
} else {
switch (this->resolution_) { switch (this->resolution_) {
case 9: case 9:
temp &= 0xfff8; temp &= 0xfff8;
@ -163,7 +161,6 @@ float DallasTemperatureSensor::get_temp_c_() {
default: default:
break; break;
} }
}
return temp / 16.0f; return temp / 16.0f;
} }

View file

@ -37,14 +37,18 @@ void DS1307Component::read_time() {
ESP_LOGW(TAG, "RTC halted, not syncing to system clock."); ESP_LOGW(TAG, "RTC halted, not syncing to system clock.");
return; return;
} }
ESPTime rtc_time{.second = uint8_t(ds1307_.reg.second + 10 * ds1307_.reg.second_10), ESPTime rtc_time{
.second = uint8_t(ds1307_.reg.second + 10 * ds1307_.reg.second_10),
.minute = uint8_t(ds1307_.reg.minute + 10u * ds1307_.reg.minute_10), .minute = uint8_t(ds1307_.reg.minute + 10u * ds1307_.reg.minute_10),
.hour = uint8_t(ds1307_.reg.hour + 10u * ds1307_.reg.hour_10), .hour = uint8_t(ds1307_.reg.hour + 10u * ds1307_.reg.hour_10),
.day_of_week = uint8_t(ds1307_.reg.weekday), .day_of_week = uint8_t(ds1307_.reg.weekday),
.day_of_month = uint8_t(ds1307_.reg.day + 10u * ds1307_.reg.day_10), .day_of_month = uint8_t(ds1307_.reg.day + 10u * ds1307_.reg.day_10),
.day_of_year = 1, // ignored by recalc_timestamp_utc(false) .day_of_year = 1, // ignored by recalc_timestamp_utc(false)
.month = uint8_t(ds1307_.reg.month + 10u * ds1307_.reg.month_10), .month = uint8_t(ds1307_.reg.month + 10u * ds1307_.reg.month_10),
.year = uint16_t(ds1307_.reg.year + 10u * ds1307_.reg.year_10 + 2000)}; .year = uint16_t(ds1307_.reg.year + 10u * ds1307_.reg.year_10 + 2000),
.is_dst = false, // not used
.timestamp = 0 // overwritten by recalc_timestamp_utc(false)
};
rtc_time.recalc_timestamp_utc(false); rtc_time.recalc_timestamp_utc(false);
if (!rtc_time.is_valid()) { if (!rtc_time.is_valid()) {
ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock."); ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");

View file

@ -2,6 +2,6 @@ import esphome.config_validation as cv
CODEOWNERS = ["@latonita"] CODEOWNERS = ["@latonita"]
CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( CONFIG_SCHEMA = cv.invalid(
"The ens160 sensor component has been renamed to ens160_i2c." "The ens160 sensor component has been renamed to ens160_i2c."
) )

View file

@ -1,11 +1,12 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Union, Optional
from pathlib import Path
import logging import logging
import os import os
import esphome.final_validate as fv from pathlib import Path
from typing import Optional, Union
from esphome.helpers import copy_file_if_changed, write_file_if_changed, mkdir_p from esphome import git
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ADVANCED, CONF_ADVANCED,
CONF_BOARD, CONF_BOARD,
@ -15,6 +16,7 @@ from esphome.const import (
CONF_IGNORE_EFUSE_MAC_CRC, CONF_IGNORE_EFUSE_MAC_CRC,
CONF_NAME, CONF_NAME,
CONF_PATH, CONF_PATH,
CONF_PLATFORM_VERSION,
CONF_PLATFORMIO_OPTIONS, CONF_PLATFORMIO_OPTIONS,
CONF_REF, CONF_REF,
CONF_REFRESH, CONF_REFRESH,
@ -32,13 +34,12 @@ from esphome.const import (
TYPE_GIT, TYPE_GIT,
TYPE_LOCAL, TYPE_LOCAL,
__version__, __version__,
CONF_PLATFORM_VERSION,
) )
from esphome.core import CORE, HexInt, TimePeriod from esphome.core import CORE, HexInt, TimePeriod
import esphome.config_validation as cv import esphome.final_validate as fv
import esphome.codegen as cg from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed
from esphome import git
from .boards import BOARDS
from .const import ( # noqa from .const import ( # noqa
KEY_BOARD, KEY_BOARD,
KEY_COMPONENTS, KEY_COMPONENTS,
@ -54,12 +55,10 @@ from .const import ( # noqa
VARIANT_FRIENDLY, VARIANT_FRIENDLY,
VARIANTS, VARIANTS,
) )
from .boards import BOARDS
# force import gpio to register pin schema # force import gpio to register pin schema
from .gpio import esp32_pin_to_code # noqa from .gpio import esp32_pin_to_code # noqa
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
AUTO_LOAD = ["preferences"] AUTO_LOAD = ["preferences"]

View file

@ -1,4 +1,4 @@
from .const import VARIANT_ESP32, VARIANT_ESP32S2, VARIANT_ESP32C3, VARIANT_ESP32S3 from .const import VARIANT_ESP32, VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3
ESP32_BASE_PINS = { ESP32_BASE_PINS = {
"TX": 1, "TX": 1,

View file

@ -1,22 +1,22 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any
import logging import logging
from typing import Any
from esphome import pins
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_ID,
CONF_IGNORE_PIN_VALIDATION_ERROR,
CONF_IGNORE_STRAPPING_WARNING,
CONF_INVERTED, CONF_INVERTED,
CONF_MODE, CONF_MODE,
CONF_NUMBER, CONF_NUMBER,
CONF_OPEN_DRAIN, CONF_OPEN_DRAIN,
CONF_OUTPUT, CONF_OUTPUT,
CONF_IGNORE_PIN_VALIDATION_ERROR,
CONF_IGNORE_STRAPPING_WARNING,
PLATFORM_ESP32, PLATFORM_ESP32,
) )
from esphome import pins
from esphome.core import CORE from esphome.core import CORE
import esphome.config_validation as cv
import esphome.codegen as cg
from . import boards from . import boards
from .const import ( from .const import (
@ -24,22 +24,21 @@ from .const import (
KEY_ESP32, KEY_ESP32,
KEY_VARIANT, KEY_VARIANT,
VARIANT_ESP32, VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
VARIANT_ESP32C2, VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C6, VARIANT_ESP32C6,
VARIANT_ESP32H2, VARIANT_ESP32H2,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
esp32_ns, esp32_ns,
) )
from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports
from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports
from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports
from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_supports
from .gpio_esp32_c2 import esp32_c2_validate_gpio_pin, esp32_c2_validate_supports from .gpio_esp32_c2 import esp32_c2_validate_gpio_pin, esp32_c2_validate_supports
from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports
from .gpio_esp32_c6 import esp32_c6_validate_gpio_pin, esp32_c6_validate_supports from .gpio_esp32_c6 import esp32_c6_validate_gpio_pin, esp32_c6_validate_supports
from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports
from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports
from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_supports
ESP32InternalGPIOPin = esp32_ns.class_("ESP32InternalGPIOPin", cg.InternalGPIOPin) ESP32InternalGPIOPin = esp32_ns.class_("ESP32InternalGPIOPin", cg.InternalGPIOPin)

View file

@ -1,5 +1,6 @@
import logging import logging
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_INPUT, CONF_INPUT,
CONF_MODE, CONF_MODE,
@ -8,10 +9,8 @@ from esphome.const import (
CONF_PULLDOWN, CONF_PULLDOWN,
CONF_PULLUP, CONF_PULLUP,
) )
import esphome.config_validation as cv
from esphome.pins import check_strapping_pin from esphome.pins import check_strapping_pin
_ESP_SDIO_PINS = { _ESP_SDIO_PINS = {
6: "Flash Clock", 6: "Flash Clock",
7: "Flash Data 0", 7: "Flash Data 0",

View file

@ -1,10 +1,9 @@
import logging import logging
import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.pins import check_strapping_pin from esphome.pins import check_strapping_pin
import esphome.config_validation as cv
_ESP32C2_STRAPPING_PINS = {8, 9} _ESP32C2_STRAPPING_PINS = {8, 9}
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View file

@ -1,11 +1,7 @@
import logging import logging
from esphome.const import (
CONF_INPUT,
CONF_MODE,
CONF_NUMBER,
)
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.pins import check_strapping_pin from esphome.pins import check_strapping_pin
_ESP32C3_SPI_PSRAM_PINS = { _ESP32C3_SPI_PSRAM_PINS = {

View file

@ -1,8 +1,7 @@
import logging import logging
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.pins import check_strapping_pin from esphome.pins import check_strapping_pin
_ESP32C6_SPI_PSRAM_PINS = { _ESP32C6_SPI_PSRAM_PINS = {

View file

@ -1,8 +1,7 @@
import logging import logging
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
_ESP32H2_SPI_FLASH_PINS = {6, 7, 15, 16, 17, 18, 19, 20, 21} _ESP32H2_SPI_FLASH_PINS = {6, 7, 15, 16, 17, 18, 19, 20, 21}

View file

@ -1,5 +1,6 @@
import logging import logging
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_INPUT, CONF_INPUT,
CONF_MODE, CONF_MODE,
@ -8,8 +9,6 @@ from esphome.const import (
CONF_PULLDOWN, CONF_PULLDOWN,
CONF_PULLUP, CONF_PULLUP,
) )
import esphome.config_validation as cv
from esphome.pins import check_strapping_pin from esphome.pins import check_strapping_pin
_ESP32S2_SPI_PSRAM_PINS = { _ESP32S2_SPI_PSRAM_PINS = {

View file

@ -1,12 +1,7 @@
import logging import logging
from esphome.const import (
CONF_INPUT,
CONF_MODE,
CONF_NUMBER,
)
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.pins import check_strapping_pin from esphome.pins import check_strapping_pin
_ESP_32S3_SPI_PSRAM_PINS = { _ESP_32S3_SPI_PSRAM_PINS = {

View file

@ -1,16 +1,16 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
import esphome.config_validation as cv
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ID from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ID
from esphome.core import CORE from esphome.core import CORE
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant, const
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
CODEOWNERS = ["@jesserockz", "@Rapsssito"] CODEOWNERS = ["@jesserockz", "@Rapsssito"]
CONFLICTS_WITH = ["esp32_ble_beacon"]
CONF_BLE_ID = "ble_id" CONF_BLE_ID = "ble_id"
CONF_IO_CAPABILITY = "io_capability" CONF_IO_CAPABILITY = "io_capability"
CONF_ADVERTISING_CYCLE_TIME = "advertising_cycle_time"
NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
@ -34,6 +34,19 @@ IO_CAPABILITY = {
"display_yes_no": IoCapability.IO_CAP_IO, "display_yes_no": IoCapability.IO_CAP_IO,
} }
esp_power_level_t = cg.global_ns.enum("esp_power_level_t")
TX_POWER_LEVELS = {
-12: esp_power_level_t.ESP_PWR_LVL_N12,
-9: esp_power_level_t.ESP_PWR_LVL_N9,
-6: esp_power_level_t.ESP_PWR_LVL_N6,
-3: esp_power_level_t.ESP_PWR_LVL_N3,
0: esp_power_level_t.ESP_PWR_LVL_N0,
3: esp_power_level_t.ESP_PWR_LVL_P3,
6: esp_power_level_t.ESP_PWR_LVL_P6,
9: esp_power_level_t.ESP_PWR_LVL_P9,
}
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(ESP32BLE), cv.GenerateID(): cv.declare_id(ESP32BLE),
@ -41,6 +54,9 @@ CONFIG_SCHEMA = cv.Schema(
IO_CAPABILITY, lower=True IO_CAPABILITY, lower=True
), ),
cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean, cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
cv.Optional(
CONF_ADVERTISING_CYCLE_TIME, default="10s"
): cv.positive_time_period_milliseconds,
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
@ -58,6 +74,7 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT])) cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT]))
cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY])) cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY]))
cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME]))
await cg.register_component(var, config) await cg.register_component(var, config)
if CORE.using_esp_idf: if CORE.using_esp_idf:

View file

@ -78,6 +78,11 @@ void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &dat
this->advertising_start(); this->advertising_start();
} }
void ESP32BLE::advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback) {
this->advertising_init_();
this->advertising_->register_raw_advertisement_callback(std::move(callback));
}
void ESP32BLE::advertising_add_service_uuid(ESPBTUUID uuid) { void ESP32BLE::advertising_add_service_uuid(ESPBTUUID uuid) {
this->advertising_init_(); this->advertising_init_();
this->advertising_->add_service_uuid(uuid); this->advertising_->add_service_uuid(uuid);
@ -102,7 +107,7 @@ bool ESP32BLE::ble_pre_setup_() {
void ESP32BLE::advertising_init_() { void ESP32BLE::advertising_init_() {
if (this->advertising_ != nullptr) if (this->advertising_ != nullptr)
return; return;
this->advertising_ = new BLEAdvertising(); // NOLINT(cppcoreguidelines-owning-memory) this->advertising_ = new BLEAdvertising(this->advertising_cycle_time_); // NOLINT(cppcoreguidelines-owning-memory)
this->advertising_->set_scan_response(true); this->advertising_->set_scan_response(true);
this->advertising_->set_min_preferred_interval(0x06); this->advertising_->set_min_preferred_interval(0x06);
@ -312,6 +317,9 @@ void ESP32BLE::loop() {
delete ble_event; // NOLINT(cppcoreguidelines-owning-memory) delete ble_event; // NOLINT(cppcoreguidelines-owning-memory)
ble_event = this->ble_events_.pop(); ble_event = this->ble_events_.pop();
} }
if (this->advertising_ != nullptr) {
this->advertising_->loop();
}
} }
void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {

View file

@ -3,6 +3,8 @@
#include "ble_advertising.h" #include "ble_advertising.h"
#include "ble_uuid.h" #include "ble_uuid.h"
#include <functional>
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
@ -76,6 +78,11 @@ class ESP32BLE : public Component {
public: public:
void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; } void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; }
void set_advertising_cycle_time(uint32_t advertising_cycle_time) {
this->advertising_cycle_time_ = advertising_cycle_time;
}
uint32_t get_advertising_cycle_time() const { return this->advertising_cycle_time_; }
void enable(); void enable();
void disable(); void disable();
bool is_active(); bool is_active();
@ -89,6 +96,7 @@ class ESP32BLE : public Component {
void advertising_set_manufacturer_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_add_service_uuid(ESPBTUUID uuid);
void advertising_remove_service_uuid(ESPBTUUID uuid); void advertising_remove_service_uuid(ESPBTUUID uuid);
void advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback);
void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); } 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_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); }
@ -121,6 +129,7 @@ class ESP32BLE : public Component {
Queue<BLEEvent> ble_events_; Queue<BLEEvent> ble_events_;
BLEAdvertising *advertising_; BLEAdvertising *advertising_;
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
uint32_t advertising_cycle_time_;
bool enable_on_boot_; bool enable_on_boot_;
}; };

View file

@ -10,9 +10,9 @@
namespace esphome { namespace esphome {
namespace esp32_ble { namespace esp32_ble {
static const char *const TAG = "esp32_ble"; static const char *const TAG = "esp32_ble.advertising";
BLEAdvertising::BLEAdvertising() { BLEAdvertising::BLEAdvertising(uint32_t advertising_cycle_time) : advertising_cycle_time_(advertising_cycle_time) {
this->advertising_data_.set_scan_rsp = false; this->advertising_data_.set_scan_rsp = false;
this->advertising_data_.include_name = true; this->advertising_data_.include_name = true;
this->advertising_data_.include_txpower = true; this->advertising_data_.include_txpower = true;
@ -64,7 +64,7 @@ void BLEAdvertising::set_manufacturer_data(const std::vector<uint8_t> &data) {
} }
} }
void BLEAdvertising::start() { esp_err_t BLEAdvertising::services_advertisement_() {
int num_services = this->advertising_uuids_.size(); int num_services = this->advertising_uuids_.size();
if (num_services == 0) { if (num_services == 0) {
this->advertising_data_.service_uuid_len = 0; this->advertising_data_.service_uuid_len = 0;
@ -87,8 +87,8 @@ void BLEAdvertising::start() {
this->advertising_data_.include_txpower = !this->scan_response_; this->advertising_data_.include_txpower = !this->scan_response_;
err = esp_ble_gap_config_adv_data(&this->advertising_data_); err = esp_ble_gap_config_adv_data(&this->advertising_data_);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Advertising): %d", err); ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Advertising): %s", esp_err_to_name(err));
return; return err;
} }
if (this->scan_response_) { if (this->scan_response_) {
@ -101,8 +101,8 @@ void BLEAdvertising::start() {
this->scan_response_data_.flag = 0; this->scan_response_data_.flag = 0;
err = esp_ble_gap_config_adv_data(&this->scan_response_data_); err = esp_ble_gap_config_adv_data(&this->scan_response_data_);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Scan response): %d", err); ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Scan response): %s", esp_err_to_name(err));
return; return err;
} }
} }
@ -113,8 +113,18 @@ void BLEAdvertising::start() {
err = esp_ble_gap_start_advertising(&this->advertising_params_); err = esp_ble_gap_start_advertising(&this->advertising_params_);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_start_advertising failed: %d", err); ESP_LOGE(TAG, "esp_ble_gap_start_advertising failed: %s", esp_err_to_name(err));
return; return err;
}
return ESP_OK;
}
void BLEAdvertising::start() {
if (this->current_adv_index_ == -1) {
this->services_advertisement_();
} else {
this->raw_advertisements_callbacks_[this->current_adv_index_](true);
} }
} }
@ -124,6 +134,29 @@ void BLEAdvertising::stop() {
ESP_LOGE(TAG, "esp_ble_gap_stop_advertising failed: %d", err); ESP_LOGE(TAG, "esp_ble_gap_stop_advertising failed: %d", err);
return; return;
} }
if (this->current_adv_index_ != -1) {
this->raw_advertisements_callbacks_[this->current_adv_index_](false);
}
}
void BLEAdvertising::loop() {
if (this->raw_advertisements_callbacks_.empty()) {
return;
}
const uint32_t now = millis();
if (now - this->last_advertisement_time_ > this->advertising_cycle_time_) {
this->stop();
this->current_adv_index_ += 1;
if (this->current_adv_index_ >= this->raw_advertisements_callbacks_.size()) {
this->current_adv_index_ = -1;
}
this->start();
this->last_advertisement_time_ = now;
}
}
void BLEAdvertising::register_raw_advertisement_callback(std::function<void(bool)> &&callback) {
this->raw_advertisements_callbacks_.push_back(std::move(callback));
} }
} // namespace esp32_ble } // namespace esp32_ble

View file

@ -1,20 +1,31 @@
#pragma once #pragma once
#include <array>
#include <functional>
#include <vector> #include <vector>
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_bt.h>
#include <esp_gap_ble_api.h> #include <esp_gap_ble_api.h>
#include <esp_gatts_api.h> #include <esp_gatts_api.h>
namespace esphome { namespace esphome {
namespace esp32_ble { namespace esp32_ble {
using raw_adv_data_t = struct {
uint8_t *data;
size_t length;
esp_power_level_t power_level;
};
class ESPBTUUID; class ESPBTUUID;
class BLEAdvertising { class BLEAdvertising {
public: public:
BLEAdvertising(); BLEAdvertising(uint32_t advertising_cycle_time);
void loop();
void add_service_uuid(ESPBTUUID uuid); void add_service_uuid(ESPBTUUID uuid);
void remove_service_uuid(ESPBTUUID uuid); void remove_service_uuid(ESPBTUUID uuid);
@ -22,16 +33,25 @@ class BLEAdvertising {
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; } void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
void set_manufacturer_data(const std::vector<uint8_t> &data); void set_manufacturer_data(const std::vector<uint8_t> &data);
void set_service_data(const std::vector<uint8_t> &data); void set_service_data(const std::vector<uint8_t> &data);
void register_raw_advertisement_callback(std::function<void(bool)> &&callback);
void start(); void start();
void stop(); void stop();
protected: protected:
esp_err_t services_advertisement_();
bool scan_response_; bool scan_response_;
esp_ble_adv_data_t advertising_data_; esp_ble_adv_data_t advertising_data_;
esp_ble_adv_data_t scan_response_data_; esp_ble_adv_data_t scan_response_data_;
esp_ble_adv_params_t advertising_params_; esp_ble_adv_params_t advertising_params_;
std::vector<ESPBTUUID> advertising_uuids_; std::vector<ESPBTUUID> advertising_uuids_;
std::vector<std::function<void(bool)>> raw_advertisements_callbacks_;
const uint32_t advertising_cycle_time_;
uint32_t last_advertisement_time_{0};
int8_t current_adv_index_{-1}; // -1 means standard scan response
}; };
} // namespace esp32_ble } // namespace esp32_ble

View file

@ -1,16 +1,21 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, CONF_TX_POWER
from esphome.core import CORE, TimePeriod
from esphome.components.esp32 import add_idf_sdkconfig_option
from esphome.components import esp32_ble from esphome.components import esp32_ble
from esphome.components.esp32 import add_idf_sdkconfig_option
from esphome.components.esp32_ble import CONF_BLE_ID
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_TX_POWER, CONF_TYPE, CONF_UUID
from esphome.core import CORE, TimePeriod
AUTO_LOAD = ["esp32_ble"]
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
CONFLICTS_WITH = ["esp32_ble_tracker"]
esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon") esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon")
ESP32BLEBeacon = esp32_ble_beacon_ns.class_("ESP32BLEBeacon", cg.Component) ESP32BLEBeacon = esp32_ble_beacon_ns.class_(
"ESP32BLEBeacon",
cg.Component,
esp32_ble.GAPEventHandler,
cg.Parented.template(esp32_ble.ESP32BLE),
)
CONF_MAJOR = "major" CONF_MAJOR = "major"
CONF_MINOR = "minor" CONF_MINOR = "minor"
CONF_MIN_INTERVAL = "min_interval" CONF_MIN_INTERVAL = "min_interval"
@ -28,6 +33,7 @@ CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(ESP32BLEBeacon), cv.GenerateID(): cv.declare_id(ESP32BLEBeacon),
cv.GenerateID(CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True), cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True),
cv.Required(CONF_UUID): cv.uuid, cv.Required(CONF_UUID): cv.uuid,
cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t, cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t,
@ -48,7 +54,7 @@ CONFIG_SCHEMA = cv.All(
min=-128, max=0 min=-128, max=0
), ),
cv.Optional(CONF_TX_POWER, default="3dBm"): cv.All( cv.Optional(CONF_TX_POWER, default="3dBm"): cv.All(
cv.decibel, cv.one_of(-12, -9, -6, -3, 0, 3, 6, 9, int=True) cv.decibel, cv.enum(esp32_ble.TX_POWER_LEVELS, int=True)
), ),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
@ -62,6 +68,10 @@ async def to_code(config):
uuid = config[CONF_UUID].hex uuid = config[CONF_UUID].hex
uuid_arr = [cg.RawExpression(f"0x{uuid[i:i + 2]}") for i in range(0, len(uuid), 2)] uuid_arr = [cg.RawExpression(f"0x{uuid[i:i + 2]}") for i in range(0, len(uuid), 2)]
var = cg.new_Pvariable(config[CONF_ID], uuid_arr) var = cg.new_Pvariable(config[CONF_ID], uuid_arr)
parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID])
cg.add(parent.register_gap_event_handler(var))
await cg.register_component(var, config) await cg.register_component(var, config)
cg.add(var.set_major(config[CONF_MAJOR])) cg.add(var.set_major(config[CONF_MAJOR]))
cg.add(var.set_minor(config[CONF_MINOR])) cg.add(var.set_minor(config[CONF_MINOR]))

View file

@ -3,14 +3,16 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <nvs_flash.h>
#include <freertos/FreeRTOS.h>
#include <esp_bt_main.h>
#include <esp_bt.h> #include <esp_bt.h>
#include <freertos/task.h> #include <esp_bt_main.h>
#include <esp_gap_ble_api.h> #include <esp_gap_ble_api.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <nvs_flash.h>
#include <cstring> #include <cstring>
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#include <esp32-hal-bt.h> #include <esp32-hal-bt.h>
@ -21,20 +23,6 @@ namespace esp32_ble_beacon {
static const char *const TAG = "esp32_ble_beacon"; static const char *const TAG = "esp32_ble_beacon";
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static esp_ble_adv_params_t ble_adv_params = {
.adv_int_min = 0x20,
.adv_int_max = 0x40,
.adv_type = ADV_TYPE_NONCONN_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.peer_addr = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
.peer_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
#define ENDIAN_CHANGE_U16(x) ((((x) &0xFF00) >> 8) + (((x) &0xFF) << 8))
static const esp_ble_ibeacon_head_t IBEACON_COMMON_HEAD = { static const esp_ble_ibeacon_head_t IBEACON_COMMON_HEAD = {
.flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = {0x4C, 0x00}, .beacon_type = {0x02, 0x15}}; .flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = {0x4C, 0x00}, .beacon_type = {0x02, 0x15}};
@ -53,117 +41,62 @@ void ESP32BLEBeacon::dump_config() {
" UUID: %s, Major: %u, Minor: %u, Min Interval: %ums, Max Interval: %ums, Measured Power: %d" " UUID: %s, Major: %u, Minor: %u, Min Interval: %ums, Max Interval: %ums, Measured Power: %d"
", TX Power: %ddBm", ", TX Power: %ddBm",
uuid, this->major_, this->minor_, this->min_interval_, this->max_interval_, this->measured_power_, uuid, this->major_, this->minor_, this->min_interval_, this->max_interval_, this->measured_power_,
this->tx_power_); (this->tx_power_ * 3) - 12);
} }
float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
void ESP32BLEBeacon::setup() { void ESP32BLEBeacon::setup() {
ESP_LOGCONFIG(TAG, "Setting up ESP32 BLE beacon..."); this->ble_adv_params_ = {
global_esp32_ble_beacon = this; .adv_int_min = static_cast<uint16_t>(this->min_interval_ / 0.625f),
.adv_int_max = static_cast<uint16_t>(this->max_interval_ / 0.625f),
.adv_type = ADV_TYPE_NONCONN_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.peer_addr = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
.peer_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
xTaskCreatePinnedToCore(ESP32BLEBeacon::ble_core_task, global_ble->advertising_register_raw_advertisement_callback([this](bool advertise) {
"ble_task", // name this->advertising_ = advertise;
10000, // stack size (in words) if (advertise) {
nullptr, // input params this->on_advertise_();
1, // priority }
nullptr, // Handle, not needed });
0 // core
);
} }
float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::BLUETOOTH; } void ESP32BLEBeacon::on_advertise_() {
void ESP32BLEBeacon::ble_core_task(void *params) {
ble_setup();
while (true) {
delay(1000); // NOLINT
}
}
void ESP32BLEBeacon::ble_setup() {
ble_adv_params.adv_int_min = static_cast<uint16_t>(global_esp32_ble_beacon->min_interval_ / 0.625f);
ble_adv_params.adv_int_max = static_cast<uint16_t>(global_esp32_ble_beacon->max_interval_ / 0.625f);
// Initialize non-volatile storage for the bluetooth controller
esp_err_t err = nvs_flash_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "nvs_flash_init failed: %d", err);
return;
}
#ifdef USE_ARDUINO
if (!btStart()) {
ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
return;
}
#else
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
// start bt controller
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
err = esp_bt_controller_init(&cfg);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err));
return;
}
while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE)
;
}
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) {
err = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err));
return;
}
}
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
ESP_LOGE(TAG, "esp bt controller enable failed");
return;
}
}
#endif
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
err = esp_bluedroid_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err);
return;
}
err = esp_bluedroid_enable();
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err);
return;
}
err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV,
static_cast<esp_power_level_t>((global_esp32_ble_beacon->tx_power_ + 12) / 3));
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err));
return;
}
err = esp_ble_gap_register_callback(ESP32BLEBeacon::gap_event_handler);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err);
return;
}
esp_ble_ibeacon_t ibeacon_adv_data; esp_ble_ibeacon_t ibeacon_adv_data;
memcpy(&ibeacon_adv_data.ibeacon_head, &IBEACON_COMMON_HEAD, sizeof(esp_ble_ibeacon_head_t)); memcpy(&ibeacon_adv_data.ibeacon_head, &IBEACON_COMMON_HEAD, sizeof(esp_ble_ibeacon_head_t));
memcpy(&ibeacon_adv_data.ibeacon_vendor.proximity_uuid, global_esp32_ble_beacon->uuid_.data(), memcpy(&ibeacon_adv_data.ibeacon_vendor.proximity_uuid, this->uuid_.data(),
sizeof(ibeacon_adv_data.ibeacon_vendor.proximity_uuid)); sizeof(ibeacon_adv_data.ibeacon_vendor.proximity_uuid));
ibeacon_adv_data.ibeacon_vendor.minor = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->minor_); ibeacon_adv_data.ibeacon_vendor.minor = byteswap(this->minor_);
ibeacon_adv_data.ibeacon_vendor.major = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->major_); ibeacon_adv_data.ibeacon_vendor.major = byteswap(this->major_);
ibeacon_adv_data.ibeacon_vendor.measured_power = static_cast<uint8_t>(global_esp32_ble_beacon->measured_power_); ibeacon_adv_data.ibeacon_vendor.measured_power = static_cast<uint8_t>(this->measured_power_);
esp_ble_gap_config_adv_data_raw((uint8_t *) &ibeacon_adv_data, sizeof(ibeacon_adv_data)); ESP_LOGD(TAG, "Setting BLE TX power");
esp_err_t err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, this->tx_power_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err));
}
err = esp_ble_gap_config_adv_data_raw((uint8_t *) &ibeacon_adv_data, sizeof(ibeacon_adv_data));
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_config_adv_data_raw failed: %s", esp_err_to_name(err));
return;
}
} }
void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
if (!this->advertising_)
return;
esp_err_t err; esp_err_t err;
switch (event) { switch (event) {
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: { case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: {
err = esp_ble_gap_start_advertising(&ble_adv_params); err = esp_ble_gap_start_advertising(&this->ble_adv_params_);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_start_advertising failed: %d", err); ESP_LOGE(TAG, "esp_ble_gap_start_advertising failed: %s", esp_err_to_name(err));
} }
break; break;
} }
@ -181,6 +114,7 @@ void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap
} else { } else {
ESP_LOGD(TAG, "BLE stopped advertising successfully"); ESP_LOGD(TAG, "BLE stopped advertising successfully");
} }
// this->advertising_ = false;
break; break;
} }
default: default:
@ -188,8 +122,6 @@ void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap
} }
} }
ESP32BLEBeacon *global_esp32_ble_beacon = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace esp32_ble_beacon } // namespace esp32_ble_beacon
} // namespace esphome } // namespace esphome

View file

@ -1,39 +1,39 @@
#pragma once #pragma once
#include "esphome/components/esp32_ble/ble.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_gap_ble_api.h>
#include <esp_bt.h> #include <esp_bt.h>
#include <esp_gap_ble_api.h>
namespace esphome { namespace esphome {
namespace esp32_ble_beacon { namespace esp32_ble_beacon {
// NOLINTNEXTLINE(modernize-use-using) using esp_ble_ibeacon_head_t = struct {
typedef struct {
uint8_t flags[3]; uint8_t flags[3];
uint8_t length; uint8_t length;
uint8_t type; uint8_t type;
uint8_t company_id[2]; uint8_t company_id[2];
uint8_t beacon_type[2]; uint8_t beacon_type[2];
} __attribute__((packed)) esp_ble_ibeacon_head_t; } __attribute__((packed));
// NOLINTNEXTLINE(modernize-use-using) using esp_ble_ibeacon_vendor_t = struct {
typedef struct {
uint8_t proximity_uuid[16]; uint8_t proximity_uuid[16];
uint16_t major; uint16_t major;
uint16_t minor; uint16_t minor;
uint8_t measured_power; uint8_t measured_power;
} __attribute__((packed)) esp_ble_ibeacon_vendor_t; } __attribute__((packed));
// NOLINTNEXTLINE(modernize-use-using) using esp_ble_ibeacon_t = struct {
typedef struct {
esp_ble_ibeacon_head_t ibeacon_head; esp_ble_ibeacon_head_t ibeacon_head;
esp_ble_ibeacon_vendor_t ibeacon_vendor; esp_ble_ibeacon_vendor_t ibeacon_vendor;
} __attribute__((packed)) esp_ble_ibeacon_t; } __attribute__((packed));
class ESP32BLEBeacon : public Component { using namespace esp32_ble;
class ESP32BLEBeacon : public Component, public GAPEventHandler, public Parented<ESP32BLE> {
public: public:
explicit ESP32BLEBeacon(const std::array<uint8_t, 16> &uuid) : uuid_(uuid) {} explicit ESP32BLEBeacon(const std::array<uint8_t, 16> &uuid) : uuid_(uuid) {}
@ -46,12 +46,11 @@ class ESP32BLEBeacon : public Component {
void set_min_interval(uint16_t val) { this->min_interval_ = val; } void set_min_interval(uint16_t val) { this->min_interval_ = val; }
void set_max_interval(uint16_t val) { this->max_interval_ = val; } void set_max_interval(uint16_t val) { this->max_interval_ = val; }
void set_measured_power(int8_t val) { this->measured_power_ = val; } void set_measured_power(int8_t val) { this->measured_power_ = val; }
void set_tx_power(int8_t val) { this->tx_power_ = val; } void set_tx_power(esp_power_level_t val) { this->tx_power_ = val; }
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
protected: protected:
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); void on_advertise_();
static void ble_core_task(void *params);
static void ble_setup();
std::array<uint8_t, 16> uuid_; std::array<uint8_t, 16> uuid_;
uint16_t major_{}; uint16_t major_{};
@ -59,12 +58,11 @@ class ESP32BLEBeacon : public Component {
uint16_t min_interval_{}; uint16_t min_interval_{};
uint16_t max_interval_{}; uint16_t max_interval_{};
int8_t measured_power_{}; int8_t measured_power_{};
int8_t tx_power_{}; esp_power_level_t tx_power_{};
esp_ble_adv_params_t ble_adv_params_;
bool advertising_{false};
}; };
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
extern ESP32BLEBeacon *global_esp32_ble_beacon;
} // namespace esp32_ble_beacon } // namespace esp32_ble_beacon
} // namespace esphome } // namespace esphome

View file

@ -1,5 +1,4 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32_ble_tracker from esphome.components import esp32_ble_tracker
AUTO_LOAD = ["esp32_ble_tracker"] AUTO_LOAD = ["esp32_ble_tracker"]

View file

@ -1,13 +1,12 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32_ble
from esphome.components.esp32 import add_idf_sdkconfig_option
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MODEL from esphome.const import CONF_ID, CONF_MODEL
from esphome.components import esp32_ble
from esphome.core import CORE from esphome.core import CORE
from esphome.components.esp32 import add_idf_sdkconfig_option
AUTO_LOAD = ["esp32_ble"] AUTO_LOAD = ["esp32_ble"]
CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"] CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"]
CONFLICTS_WITH = ["esp32_ble_beacon"]
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
CONF_MANUFACTURER = "manufacturer" CONF_MANUFACTURER = "manufacturer"

View file

@ -1,10 +1,10 @@
import re import re
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
import esphome.codegen as cg
from esphome.components import esp32_ble from esphome.components import esp32_ble
from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.components.esp32 import add_idf_sdkconfig_option
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ACTIVE, CONF_ACTIVE,
CONF_DURATION, CONF_DURATION,

View file

@ -11,6 +11,7 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2, VARIANT_ESP32S2,
VARIANT_ESP32S3, VARIANT_ESP32S3,
VARIANT_ESP32C3, VARIANT_ESP32C3,
VARIANT_ESP32C6,
VARIANT_ESP32H2, VARIANT_ESP32H2,
) )
@ -47,6 +48,7 @@ CAN_SPEEDS_ESP32_S2 = {
CAN_SPEEDS_ESP32_S3 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS_ESP32_S3 = {**CAN_SPEEDS_ESP32_S2}
CAN_SPEEDS_ESP32_C3 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS_ESP32_C3 = {**CAN_SPEEDS_ESP32_S2}
CAN_SPEEDS_ESP32_C6 = {**CAN_SPEEDS_ESP32_S2}
CAN_SPEEDS_ESP32_H2 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS_ESP32_H2 = {**CAN_SPEEDS_ESP32_S2}
CAN_SPEEDS = { CAN_SPEEDS = {
@ -54,6 +56,7 @@ CAN_SPEEDS = {
VARIANT_ESP32S2: CAN_SPEEDS_ESP32_S2, VARIANT_ESP32S2: CAN_SPEEDS_ESP32_S2,
VARIANT_ESP32S3: CAN_SPEEDS_ESP32_S3, VARIANT_ESP32S3: CAN_SPEEDS_ESP32_S3,
VARIANT_ESP32C3: CAN_SPEEDS_ESP32_C3, VARIANT_ESP32C3: CAN_SPEEDS_ESP32_C3,
VARIANT_ESP32C6: CAN_SPEEDS_ESP32_C6,
VARIANT_ESP32H2: CAN_SPEEDS_ESP32_H2, VARIANT_ESP32H2: CAN_SPEEDS_ESP32_H2,
} }

View file

@ -6,7 +6,6 @@ from esphome.const import CONF_ID
AUTO_LOAD = ["esp32_ble_server"] AUTO_LOAD = ["esp32_ble_server"]
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
CONFLICTS_WITH = ["esp32_ble_beacon"]
DEPENDENCIES = ["wifi", "esp32"] DEPENDENCIES = ["wifi", "esp32"]
CONF_AUTHORIZED_DURATION = "authorized_duration" CONF_AUTHORIZED_DURATION = "authorized_duration"

View file

@ -1,10 +1,17 @@
import logging
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent
from esphome.config_helpers import merge_config
from esphome.const import ( from esphome.const import (
CONF_ESPHOME,
CONF_ID, CONF_ID,
CONF_NUM_ATTEMPTS, CONF_NUM_ATTEMPTS,
CONF_OTA,
CONF_PASSWORD, CONF_PASSWORD,
CONF_PLATFORM,
CONF_PORT, CONF_PORT,
CONF_REBOOT_TIMEOUT, CONF_REBOOT_TIMEOUT,
CONF_SAFE_MODE, CONF_SAFE_MODE,
@ -12,6 +19,8 @@ from esphome.const import (
) )
from esphome.core import coroutine_with_priority from esphome.core import coroutine_with_priority
_LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
AUTO_LOAD = ["md5", "socket"] AUTO_LOAD = ["md5", "socket"]
@ -21,6 +30,65 @@ esphome = cg.esphome_ns.namespace("esphome")
ESPHomeOTAComponent = esphome.class_("ESPHomeOTAComponent", OTAComponent) ESPHomeOTAComponent = esphome.class_("ESPHomeOTAComponent", OTAComponent)
def ota_esphome_final_validate(config):
full_conf = fv.full_config.get()
full_ota_conf = full_conf[CONF_OTA]
new_ota_conf = []
merged_ota_esphome_configs_by_port = {}
ports_with_merged_configs = []
for ota_conf in full_ota_conf:
if ota_conf.get(CONF_PLATFORM) == CONF_ESPHOME:
if (
conf_port := ota_conf.get(CONF_PORT)
) not in merged_ota_esphome_configs_by_port:
merged_ota_esphome_configs_by_port[conf_port] = ota_conf
else:
if merged_ota_esphome_configs_by_port[conf_port][
CONF_VERSION
] != ota_conf.get(CONF_VERSION):
raise cv.Invalid(
f"Found multiple configurations but {CONF_VERSION} is inconsistent"
)
if (
merged_ota_esphome_configs_by_port[conf_port][CONF_ID].is_manual
and ota_conf.get(CONF_ID).is_manual
):
raise cv.Invalid(
f"Found multiple configurations but {CONF_ID} is inconsistent"
)
if (
CONF_PASSWORD in merged_ota_esphome_configs_by_port[conf_port]
and CONF_PASSWORD in ota_conf
and merged_ota_esphome_configs_by_port[conf_port][CONF_PASSWORD]
!= ota_conf.get(CONF_PASSWORD)
):
raise cv.Invalid(
f"Found multiple configurations but {CONF_PASSWORD} is inconsistent"
)
ports_with_merged_configs.append(conf_port)
merged_ota_esphome_configs_by_port[conf_port] = merge_config(
merged_ota_esphome_configs_by_port[conf_port], ota_conf
)
else:
new_ota_conf.append(ota_conf)
for port_conf in merged_ota_esphome_configs_by_port.values():
new_ota_conf.append(port_conf)
full_conf[CONF_OTA] = new_ota_conf
fv.full_config.set(full_conf)
if len(ports_with_merged_configs) > 0:
_LOGGER.warning(
"Found and merged multiple configurations for %s %s %s port(s) %s",
CONF_OTA,
CONF_PLATFORM,
CONF_ESPHOME,
ports_with_merged_configs,
)
CONFIG_SCHEMA = ( CONFIG_SCHEMA = (
cv.Schema( cv.Schema(
{ {
@ -50,6 +118,8 @@ CONFIG_SCHEMA = (
.extend(cv.COMPONENT_SCHEMA) .extend(cv.COMPONENT_SCHEMA)
) )
FINAL_VALIDATE_SCHEMA = ota_esphome_final_validate
@coroutine_with_priority(52.0) @coroutine_with_priority(52.0)
async def to_code(config): async def to_code(config):

View file

@ -1,6 +1,4 @@
from esphome import pins from esphome import pins
import esphome.config_validation as cv
import esphome.final_validate as fv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
from esphome.components.esp32.const import ( from esphome.components.esp32.const import (
@ -8,31 +6,33 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2, VARIANT_ESP32S2,
VARIANT_ESP32S3, VARIANT_ESP32S3,
) )
from esphome.components.network import IPAddress
from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DOMAIN, CONF_ADDRESS,
CONF_ID, CONF_CLK_PIN,
CONF_VALUE, CONF_CS_PIN,
CONF_MANUAL_IP,
CONF_STATIC_IP,
CONF_TYPE,
CONF_USE_ADDRESS,
CONF_GATEWAY,
CONF_SUBNET,
CONF_DNS1, CONF_DNS1,
CONF_DNS2, CONF_DNS2,
CONF_CLK_PIN, CONF_DOMAIN,
CONF_GATEWAY,
CONF_ID,
CONF_INTERRUPT_PIN,
CONF_MANUAL_IP,
CONF_MISO_PIN, CONF_MISO_PIN,
CONF_MOSI_PIN, CONF_MOSI_PIN,
CONF_CS_PIN, CONF_PAGE_ID,
CONF_INTERRUPT_PIN,
CONF_RESET_PIN, CONF_RESET_PIN,
CONF_SPI, CONF_SPI,
CONF_PAGE_ID, CONF_STATIC_IP,
CONF_ADDRESS, CONF_SUBNET,
CONF_TYPE,
CONF_USE_ADDRESS,
CONF_VALUE,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.components.network import IPAddress import esphome.final_validate as fv
from esphome.components.spi import get_spi_interface, CONF_INTERFACE_INDEX
CONFLICTS_WITH = ["wifi"] CONFLICTS_WITH = ["wifi"]
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]

View file

@ -65,7 +65,8 @@ void EthernetComponent::setup() {
.intr_flags = 0, .intr_flags = 0,
}; };
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
defined(USE_ESP32_VARIANT_ESP32C6)
auto host = SPI2_HOST; auto host = SPI2_HOST;
#else #else
auto host = SPI3_HOST; auto host = SPI3_HOST;
@ -393,7 +394,7 @@ void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_b
const esp_netif_ip_info_t *ip_info = &event->ip_info; const esp_netif_ip_info_t *ip_info = &event->ip_info;
ESP_LOGV(TAG, "[Ethernet event] ETH Got IP " IPSTR, IP2STR(&ip_info->ip)); ESP_LOGV(TAG, "[Ethernet event] ETH Got IP " IPSTR, IP2STR(&ip_info->ip));
global_eth_component->got_ipv4_address_ = true; global_eth_component->got_ipv4_address_ = true;
#if USE_NETWORK_IPV6 #if USE_NETWORK_IPV6 && (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0)
global_eth_component->connected_ = global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT; global_eth_component->connected_ = global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT;
#else #else
global_eth_component->connected_ = true; global_eth_component->connected_ = true;
@ -406,8 +407,12 @@ void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_
ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data; ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data;
ESP_LOGV(TAG, "[Ethernet event] ETH Got IPv6: " IPV6STR, IPV62STR(event->ip6_info.ip)); ESP_LOGV(TAG, "[Ethernet event] ETH Got IPv6: " IPV6STR, IPV62STR(event->ip6_info.ip));
global_eth_component->ipv6_count_ += 1; global_eth_component->ipv6_count_ += 1;
#if (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0)
global_eth_component->connected_ = global_eth_component->connected_ =
global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT); global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT);
#else
global_eth_component->connected_ = global_eth_component->got_ipv4_address_;
#endif
} }
#endif /* USE_NETWORK_IPV6 */ #endif /* USE_NETWORK_IPV6 */

View file

@ -1,9 +1,9 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_IP_ADDRESS,
CONF_DNS_ADDRESS, CONF_DNS_ADDRESS,
CONF_IP_ADDRESS,
CONF_MAC_ADDRESS, CONF_MAC_ADDRESS,
ENTITY_CATEGORY_DIAGNOSTIC, ENTITY_CATEGORY_DIAGNOSTIC,
) )

View file

@ -45,8 +45,8 @@ void FanCall::validate_() {
this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count()); this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count());
if (this->binary_state_.has_value() && *this->binary_state_) { if (this->binary_state_.has_value() && *this->binary_state_) {
// when turning on, if current speed is zero, set speed to 100% // when turning on, if neither current nor new speed available, set speed to 100%
if (traits.supports_speed() && !this->parent_.state && this->parent_.speed == 0) { if (traits.supports_speed() && !this->parent_.state && this->parent_.speed == 0 && !this->speed_.has_value()) {
this->speed_ = traits.supported_speed_count(); this->speed_ = traits.supported_speed_count();
} }
} }

View file

@ -17,7 +17,6 @@ from esphome.helpers import (
cpp_string_escape, cpp_string_escape,
) )
from esphome.const import ( from esphome.const import (
__version__,
CONF_FAMILY, CONF_FAMILY,
CONF_FILE, CONF_FILE,
CONF_GLYPHS, CONF_GLYPHS,
@ -185,31 +184,6 @@ def get_font_path(value, type) -> Path:
return None return None
def download_content(url: str, path: Path) -> None:
if not external_files.has_remote_file_changed(url, path):
_LOGGER.debug("Remote file has not changed %s", url)
return
_LOGGER.debug(
"Remote file has changed, downloading from %s to %s",
url,
path,
)
try:
req = requests.get(
url,
timeout=external_files.NETWORK_TIMEOUT,
headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"},
)
req.raise_for_status()
except requests.exceptions.RequestException as e:
raise cv.Invalid(f"Could not download from {url}: {e}")
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(req.content)
def download_gfont(value): def download_gfont(value):
name = ( name = (
f"{value[CONF_FAMILY]}:ital,wght@{int(value[CONF_ITALIC])},{value[CONF_WEIGHT]}" f"{value[CONF_FAMILY]}:ital,wght@{int(value[CONF_ITALIC])},{value[CONF_WEIGHT]}"
@ -236,7 +210,7 @@ def download_gfont(value):
ttf_url = match.group(1) ttf_url = match.group(1)
_LOGGER.debug("download_gfont: ttf_url=%s", ttf_url) _LOGGER.debug("download_gfont: ttf_url=%s", ttf_url)
download_content(ttf_url, path) external_files.download_content(ttf_url, path)
return value return value
@ -244,7 +218,7 @@ def download_web_font(value):
url = value[CONF_URL] url = value[CONF_URL]
path = get_font_path(value, TYPE_WEB) path = get_font_path(value, TYPE_WEB)
download_content(url, path) external_files.download_content(url, path)
_LOGGER.debug("download_web_font: path=%s", path) _LOGGER.debug("download_web_font: path=%s", path)
return value return value

View file

@ -16,6 +16,7 @@ MODELS = {
"yan": Model.GREE_YAN, "yan": Model.GREE_YAN,
"yaa": Model.GREE_YAA, "yaa": Model.GREE_YAA,
"yac": Model.GREE_YAC, "yac": Model.GREE_YAC,
"yac1fb9": Model.GREE_YAC1FB9,
} }
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(

View file

@ -24,7 +24,7 @@ void GreeClimate::transmit_state() {
remote_state[4] |= (this->horizontal_swing_() << 4); remote_state[4] |= (this->horizontal_swing_() << 4);
} }
if (this->model_ == GREE_YAA || this->model_ == GREE_YAC) { if (this->model_ == GREE_YAA || this->model_ == GREE_YAC || this->model_ == GREE_YAC1FB9) {
remote_state[2] = 0x20; // bits 0..3 always 0000, bits 4..7 TURBO,LIGHT,HEALTH,X-FAN 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[3] = 0x50; // bits 4..7 always 0101
remote_state[6] = 0x20; // YAA1FB, FAA1FB1, YB1F2 bits 4..7 always 0010 remote_state[6] = 0x20; // YAA1FB, FAA1FB1, YB1F2 bits 4..7 always 0010
@ -53,7 +53,11 @@ void GreeClimate::transmit_state() {
data->set_carrier_frequency(GREE_IR_FREQUENCY); data->set_carrier_frequency(GREE_IR_FREQUENCY);
data->mark(GREE_HEADER_MARK); data->mark(GREE_HEADER_MARK);
if (this->model_ == GREE_YAC1FB9) {
data->space(GREE_YAC1FB9_HEADER_SPACE);
} else {
data->space(GREE_HEADER_SPACE); data->space(GREE_HEADER_SPACE);
}
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
@ -71,7 +75,11 @@ void GreeClimate::transmit_state() {
data->space(GREE_ZERO_SPACE); data->space(GREE_ZERO_SPACE);
data->mark(GREE_BIT_MARK); data->mark(GREE_BIT_MARK);
if (this->model_ == GREE_YAC1FB9) {
data->space(GREE_YAC1FB9_MESSAGE_SPACE);
} else {
data->space(GREE_MESSAGE_SPACE); data->space(GREE_MESSAGE_SPACE);
}
for (int i = 4; i < 8; i++) { for (int i = 4; i < 8; i++) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask

View file

@ -41,6 +41,10 @@ const uint32_t GREE_YAC_HEADER_MARK = 6000;
const uint32_t GREE_YAC_HEADER_SPACE = 3000; const uint32_t GREE_YAC_HEADER_SPACE = 3000;
const uint32_t GREE_YAC_BIT_MARK = 650; const uint32_t GREE_YAC_BIT_MARK = 650;
// Timing specific to YAC1FB9
const uint32_t GREE_YAC1FB9_HEADER_SPACE = 4500;
const uint32_t GREE_YAC1FB9_MESSAGE_SPACE = 19980;
// State Frame size // State Frame size
const uint8_t GREE_STATE_FRAME_SIZE = 8; const uint8_t GREE_STATE_FRAME_SIZE = 8;
@ -67,7 +71,7 @@ const uint8_t GREE_HDIR_MRIGHT = 0x05;
const uint8_t GREE_HDIR_RIGHT = 0x06; const uint8_t GREE_HDIR_RIGHT = 0x06;
// Model codes // Model codes
enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC }; enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9 };
class GreeClimate : public climate_ir::ClimateIR { class GreeClimate : public climate_ir::ClimateIR {
public: public:

View file

@ -38,6 +38,9 @@ PROTOCOL_MAX_TEMPERATURE = 30.0
PROTOCOL_TARGET_TEMPERATURE_STEP = 1.0 PROTOCOL_TARGET_TEMPERATURE_STEP = 1.0
PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5 PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5
PROTOCOL_CONTROL_PACKET_SIZE = 10 PROTOCOL_CONTROL_PACKET_SIZE = 10
PROTOCOL_MIN_SENSORS_PACKET_SIZE = 18
PROTOCOL_DEFAULT_SENSORS_PACKET_SIZE = 22
PROTOCOL_STATUS_MESSAGE_HEADER_SIZE = 0
CODEOWNERS = ["@paveldn"] CODEOWNERS = ["@paveldn"]
DEPENDENCIES = ["climate", "uart"] DEPENDENCIES = ["climate", "uart"]
@ -48,6 +51,9 @@ CONF_CONTROL_PACKET_SIZE = "control_packet_size"
CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow" CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow"
CONF_ON_ALARM_START = "on_alarm_start" CONF_ON_ALARM_START = "on_alarm_start"
CONF_ON_ALARM_END = "on_alarm_end" CONF_ON_ALARM_END = "on_alarm_end"
CONF_ON_STATUS_MESSAGE = "on_status_message"
CONF_SENSORS_PACKET_SIZE = "sensors_packet_size"
CONF_STATUS_MESSAGE_HEADER_SIZE = "status_message_header_size"
CONF_VERTICAL_AIRFLOW = "vertical_airflow" CONF_VERTICAL_AIRFLOW = "vertical_airflow"
CONF_WIFI_SIGNAL = "wifi_signal" CONF_WIFI_SIGNAL = "wifi_signal"
@ -129,6 +135,11 @@ HaierAlarmEndTrigger = haier_ns.class_(
automation.Trigger.template(cg.uint8, cg.const_char_ptr), automation.Trigger.template(cg.uint8, cg.const_char_ptr),
) )
StatusMessageTrigger = haier_ns.class_(
"StatusMessageTrigger",
automation.Trigger.template(cg.const_char_ptr, cg.size_t),
)
def validate_visual(config): def validate_visual(config):
if CONF_VISUAL in config: if CONF_VISUAL in config:
@ -193,6 +204,11 @@ BASE_CONFIG_SCHEMA = (
cv.Optional( cv.Optional(
CONF_ANSWER_TIMEOUT, CONF_ANSWER_TIMEOUT,
): cv.positive_time_period_milliseconds, ): cv.positive_time_period_milliseconds,
cv.Optional(CONF_ON_STATUS_MESSAGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StatusMessageTrigger),
}
),
} }
) )
.extend(uart.UART_DEVICE_SCHEMA) .extend(uart.UART_DEVICE_SCHEMA)
@ -228,6 +244,14 @@ CONFIG_SCHEMA = cv.All(
cv.Optional( cv.Optional(
CONF_CONTROL_PACKET_SIZE, default=PROTOCOL_CONTROL_PACKET_SIZE CONF_CONTROL_PACKET_SIZE, default=PROTOCOL_CONTROL_PACKET_SIZE
): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50), ): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50),
cv.Optional(
CONF_SENSORS_PACKET_SIZE,
default=PROTOCOL_DEFAULT_SENSORS_PACKET_SIZE,
): cv.int_range(min=PROTOCOL_MIN_SENSORS_PACKET_SIZE, max=50),
cv.Optional(
CONF_STATUS_MESSAGE_HEADER_SIZE,
default=PROTOCOL_STATUS_MESSAGE_HEADER_SIZE,
): cv.int_range(min=PROTOCOL_STATUS_MESSAGE_HEADER_SIZE),
cv.Optional( cv.Optional(
CONF_SUPPORTED_PRESETS, CONF_SUPPORTED_PRESETS,
default=["BOOST", "ECO", "SLEEP"], # No AWAY by default default=["BOOST", "ECO", "SLEEP"], # No AWAY by default
@ -468,6 +492,16 @@ async def to_code(config):
config[CONF_CONTROL_PACKET_SIZE] - PROTOCOL_CONTROL_PACKET_SIZE config[CONF_CONTROL_PACKET_SIZE] - PROTOCOL_CONTROL_PACKET_SIZE
) )
) )
if CONF_SENSORS_PACKET_SIZE in config:
cg.add(
var.set_extra_sensors_packet_bytes_size(
config[CONF_SENSORS_PACKET_SIZE] - PROTOCOL_MIN_SENSORS_PACKET_SIZE
)
)
if CONF_STATUS_MESSAGE_HEADER_SIZE in config:
cg.add(
var.set_status_message_header_size(config[CONF_STATUS_MESSAGE_HEADER_SIZE])
)
for conf in config.get(CONF_ON_ALARM_START, []): for conf in config.get(CONF_ON_ALARM_START, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation( await automation.build_automation(
@ -478,5 +512,10 @@ async def to_code(config):
await automation.build_automation( await automation.build_automation(
trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf
) )
for conf in config.get(CONF_ON_STATUS_MESSAGE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.const_char_ptr, "data"), (cg.size_t, "data_size")], conf
)
# https://github.com/paveldn/HaierProtocol # https://github.com/paveldn/HaierProtocol
cg.add_library("pavlodn/HaierProtocol", "0.9.28") cg.add_library("pavlodn/HaierProtocol", "0.9.31")

View file

@ -186,6 +186,10 @@ void HaierClimateBase::send_custom_command(const haier_protocol::HaierMessage &m
this->action_request_ = PendingAction({ActionRequest::SEND_CUSTOM_COMMAND, message}); this->action_request_ = PendingAction({ActionRequest::SEND_CUSTOM_COMMAND, message});
} }
void HaierClimateBase::add_status_message_callback(std::function<void(const char *, size_t)> &&callback) {
this->status_message_callback_.add(std::move(callback));
}
haier_protocol::HandlerError HaierClimateBase::answer_preprocess_( haier_protocol::HandlerError HaierClimateBase::answer_preprocess_(
haier_protocol::FrameType request_message_type, haier_protocol::FrameType expected_request_message_type, haier_protocol::FrameType request_message_type, haier_protocol::FrameType expected_request_message_type,
haier_protocol::FrameType answer_message_type, haier_protocol::FrameType expected_answer_message_type, haier_protocol::FrameType answer_message_type, haier_protocol::FrameType expected_answer_message_type,

View file

@ -4,6 +4,7 @@
#include <set> #include <set>
#include "esphome/components/climate/climate.h" #include "esphome/components/climate/climate.h"
#include "esphome/components/uart/uart.h" #include "esphome/components/uart/uart.h"
#include "esphome/core/automation.h"
// HaierProtocol // HaierProtocol
#include <protocol/haier_protocol.h> #include <protocol/haier_protocol.h>
@ -56,6 +57,7 @@ class HaierClimateBase : public esphome::Component,
void set_answer_timeout(uint32_t timeout); void set_answer_timeout(uint32_t timeout);
void set_send_wifi(bool send_wifi); void set_send_wifi(bool send_wifi);
void send_custom_command(const haier_protocol::HaierMessage &message); void send_custom_command(const haier_protocol::HaierMessage &message);
void add_status_message_callback(std::function<void(const char *, size_t)> &&callback);
protected: protected:
enum class ProtocolPhases { enum class ProtocolPhases {
@ -140,11 +142,19 @@ class HaierClimateBase : public esphome::Component,
esphome::climate::ClimateTraits traits_; esphome::climate::ClimateTraits traits_;
HvacSettings current_hvac_settings_; HvacSettings current_hvac_settings_;
HvacSettings next_hvac_settings_; HvacSettings next_hvac_settings_;
std::unique_ptr<uint8_t[]> last_status_message_; std::unique_ptr<uint8_t[]> last_status_message_{nullptr};
std::chrono::steady_clock::time_point last_request_timestamp_; // For interval between messages std::chrono::steady_clock::time_point last_request_timestamp_; // For interval between messages
std::chrono::steady_clock::time_point last_valid_status_timestamp_; // For protocol timeout std::chrono::steady_clock::time_point last_valid_status_timestamp_; // For protocol timeout
std::chrono::steady_clock::time_point last_status_request_; // To request AC status std::chrono::steady_clock::time_point last_status_request_; // To request AC status
std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level
CallbackManager<void(const char *, size_t)> status_message_callback_{};
};
class StatusMessageTrigger : public Trigger<const char *, size_t> {
public:
explicit StatusMessageTrigger(HaierClimateBase *parent) {
parent->add_status_message_callback([this](const char *data, size_t data_size) { this->trigger(data, data_size); });
}
}; };
} // namespace haier } // namespace haier

View file

@ -18,12 +18,13 @@ constexpr int PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET = -64;
constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5; constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5;
constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500); constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500);
constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000; constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000;
const uint8_t ONE_BUF[] = {0x00, 0x01};
const uint8_t ZERO_BUF[] = {0x00, 0x00};
HonClimate::HonClimate() HonClimate::HonClimate()
: cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00, : cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00} { 0x00, 0x00} {
last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[sizeof(hon_protocol::HaierPacketControl)]);
this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID; this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID;
this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO; this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
} }
@ -169,11 +170,18 @@ haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameTy
this->action_request_.reset(); this->action_request_.reset();
this->force_send_control_ = false; this->force_send_control_ = false;
} else { } else {
if (data_size >= sizeof(hon_protocol::HaierPacketControl) + 2) { if (!this->last_status_message_) {
memcpy(this->last_status_message_.get(), data + 2, sizeof(hon_protocol::HaierPacketControl)); this->real_control_packet_size_ = sizeof(hon_protocol::HaierPacketControl) + this->extra_control_packet_bytes_;
this->real_sensors_packet_size_ = sizeof(hon_protocol::HaierPacketSensors) + this->extra_sensors_packet_bytes_;
this->last_status_message_.reset();
this->last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[this->real_control_packet_size_]);
};
if (data_size >= this->real_control_packet_size_ + 2) {
memcpy(this->last_status_message_.get(), data + 2 + this->status_message_header_size_,
this->real_control_packet_size_);
this->status_message_callback_.call((const char *) data, data_size);
} else { } else {
ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, this->real_control_packet_size_);
sizeof(hon_protocol::HaierPacketControl));
} }
switch (this->protocol_phase_) { switch (this->protocol_phase_) {
case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
@ -479,8 +487,8 @@ void HonClimate::initialization() {
} }
haier_protocol::HaierMessage HonClimate::get_control_message() { haier_protocol::HaierMessage HonClimate::get_control_message() {
uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)]; uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl)); memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
control_out_buffer[4] = 0; // This byte should be cleared before setting values control_out_buffer[4] = 0; // This byte should be cleared before setting values
bool has_hvac_settings = false; bool has_hvac_settings = false;
@ -636,7 +644,7 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
out_data->health_mode = this->health_mode_ ? 1 : 0; out_data->health_mode = this->health_mode_ ? 1 : 0;
return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); control_out_buffer, this->real_control_packet_size_);
} }
void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new) { void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new) {
@ -758,15 +766,17 @@ void HonClimate::update_sub_text_sensor_(SubTextSensorType type, const std::stri
#endif // USE_TEXT_SENSOR #endif // USE_TEXT_SENSOR
haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) { haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
size_t expected_size = 2 + sizeof(hon_protocol::HaierPacketControl) + sizeof(hon_protocol::HaierPacketSensors) + size_t expected_size =
this->extra_control_packet_bytes_; 2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_size_;
if (size < expected_size) if (size < expected_size) {
ESP_LOGW(TAG, "Unexpected message size %d (expexted >= %d)", size, expected_size);
return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
}
uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1]; uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1];
if ((subtype == 0x7D01) && (size >= expected_size + 4 + sizeof(hon_protocol::HaierPacketBigData))) { if ((subtype == 0x7D01) && (size >= expected_size + sizeof(hon_protocol::HaierPacketBigData))) {
// Got BigData packet // Got BigData packet
const hon_protocol::HaierPacketBigData *bd_packet = const hon_protocol::HaierPacketBigData *bd_packet =
(const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size + 4]); (const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size]);
#ifdef USE_SENSOR #ifdef USE_SENSOR
this->update_sub_sensor_(SubSensorType::INDOOR_COIL_TEMPERATURE, bd_packet->indoor_coil_temperature / 2.0 - 20); this->update_sub_sensor_(SubSensorType::INDOOR_COIL_TEMPERATURE, bd_packet->indoor_coil_temperature / 2.0 - 20);
this->update_sub_sensor_(SubSensorType::OUTDOOR_COIL_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64); this->update_sub_sensor_(SubSensorType::OUTDOOR_COIL_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64);
@ -795,9 +805,9 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
hon_protocol::HaierPacketControl control; hon_protocol::HaierPacketControl control;
hon_protocol::HaierPacketSensors sensors; hon_protocol::HaierPacketSensors sensors;
} packet; } packet;
memcpy(&packet.control, packet_buffer + 2, sizeof(hon_protocol::HaierPacketControl)); memcpy(&packet.control, packet_buffer + 2 + this->status_message_header_size_,
memcpy(&packet.sensors, sizeof(hon_protocol::HaierPacketControl));
packet_buffer + 2 + sizeof(hon_protocol::HaierPacketControl) + this->extra_control_packet_bytes_, memcpy(&packet.sensors, packet_buffer + 2 + this->status_message_header_size_ + this->real_control_packet_size_,
sizeof(hon_protocol::HaierPacketSensors)); sizeof(hon_protocol::HaierPacketSensors));
if (packet.sensors.error_status != 0) { if (packet.sensors.error_status != 0) {
ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status); ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status);
@ -996,8 +1006,6 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
} }
void HonClimate::fill_control_messages_queue_() { void HonClimate::fill_control_messages_queue_() {
static uint8_t one_buf[] = {0x00, 0x01};
static uint8_t zero_buf[] = {0x00, 0x00};
if (!this->current_hvac_settings_.valid && !this->force_send_control_) if (!this->current_hvac_settings_.valid && !this->force_send_control_)
return; return;
this->clear_control_messages_queue_(); this->clear_control_messages_queue_();
@ -1009,7 +1017,7 @@ void HonClimate::fill_control_messages_queue_() {
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::BEEPER_STATUS, (uint8_t) hon_protocol::DataParameters::BEEPER_STATUS,
this->beeper_status_ ? zero_buf : one_buf, 2)); this->beeper_status_ ? ZERO_BUF : ONE_BUF, 2));
} }
// Health mode // Health mode
{ {
@ -1017,7 +1025,7 @@ void HonClimate::fill_control_messages_queue_() {
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::HEALTH_MODE, (uint8_t) hon_protocol::DataParameters::HEALTH_MODE,
this->health_mode_ ? one_buf : zero_buf, 2)); this->health_mode_ ? ONE_BUF : ZERO_BUF, 2));
} }
// Climate mode // Climate mode
bool new_power = this->mode != CLIMATE_MODE_OFF; bool new_power = this->mode != CLIMATE_MODE_OFF;
@ -1092,7 +1100,7 @@ void HonClimate::fill_control_messages_queue_() {
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::AC_POWER, (uint8_t) hon_protocol::DataParameters::AC_POWER,
new_power ? one_buf : zero_buf, 2)); new_power ? ONE_BUF : ZERO_BUF, 2));
} }
// CLimate preset // CLimate preset
{ {
@ -1165,6 +1173,35 @@ void HonClimate::fill_control_messages_queue_() {
(uint8_t) hon_protocol::DataParameters::SET_POINT, (uint8_t) hon_protocol::DataParameters::SET_POINT,
buffer, 2)); buffer, 2));
} }
// Vertical swing mode
if (climate_control.swing_mode.has_value()) {
uint8_t vertical_swing_buf[] = {0x00, (uint8_t) hon_protocol::VerticalSwingMode::AUTO};
uint8_t horizontal_swing_buf[] = {0x00, (uint8_t) hon_protocol::HorizontalSwingMode::AUTO};
switch (climate_control.swing_mode.value()) {
case CLIMATE_SWING_OFF:
horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing;
vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing;
break;
case CLIMATE_SWING_VERTICAL:
horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing;
break;
case CLIMATE_SWING_HORIZONTAL:
vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing;
break;
case CLIMATE_SWING_BOTH:
break;
}
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::HORIZONTAL_SWING_MODE,
horizontal_swing_buf, 2));
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::VERTICAL_SWING_MODE,
vertical_swing_buf, 2));
}
// Fan mode // Fan mode
if (climate_control.fan_mode.has_value()) { if (climate_control.fan_mode.has_value()) {
switch (climate_control.fan_mode.value()) { switch (climate_control.fan_mode.value()) {
@ -1202,9 +1239,10 @@ void HonClimate::clear_control_messages_queue_() {
bool HonClimate::prepare_pending_action() { bool HonClimate::prepare_pending_action() {
switch (this->action_request_.value().action) { switch (this->action_request_.value().action) {
case ActionRequest::START_SELF_CLEAN: { case ActionRequest::START_SELF_CLEAN:
uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)]; if (this->control_method_ == HonControlMethod::SET_GROUP_PARAMETERS) {
memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl)); uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
out_data->self_cleaning_status = 1; out_data->self_cleaning_status = 1;
out_data->steri_clean = 0; out_data->steri_clean = 0;
@ -1216,12 +1254,23 @@ bool HonClimate::prepare_pending_action() {
out_data->light_status = 0; out_data->light_status = 0;
this->action_request_.value().message = haier_protocol::HaierMessage( this->action_request_.value().message = haier_protocol::HaierMessage(
haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); control_out_buffer, this->real_control_packet_size_);
}
return true; return true;
case ActionRequest::START_STERI_CLEAN: { } else if (this->control_method_ == HonControlMethod::SET_SINGLE_PARAMETER) {
uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)]; this->action_request_.value().message =
memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl)); haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::SELF_CLEANING,
ONE_BUF, 2);
return true;
} else {
this->action_request_.reset();
return false;
}
case ActionRequest::START_STERI_CLEAN:
if (this->control_method_ == HonControlMethod::SET_GROUP_PARAMETERS) {
uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
out_data->self_cleaning_status = 0; out_data->self_cleaning_status = 0;
out_data->steri_clean = 1; out_data->steri_clean = 1;
@ -1233,9 +1282,13 @@ bool HonClimate::prepare_pending_action() {
out_data->light_status = 0; out_data->light_status = 0;
this->action_request_.value().message = haier_protocol::HaierMessage( this->action_request_.value().message = haier_protocol::HaierMessage(
haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); control_out_buffer, this->real_control_packet_size_);
}
return true; return true;
} else {
// No Steri clean support (yet?) in SET_SINGLE_PARAMETER
this->action_request_.reset();
return false;
}
default: default:
return HaierClimateBase::prepare_pending_action(); return HaierClimateBase::prepare_pending_action();
} }
@ -1251,6 +1304,7 @@ void HonClimate::process_protocol_reset() {
#endif // USE_SENSOR #endif // USE_SENSOR
this->got_valid_outdoor_temp_ = false; this->got_valid_outdoor_temp_ = false;
this->hvac_hardware_info_.reset(); this->hvac_hardware_info_.reset();
this->last_status_message_.reset(nullptr);
} }
bool HonClimate::should_get_big_data_() { bool HonClimate::should_get_big_data_() {

View file

@ -104,6 +104,8 @@ class HonClimate : public HaierClimateBase {
void start_self_cleaning(); void start_self_cleaning();
void start_steri_cleaning(); void start_steri_cleaning();
void set_extra_control_packet_bytes_size(size_t size) { this->extra_control_packet_bytes_ = size; }; void set_extra_control_packet_bytes_size(size_t size) { this->extra_control_packet_bytes_ = size; };
void set_extra_sensors_packet_bytes_size(size_t size) { this->extra_sensors_packet_bytes_ = size; };
void set_status_message_header_size(size_t size) { this->status_message_header_size_ = size; };
void set_control_method(HonControlMethod method) { this->control_method_ = method; }; void set_control_method(HonControlMethod method) { this->control_method_ = method; };
void add_alarm_start_callback(std::function<void(uint8_t, const char *)> &&callback); void add_alarm_start_callback(std::function<void(uint8_t, const char *)> &&callback);
void add_alarm_end_callback(std::function<void(uint8_t, const char *)> &&callback); void add_alarm_end_callback(std::function<void(uint8_t, const char *)> &&callback);
@ -158,7 +160,11 @@ class HonClimate : public HaierClimateBase {
esphome::optional<hon_protocol::HorizontalSwingMode> pending_horizontal_direction_{}; esphome::optional<hon_protocol::HorizontalSwingMode> pending_horizontal_direction_{};
esphome::optional<HardwareInfo> hvac_hardware_info_{}; esphome::optional<HardwareInfo> hvac_hardware_info_{};
uint8_t active_alarms_[8]; uint8_t active_alarms_[8];
int extra_control_packet_bytes_; int extra_control_packet_bytes_{0};
int extra_sensors_packet_bytes_{4};
int status_message_header_size_{0};
int real_control_packet_size_{sizeof(hon_protocol::HaierPacketControl)};
int real_sensors_packet_size_{sizeof(hon_protocol::HaierPacketSensors) + 4};
HonControlMethod control_method_; HonControlMethod control_method_;
std::queue<haier_protocol::HaierMessage> control_messages_queue_; std::queue<haier_protocol::HaierMessage> control_messages_queue_;
CallbackManager<void(uint8_t, const char *)> alarm_start_callback_{}; CallbackManager<void(uint8_t, const char *)> alarm_start_callback_{};

View file

@ -41,15 +41,20 @@ enum class ConditioningMode : uint8_t {
enum class DataParameters : uint8_t { enum class DataParameters : uint8_t {
AC_POWER = 0x01, AC_POWER = 0x01,
SET_POINT = 0x02, SET_POINT = 0x02,
VERTICAL_SWING_MODE = 0x03,
AC_MODE = 0x04, AC_MODE = 0x04,
FAN_MODE = 0x05, FAN_MODE = 0x05,
USE_FAHRENHEIT = 0x07, USE_FAHRENHEIT = 0x07,
DISPLAY_STATUS = 0x09,
TEN_DEGREE = 0x0A, TEN_DEGREE = 0x0A,
HEALTH_MODE = 0x0B, HEALTH_MODE = 0x0B,
HORIZONTAL_SWING_MODE = 0x0C,
SELF_CLEANING = 0x0D,
BEEPER_STATUS = 0x16, BEEPER_STATUS = 0x16,
LOCK_REMOTE = 0x17, LOCK_REMOTE = 0x17,
QUIET_MODE = 0x19, QUIET_MODE = 0x19,
FAST_MODE = 0x1A, FAST_MODE = 0x1A,
SLEEP_MODE = 0x1B,
}; };
enum class SpecialMode : uint8_t { NONE = 0x00, ELDERLY = 0x01, CHILDREN = 0x02, PREGNANT = 0x03 }; enum class SpecialMode : uint8_t { NONE = 0x00, ELDERLY = 0x01, CHILDREN = 0x02, PREGNANT = 0x03 };

View file

@ -37,6 +37,7 @@ haier_protocol::HandlerError Smartair2Climate::status_handler_(haier_protocol::F
} else { } else {
if (data_size >= sizeof(smartair2_protocol::HaierPacketControl) + 2) { if (data_size >= sizeof(smartair2_protocol::HaierPacketControl) + 2) {
memcpy(this->last_status_message_.get(), data + 2, sizeof(smartair2_protocol::HaierPacketControl)); memcpy(this->last_status_message_.get(), data + 2, sizeof(smartair2_protocol::HaierPacketControl));
this->status_message_callback_.call((const char *) data, data_size);
} else { } else {
ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size,
sizeof(smartair2_protocol::HaierPacketControl)); sizeof(smartair2_protocol::HaierPacketControl));

View file

@ -34,6 +34,7 @@ PROTOCOLS = {
"greeyan": Protocol.PROTOCOL_GREEYAN, "greeyan": Protocol.PROTOCOL_GREEYAN,
"greeyac": Protocol.PROTOCOL_GREEYAC, "greeyac": Protocol.PROTOCOL_GREEYAC,
"greeyt": Protocol.PROTOCOL_GREEYT, "greeyt": Protocol.PROTOCOL_GREEYT,
"greeyap": Protocol.PROTOCOL_GREEYAP,
"hisense_aud": Protocol.PROTOCOL_HISENSE_AUD, "hisense_aud": Protocol.PROTOCOL_HISENSE_AUD,
"hitachi": Protocol.PROTOCOL_HITACHI, "hitachi": Protocol.PROTOCOL_HITACHI,
"hyundai": Protocol.PROTOCOL_HYUNDAI, "hyundai": Protocol.PROTOCOL_HYUNDAI,
@ -61,6 +62,16 @@ PROTOCOLS = {
"toshiba_daiseikai": Protocol.PROTOCOL_TOSHIBA_DAISEIKAI, "toshiba_daiseikai": Protocol.PROTOCOL_TOSHIBA_DAISEIKAI,
"toshiba": Protocol.PROTOCOL_TOSHIBA, "toshiba": Protocol.PROTOCOL_TOSHIBA,
"zhlt01": Protocol.PROTOCOL_ZHLT01, "zhlt01": Protocol.PROTOCOL_ZHLT01,
"nibe": Protocol.PROTOCOL_NIBE,
"carrier_qlima_1": Protocol.PROTOCOL_QLIMA_1,
"carrier_qlima_2": Protocol.PROTOCOL_QLIMA_2,
"samsung_aqv12msan": Protocol.PROTOCOL_SAMSUNG_AQV12MSAN,
"zhjg01": Protocol.PROTOCOL_ZHJG01,
"airway": Protocol.PROTOCOL_AIRWAY,
"bgh_aud": Protocol.PROTOCOL_BGH_AUD,
"panasonic_altdke": Protocol.PROTOCOL_PANASONIC_ALTDKE,
"vaillantvai8": Protocol.PROTOCOL_VAILLANTVAI8,
"r51m": Protocol.PROTOCOL_R51M,
} }
CONF_HORIZONTAL_DEFAULT = "horizontal_default" CONF_HORIZONTAL_DEFAULT = "horizontal_default"
@ -116,7 +127,6 @@ def to_code(config):
cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE])) cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
cg.add_library("tonia/HeatpumpIR", "1.0.23") cg.add_library("tonia/HeatpumpIR", "1.0.27")
if CORE.is_libretiny:
if CORE.is_esp8266 or CORE.is_esp32: CORE.add_platformio_option("lib_ignore", "IRremoteESP8266")
cg.add_library("crankyoldgit/IRremoteESP8266", "2.8.4")

View file

@ -28,6 +28,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
{PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT {PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }}, // NOLINT {PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREEYT, []() { return new GreeYTHeatpumpIR(); }}, // NOLINT {PROTOCOL_GREEYT, []() { return new GreeYTHeatpumpIR(); }}, // NOLINT
{PROTOCOL_GREEYAP, []() { return new GreeYAPHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT {PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT {PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT
{PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT {PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT
@ -55,6 +56,16 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
{PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }}, // NOLINT {PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }}, // NOLINT
{PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }}, // NOLINT {PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }}, // NOLINT
{PROTOCOL_ZHLT01, []() { return new ZHLT01HeatpumpIR(); }}, // NOLINT {PROTOCOL_ZHLT01, []() { return new ZHLT01HeatpumpIR(); }}, // NOLINT
{PROTOCOL_NIBE, []() { return new NibeHeatpumpIR(); }}, // NOLINT
{PROTOCOL_QLIMA_1, []() { return new Qlima1HeatpumpIR(); }}, // NOLINT
{PROTOCOL_QLIMA_2, []() { return new Qlima2HeatpumpIR(); }}, // NOLINT
{PROTOCOL_SAMSUNG_AQV12MSAN, []() { return new SamsungAQV12MSANHeatpumpIR(); }}, // NOLINT
{PROTOCOL_ZHJG01, []() { return new ZHJG01HeatpumpIR(); }}, // NOLINT
{PROTOCOL_AIRWAY, []() { return new AIRWAYHeatpumpIR(); }}, // NOLINT
{PROTOCOL_BGH_AUD, []() { return new BGHHeatpumpIR(); }}, // NOLINT
{PROTOCOL_PANASONIC_ALTDKE, []() { return new PanasonicAltDKEHeatpumpIR(); }}, // NOLINT
{PROTOCOL_VAILLANTVAI8, []() { return new VaillantHeatpumpIR(); }}, // NOLINT
{PROTOCOL_R51M, []() { return new R51MHeatpumpIR(); }}, // NOLINT
}; };
void HeatpumpIRClimate::setup() { void HeatpumpIRClimate::setup() {

View file

@ -28,6 +28,7 @@ enum Protocol {
PROTOCOL_GREEYAN, PROTOCOL_GREEYAN,
PROTOCOL_GREEYAC, PROTOCOL_GREEYAC,
PROTOCOL_GREEYT, PROTOCOL_GREEYT,
PROTOCOL_GREEYAP,
PROTOCOL_HISENSE_AUD, PROTOCOL_HISENSE_AUD,
PROTOCOL_HITACHI, PROTOCOL_HITACHI,
PROTOCOL_HYUNDAI, PROTOCOL_HYUNDAI,
@ -55,6 +56,16 @@ enum Protocol {
PROTOCOL_TOSHIBA_DAISEIKAI, PROTOCOL_TOSHIBA_DAISEIKAI,
PROTOCOL_TOSHIBA, PROTOCOL_TOSHIBA,
PROTOCOL_ZHLT01, PROTOCOL_ZHLT01,
PROTOCOL_NIBE,
PROTOCOL_QLIMA_1,
PROTOCOL_QLIMA_2,
PROTOCOL_SAMSUNG_AQV12MSAN,
PROTOCOL_ZHJG01,
PROTOCOL_AIRWAY,
PROTOCOL_BGH_AUD,
PROTOCOL_PANASONIC_ALTDKE,
PROTOCOL_VAILLANTVAI8,
PROTOCOL_R51M,
}; };
// Simple enum to represent horizontal directios // Simple enum to represent horizontal directios

View file

@ -1,20 +1,20 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
import esphome.codegen as cg
from esphome.components import esp32
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
__version__, CONF_ESP8266_DISABLE_SSL_SUPPORT,
CONF_ID, CONF_ID,
CONF_TIMEOUT,
CONF_METHOD, CONF_METHOD,
CONF_TIMEOUT,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_URL, CONF_URL,
CONF_ESP8266_DISABLE_SSL_SUPPORT, __version__,
) )
from esphome.core import Lambda, CORE from esphome.core import CORE, Lambda
from esphome.components import esp32
DEPENDENCIES = ["network"] DEPENDENCIES = ["network"]
AUTO_LOAD = ["json"] AUTO_LOAD = ["json", "watchdog"]
http_request_ns = cg.esphome_ns.namespace("http_request") http_request_ns = cg.esphome_ns.namespace("http_request")
HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component) HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component)
@ -40,6 +40,8 @@ CONF_VERIFY_SSL = "verify_ssl"
CONF_FOLLOW_REDIRECTS = "follow_redirects" CONF_FOLLOW_REDIRECTS = "follow_redirects"
CONF_REDIRECT_LIMIT = "redirect_limit" CONF_REDIRECT_LIMIT = "redirect_limit"
CONF_WATCHDOG_TIMEOUT = "watchdog_timeout" CONF_WATCHDOG_TIMEOUT = "watchdog_timeout"
CONF_BUFFER_SIZE_RX = "buffer_size_rx"
CONF_BUFFER_SIZE_TX = "buffer_size_tx"
CONF_MAX_RESPONSE_BUFFER_SIZE = "max_response_buffer_size" CONF_MAX_RESPONSE_BUFFER_SIZE = "max_response_buffer_size"
CONF_ON_RESPONSE = "on_response" CONF_ON_RESPONSE = "on_response"
@ -99,7 +101,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_FOLLOW_REDIRECTS, True): cv.boolean, cv.Optional(CONF_FOLLOW_REDIRECTS, True): cv.boolean,
cv.Optional(CONF_REDIRECT_LIMIT, 3): cv.int_, cv.Optional(CONF_REDIRECT_LIMIT, 3): cv.int_,
cv.Optional( cv.Optional(
CONF_TIMEOUT, default="5s" CONF_TIMEOUT, default="4.5s"
): cv.positive_time_period_milliseconds, ): cv.positive_time_period_milliseconds,
cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All( cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All(
cv.only_on_esp8266, cv.boolean cv.only_on_esp8266, cv.boolean
@ -110,6 +112,12 @@ CONFIG_SCHEMA = cv.All(
cv.positive_not_null_time_period, cv.positive_not_null_time_period,
cv.positive_time_period_milliseconds, cv.positive_time_period_milliseconds,
), ),
cv.SplitDefault(CONF_BUFFER_SIZE_RX, esp32_idf=512): cv.All(
cv.uint16_t, cv.only_with_esp_idf
),
cv.SplitDefault(CONF_BUFFER_SIZE_TX, esp32_idf=512): cv.All(
cv.uint16_t, cv.only_with_esp_idf
),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.require_framework_version( cv.require_framework_version(
@ -137,6 +145,9 @@ async def to_code(config):
if CORE.is_esp32: if CORE.is_esp32:
if CORE.using_esp_idf: if CORE.using_esp_idf:
cg.add(var.set_buffer_size_rx(config[CONF_BUFFER_SIZE_RX]))
cg.add(var.set_buffer_size_tx(config[CONF_BUFFER_SIZE_TX]))
esp32.add_idf_sdkconfig_option( esp32.add_idf_sdkconfig_option(
"CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", "CONFIG_MBEDTLS_CERTIFICATE_BUNDLE",
config.get(CONF_VERIFY_SSL), config.get(CONF_VERIFY_SSL),
@ -257,7 +268,7 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
trigger, trigger,
[ [
(cg.std_shared_ptr.template(HttpContainer), "response"), (cg.std_shared_ptr.template(HttpContainer), "response"),
(cg.std_string, "body"), (cg.std_string_ref, "body"),
], ],
conf, conf,
) )

View file

@ -43,10 +43,10 @@ class HttpContainer : public Parented<HttpRequestComponent> {
bool secure_{false}; bool secure_{false};
}; };
class HttpRequestResponseTrigger : public Trigger<std::shared_ptr<HttpContainer>, std::string> { class HttpRequestResponseTrigger : public Trigger<std::shared_ptr<HttpContainer>, std::string &> {
public: public:
void process(std::shared_ptr<HttpContainer> container, std::string response_body) { void process(std::shared_ptr<HttpContainer> container, std::string &response_body) {
this->trigger(std::move(container), std::move(response_body)); this->trigger(std::move(container), response_body);
} }
}; };
@ -80,7 +80,7 @@ class HttpRequestComponent : public Component {
const char *useragent_{nullptr}; const char *useragent_{nullptr};
bool follow_redirects_; bool follow_redirects_;
uint16_t redirect_limit_; uint16_t redirect_limit_;
uint16_t timeout_{5000}; uint16_t timeout_{4500};
uint32_t watchdog_timeout_{0}; uint32_t watchdog_timeout_{0};
}; };
@ -153,8 +153,17 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
} }
} }
if (this->response_triggers_.size() == 1) {
// if there is only one trigger, no need to copy the response body
this->response_triggers_[0]->process(container, response_body);
} else {
for (auto *trigger : this->response_triggers_) { for (auto *trigger : this->response_triggers_) {
trigger->process(container, response_body); // with multiple triggers, pass a copy of the response body to each
// one so that modifications made in one trigger are not visible to
// the others
auto response_body_copy = std::string(response_body);
trigger->process(container, response_body_copy);
}
} }
container->end(); container->end();
} }

View file

@ -3,12 +3,12 @@
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#include "esphome/components/network/util.h" #include "esphome/components/network/util.h"
#include "esphome/components/watchdog/watchdog.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "watchdog.h"
namespace esphome { namespace esphome {
namespace http_request { namespace http_request {
@ -32,6 +32,13 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::s
watchdog::WatchdogManager wdm(this->get_watchdog_timeout()); watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
if (this->follow_redirects_) {
container->client_.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
container->client_.setRedirectLimit(this->redirect_limit_);
} else {
container->client_.setFollowRedirects(HTTPC_DISABLE_FOLLOW_REDIRECTS);
}
#if defined(USE_ESP8266) #if defined(USE_ESP8266)
std::unique_ptr<WiFiClient> stream_ptr; std::unique_ptr<WiFiClient> stream_ptr;
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS #ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
@ -59,8 +66,6 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::s
"in your YAML, or use HTTPS"); "in your YAML, or use HTTPS");
} }
#endif // USE_ARDUINO_VERSION_CODE #endif // USE_ARDUINO_VERSION_CODE
container->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
bool status = container->client_.begin(*stream_ptr, url.c_str()); bool status = container->client_.begin(*stream_ptr, url.c_str());
#elif defined(USE_RP2040) #elif defined(USE_RP2040)

View file

@ -3,6 +3,8 @@
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF
#include "esphome/components/network/util.h" #include "esphome/components/network/util.h"
#include "esphome/components/watchdog/watchdog.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@ -11,13 +13,17 @@
#include "esp_crt_bundle.h" #include "esp_crt_bundle.h"
#endif #endif
#include "watchdog.h"
namespace esphome { namespace esphome {
namespace http_request { namespace http_request {
static const char *const TAG = "http_request.idf"; static const char *const TAG = "http_request.idf";
void HttpRequestIDF::dump_config() {
HttpRequestComponent::dump_config();
ESP_LOGCONFIG(TAG, " Buffer Size RX: %u", this->buffer_size_rx_);
ESP_LOGCONFIG(TAG, " Buffer Size TX: %u", this->buffer_size_tx_);
}
std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::string method, std::string body, std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::string method, std::string body,
std::list<Header> headers) { std::list<Header> headers) {
if (!network::is_connected()) { if (!network::is_connected()) {
@ -52,6 +58,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
config.timeout_ms = this->timeout_; config.timeout_ms = this->timeout_;
config.disable_auto_redirect = !this->follow_redirects_; config.disable_auto_redirect = !this->follow_redirects_;
config.max_redirection_count = this->redirect_limit_; config.max_redirection_count = this->redirect_limit_;
config.auth_type = HTTP_AUTH_TYPE_BASIC;
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
if (secure) { if (secure) {
config.crt_bundle_attach = esp_crt_bundle_attach; config.crt_bundle_attach = esp_crt_bundle_attach;
@ -62,6 +69,9 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
config.user_agent = this->useragent_; config.user_agent = this->useragent_;
} }
config.buffer_size = this->buffer_size_rx_;
config.buffer_size_tx = this->buffer_size_tx_;
const uint32_t start = millis(); const uint32_t start = millis();
watchdog::WatchdogManager wdm(this->get_watchdog_timeout()); watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
@ -76,7 +86,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
esp_http_client_set_header(client, header.name, header.value); esp_http_client_set_header(client, header.name, header.value);
} }
int body_len = body.length(); const int body_len = body.length();
esp_err_t err = esp_http_client_open(client, body_len); esp_err_t err = esp_http_client_open(client, body_len);
if (err != ESP_OK) { if (err != ESP_OK) {
@ -108,18 +118,62 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin
return nullptr; return nullptr;
} }
container->content_length = esp_http_client_fetch_headers(client); auto is_ok = [](int code) { return code >= HttpStatus_Ok && code < HttpStatus_MultipleChoices; };
const auto status_code = esp_http_client_get_status_code(client);
container->status_code = status_code;
if (status_code < 200 || status_code >= 300) { container->content_length = esp_http_client_fetch_headers(client);
ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), status_code); container->status_code = esp_http_client_get_status_code(client);
if (is_ok(container->status_code)) {
container->duration_ms = millis() - start;
return container;
}
if (this->follow_redirects_) {
auto is_redirect = [](int code) {
return code == HttpStatus_MovedPermanently || code == HttpStatus_Found || code == HttpStatus_SeeOther ||
code == HttpStatus_TemporaryRedirect || code == HttpStatus_PermanentRedirect;
};
auto num_redirects = this->redirect_limit_;
while (is_redirect(container->status_code) && num_redirects > 0) {
err = esp_http_client_set_redirection(client);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_http_client_set_redirection failed: %s", esp_err_to_name(err));
this->status_momentary_error("failed", 1000); this->status_momentary_error("failed", 1000);
esp_http_client_cleanup(client); esp_http_client_cleanup(client);
return nullptr; return nullptr;
} }
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char url[256]{};
if (esp_http_client_get_url(client, url, sizeof(url) - 1) == ESP_OK) {
ESP_LOGV(TAG, "redirecting to url: %s", url);
}
#endif
err = esp_http_client_open(client, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_http_client_open failed: %s", esp_err_to_name(err));
this->status_momentary_error("failed", 1000);
esp_http_client_cleanup(client);
return nullptr;
}
container->content_length = esp_http_client_fetch_headers(client);
container->status_code = esp_http_client_get_status_code(client);
if (is_ok(container->status_code)) {
container->duration_ms = millis() - start; container->duration_ms = millis() - start;
return container; return container;
}
num_redirects--;
}
if (num_redirects == 0) {
ESP_LOGW(TAG, "Reach redirect limit count=%d", this->redirect_limit_);
}
}
ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code);
this->status_momentary_error("failed", 1000);
esp_http_client_cleanup(client);
return nullptr;
} }
int HttpContainerIDF::read(uint8_t *buf, size_t max_len) { int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {

View file

@ -24,8 +24,18 @@ class HttpContainerIDF : public HttpContainer {
class HttpRequestIDF : public HttpRequestComponent { class HttpRequestIDF : public HttpRequestComponent {
public: public:
void dump_config() override;
std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body, std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
std::list<Header> headers) override; std::list<Header> headers) override;
void set_buffer_size_rx(uint16_t buffer_size_rx) { this->buffer_size_rx_ = buffer_size_rx; }
void set_buffer_size_tx(uint16_t buffer_size_tx) { this->buffer_size_tx_ = buffer_size_tx; }
protected:
// if zero ESP-IDF will use DEFAULT_HTTP_BUF_SIZE
uint16_t buffer_size_rx_{};
uint16_t buffer_size_tx_{};
}; };
} // namespace http_request } // namespace http_request

View file

@ -1,11 +1,11 @@
#include "ota_http_request.h" #include "ota_http_request.h"
#include "../watchdog.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/components/md5/md5.h" #include "esphome/components/md5/md5.h"
#include "esphome/components/watchdog/watchdog.h"
#include "esphome/components/ota/ota_backend.h" #include "esphome/components/ota/ota_backend.h"
#include "esphome/components/ota/ota_backend_arduino_esp32.h" #include "esphome/components/ota/ota_backend_arduino_esp32.h"
#include "esphome/components/ota/ota_backend_arduino_esp8266.h" #include "esphome/components/ota/ota_backend_arduino_esp8266.h"

View file

@ -116,19 +116,18 @@ void HttpRequestUpdate::update() {
} }
} }
std::string current_version = this->current_version_; std::string current_version;
if (current_version.empty()) {
#ifdef ESPHOME_PROJECT_VERSION #ifdef ESPHOME_PROJECT_VERSION
current_version = ESPHOME_PROJECT_VERSION; current_version = ESPHOME_PROJECT_VERSION;
#else #else
current_version = ESPHOME_VERSION; current_version = ESPHOME_VERSION;
#endif #endif
}
this->update_info_.current_version = current_version; this->update_info_.current_version = current_version;
if (this->update_info_.latest_version.empty()) { if (this->update_info_.latest_version.empty() || this->update_info_.latest_version == update_info_.current_version) {
this->state_ = update::UPDATE_STATE_NO_UPDATE; this->state_ = update::UPDATE_STATE_NO_UPDATE;
} else if (this->update_info_.latest_version != this->current_version_) { } else {
this->state_ = update::UPDATE_STATE_AVAILABLE; this->state_ = update::UPDATE_STATE_AVAILABLE;
} }
@ -139,8 +138,8 @@ void HttpRequestUpdate::update() {
this->publish_state(); this->publish_state();
} }
void HttpRequestUpdate::perform() { void HttpRequestUpdate::perform(bool force) {
if (this->state_ != update::UPDATE_STATE_AVAILABLE) { if (this->state_ != update::UPDATE_STATE_AVAILABLE && !force) {
return; return;
} }

View file

@ -15,22 +15,19 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent {
void setup() override; void setup() override;
void update() override; void update() override;
void perform() override; void perform(bool force) override;
void set_source_url(const std::string &source_url) { this->source_url_ = source_url; } void set_source_url(const std::string &source_url) { this->source_url_ = source_url; }
void set_request_parent(HttpRequestComponent *request_parent) { this->request_parent_ = request_parent; } void set_request_parent(HttpRequestComponent *request_parent) { this->request_parent_ = request_parent; }
void set_ota_parent(OtaHttpRequestComponent *ota_parent) { this->ota_parent_ = ota_parent; } void set_ota_parent(OtaHttpRequestComponent *ota_parent) { this->ota_parent_ = ota_parent; }
void set_current_version(const std::string &current_version) { this->current_version_ = current_version; }
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
protected: protected:
HttpRequestComponent *request_parent_; HttpRequestComponent *request_parent_;
OtaHttpRequestComponent *ota_parent_; OtaHttpRequestComponent *ota_parent_;
std::string source_url_; std::string source_url_;
std::string current_version_{""};
}; };
} // namespace http_request } // namespace http_request

View file

@ -25,6 +25,10 @@ CONF_I2S_LRCLK_PIN = "i2s_lrclk_pin"
CONF_I2S_AUDIO = "i2s_audio" CONF_I2S_AUDIO = "i2s_audio"
CONF_I2S_AUDIO_ID = "i2s_audio_id" CONF_I2S_AUDIO_ID = "i2s_audio_id"
CONF_I2S_MODE = "i2s_mode"
CONF_PRIMARY = "primary"
CONF_SECONDARY = "secondary"
i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio") i2s_audio_ns = cg.esphome_ns.namespace("i2s_audio")
I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component) I2SAudioComponent = i2s_audio_ns.class_("I2SAudioComponent", cg.Component)
I2SAudioIn = i2s_audio_ns.class_("I2SAudioIn", cg.Parented.template(I2SAudioComponent)) I2SAudioIn = i2s_audio_ns.class_("I2SAudioIn", cg.Parented.template(I2SAudioComponent))
@ -32,6 +36,12 @@ I2SAudioOut = i2s_audio_ns.class_(
"I2SAudioOut", cg.Parented.template(I2SAudioComponent) "I2SAudioOut", cg.Parented.template(I2SAudioComponent)
) )
i2s_mode_t = cg.global_ns.enum("i2s_mode_t")
I2S_MODE_OPTIONS = {
CONF_PRIMARY: i2s_mode_t.I2S_MODE_MASTER, # NOLINT
CONF_SECONDARY: i2s_mode_t.I2S_MODE_SLAVE, # NOLINT
}
# https://github.com/espressif/esp-idf/blob/master/components/soc/{variant}/include/soc/soc_caps.h # https://github.com/espressif/esp-idf/blob/master/components/soc/{variant}/include/soc/soc_caps.h
I2S_PORTS = { I2S_PORTS = {
VARIANT_ESP32: 2, VARIANT_ESP32: 2,

View file

@ -2,11 +2,14 @@ import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome import pins from esphome import pins
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_NUMBER from esphome.const import CONF_CHANNEL, CONF_ID, CONF_NUMBER, CONF_SAMPLE_RATE
from esphome.components import microphone, esp32 from esphome.components import microphone, esp32
from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin from esphome.components.adc import ESP32_VARIANT_ADC1_PIN_TO_CHANNEL, validate_adc_pin
from .. import ( from .. import (
CONF_I2S_MODE,
CONF_PRIMARY,
I2S_MODE_OPTIONS,
i2s_audio_ns, i2s_audio_ns,
I2SAudioComponent, I2SAudioComponent,
I2SAudioIn, I2SAudioIn,
@ -20,7 +23,6 @@ DEPENDENCIES = ["i2s_audio"]
CONF_ADC_PIN = "adc_pin" CONF_ADC_PIN = "adc_pin"
CONF_ADC_TYPE = "adc_type" CONF_ADC_TYPE = "adc_type"
CONF_PDM = "pdm" CONF_PDM = "pdm"
CONF_SAMPLE_RATE = "sample_rate"
CONF_BITS_PER_SAMPLE = "bits_per_sample" CONF_BITS_PER_SAMPLE = "bits_per_sample"
CONF_USE_APLL = "use_apll" CONF_USE_APLL = "use_apll"
@ -69,6 +71,9 @@ BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
_validate_bits, cv.enum(BITS_PER_SAMPLE) _validate_bits, cv.enum(BITS_PER_SAMPLE)
), ),
cv.Optional(CONF_USE_APLL, default=False): cv.boolean, cv.Optional(CONF_USE_APLL, default=False): cv.boolean,
cv.Optional(CONF_I2S_MODE, default=CONF_PRIMARY): cv.enum(
I2S_MODE_OPTIONS, lower=True
),
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
@ -108,6 +113,7 @@ async def to_code(config):
cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN])) cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
cg.add(var.set_pdm(config[CONF_PDM])) cg.add(var.set_pdm(config[CONF_PDM]))
cg.add(var.set_i2s_mode(config[CONF_I2S_MODE]))
cg.add(var.set_channel(config[CONF_CHANNEL])) cg.add(var.set_channel(config[CONF_CHANNEL]))
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE])) cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE]))

View file

@ -46,7 +46,7 @@ void I2SAudioMicrophone::start_() {
return; // Waiting for another i2s to return lock return; // Waiting for another i2s to return lock
} }
i2s_driver_config_t config = { i2s_driver_config_t config = {
.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX), .mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_RX),
.sample_rate = this->sample_rate_, .sample_rate = this->sample_rate_,
.bits_per_sample = this->bits_per_sample_, .bits_per_sample = this->bits_per_sample_,
.channel_format = this->channel_, .channel_format = this->channel_,

View file

@ -30,6 +30,8 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
} }
#endif #endif
void set_i2s_mode(i2s_mode_t mode) { this->i2s_mode_ = mode; }
void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; } void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; }
void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; } void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; }
void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; } void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; }
@ -46,6 +48,7 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
bool adc_{false}; bool adc_{false};
#endif #endif
bool pdm_{false}; bool pdm_{false};
i2s_mode_t i2s_mode_{};
i2s_channel_fmt_t channel_; i2s_channel_fmt_t channel_;
uint32_t sample_rate_; uint32_t sample_rate_;
i2s_bits_per_sample_t bits_per_sample_; i2s_bits_per_sample_t bits_per_sample_;

View file

@ -92,7 +92,9 @@ static const uint8_t ILI9XXX_GMCTRN1 = 0xE1;
static const uint8_t ILI9XXX_CSCON = 0xF0; static const uint8_t ILI9XXX_CSCON = 0xF0;
static const uint8_t ILI9XXX_ADJCTL3 = 0xF7; static const uint8_t ILI9XXX_ADJCTL3 = 0xF7;
static const uint8_t ILI9XXX_DELAY = 0xFF; // followed by one byte of delay time in ms static const uint8_t ILI9XXX_DELAY_FLAG = 0xFF;
// special marker for delay - command byte reprents ms, length byte is an impossible value
#define ILI9XXX_DELAY(ms) ((uint8_t) ((ms) | 0x80)), ILI9XXX_DELAY_FLAG
} // namespace ili9xxx } // namespace ili9xxx
} // namespace esphome } // namespace esphome

View file

@ -34,8 +34,8 @@ void ILI9XXXDisplay::setup() {
ESP_LOGD(TAG, "Setting up ILI9xxx"); ESP_LOGD(TAG, "Setting up ILI9xxx");
this->setup_pins_(); this->setup_pins_();
this->init_lcd(this->init_sequence_); this->init_lcd_(this->init_sequence_);
this->init_lcd(this->extra_init_sequence_.data()); this->init_lcd_(this->extra_init_sequence_.data());
switch (this->pixel_mode_) { switch (this->pixel_mode_) {
case PIXEL_MODE_16: case PIXEL_MODE_16:
if (this->is_18bitdisplay_) { if (this->is_18bitdisplay_) {
@ -405,42 +405,29 @@ void ILI9XXXDisplay::reset_() {
} }
} }
void ILI9XXXDisplay::init_lcd(const uint8_t *addr) { void ILI9XXXDisplay::init_lcd_(const uint8_t *addr) {
if (addr == nullptr) if (addr == nullptr)
return; return;
uint8_t cmd, x, num_args; uint8_t cmd, x, num_args;
while ((cmd = *addr++) != 0) { while ((cmd = *addr++) != 0) {
x = *addr++; x = *addr++;
if (cmd == ILI9XXX_DELAY) { if (x == ILI9XXX_DELAY_FLAG) {
ESP_LOGD(TAG, "Delay %dms", x); cmd &= 0x7F;
delay(x); ESP_LOGV(TAG, "Delay %dms", cmd);
delay(cmd);
} else { } else {
num_args = x & 0x7F; num_args = x & 0x7F;
ESP_LOGD(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, *addr); ESP_LOGV(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, *addr);
this->send_command(cmd, addr, num_args); this->send_command(cmd, addr, num_args);
addr += num_args; addr += num_args;
if (x & 0x80) { if (x & 0x80) {
ESP_LOGD(TAG, "Delay 150ms"); ESP_LOGV(TAG, "Delay 150ms");
delay(150); // NOLINT delay(150); // NOLINT
} }
} }
} }
} }
void ILI9XXXGC9A01A::init_lcd(const uint8_t *addr) {
if (addr == nullptr)
return;
uint8_t cmd, x, num_args;
while ((cmd = *addr++) != 0) {
x = *addr++;
num_args = x & 0x7F;
this->send_command(cmd, addr, num_args);
addr += num_args;
if (x & 0x80)
delay(150); // NOLINT
}
}
// Tell the display controller where we want to draw pixels. // Tell the display controller where we want to draw pixels.
void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
x1 += this->offset_x_; x1 += this->offset_x_;

View file

@ -33,7 +33,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
uint8_t cmd, num_args, bits; uint8_t cmd, num_args, bits;
const uint8_t *addr = init_sequence; const uint8_t *addr = init_sequence;
while ((cmd = *addr++) != 0) { while ((cmd = *addr++) != 0) {
num_args = *addr++ & 0x7F; num_args = *addr++;
if (num_args == ILI9XXX_DELAY_FLAG)
continue;
bits = *addr; bits = *addr;
switch (cmd) { switch (cmd) {
case ILI9XXX_MADCTL: { case ILI9XXX_MADCTL: {
@ -50,13 +52,10 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
break; break;
} }
case ILI9XXX_DELAY:
continue; // no args to skip
default: default:
break; break;
} }
addr += num_args; addr += (num_args & 0x7F);
} }
} }
@ -109,7 +108,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
virtual void set_madctl(); virtual void set_madctl();
void display_(); void display_();
virtual void init_lcd(const uint8_t *addr); void init_lcd_(const uint8_t *addr);
void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2); void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2);
void reset_(); void reset_();
@ -269,7 +268,6 @@ class ILI9XXXS3BoxLite : public ILI9XXXDisplay {
class ILI9XXXGC9A01A : public ILI9XXXDisplay { class ILI9XXXGC9A01A : public ILI9XXXDisplay {
public: public:
ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {} ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {}
void init_lcd(const uint8_t *addr) override;
}; };
//----------- ILI9XXX_24_TFT display -------------- //----------- ILI9XXX_24_TFT display --------------

View file

@ -372,9 +372,9 @@ static const uint8_t PROGMEM INITCMD_GC9A01A[] = {
static const uint8_t PROGMEM INITCMD_ST7735[] = { static const uint8_t PROGMEM INITCMD_ST7735[] = {
ILI9XXX_SWRESET, 0, // Soft reset, then delay 10ms ILI9XXX_SWRESET, 0, // Soft reset, then delay 10ms
ILI9XXX_DELAY, 10, ILI9XXX_DELAY(10),
ILI9XXX_SLPOUT , 0, // Exit Sleep, delay ILI9XXX_SLPOUT , 0, // Exit Sleep, delay
ILI9XXX_DELAY, 10, ILI9XXX_DELAY(10),
ILI9XXX_PIXFMT , 1, 0x05, ILI9XXX_PIXFMT , 1, 0x05,
ILI9XXX_FRMCTR1, 3, // 4: Frame rate control, 3 args + delay: ILI9XXX_FRMCTR1, 3, // 4: Frame rate control, 3 args + delay:
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D) 0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
@ -415,9 +415,9 @@ static const uint8_t PROGMEM INITCMD_ST7735[] = {
0x00, 0x00, 0x02, 0x10, 0x00, 0x00, 0x02, 0x10,
ILI9XXX_MADCTL , 1, 0x00, // Memory Access Control, BGR ILI9XXX_MADCTL , 1, 0x00, // Memory Access Control, BGR
ILI9XXX_NORON , 0, ILI9XXX_NORON , 0,
ILI9XXX_DELAY, 10, ILI9XXX_DELAY(10),
ILI9XXX_DISPON , 0, // Display on ILI9XXX_DISPON , 0, // Display on
ILI9XXX_DELAY, 10, ILI9XXX_DELAY(10),
00, // endo of list 00, // endo of list
}; };

View file

@ -6,7 +6,6 @@ import hashlib
import io import io
from pathlib import Path from pathlib import Path
import re import re
import requests
from magic import Magic from magic import Magic
from esphome import core from esphome import core
@ -15,7 +14,6 @@ from esphome import external_files
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.const import ( from esphome.const import (
__version__,
CONF_DITHER, CONF_DITHER,
CONF_FILE, CONF_FILE,
CONF_ICON, CONF_ICON,
@ -75,31 +73,6 @@ def compute_local_image_path(value: dict) -> Path:
return base_dir / key return base_dir / key
def download_content(url: str, path: Path) -> None:
if not external_files.has_remote_file_changed(url, path):
_LOGGER.debug("Remote file has not changed %s", url)
return
_LOGGER.debug(
"Remote file has changed, downloading from %s to %s",
url,
path,
)
try:
req = requests.get(
url,
timeout=IMAGE_DOWNLOAD_TIMEOUT,
headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"},
)
req.raise_for_status()
except requests.exceptions.RequestException as e:
raise cv.Invalid(f"Could not download from {url}: {e}")
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(req.content)
def download_mdi(value): def download_mdi(value):
validate_cairosvg_installed(value) validate_cairosvg_installed(value)
@ -108,7 +81,7 @@ def download_mdi(value):
url = f"https://raw.githubusercontent.com/Templarian/MaterialDesign/master/svg/{mdi_id}.svg" url = f"https://raw.githubusercontent.com/Templarian/MaterialDesign/master/svg/{mdi_id}.svg"
download_content(url, path) external_files.download_content(url, path, IMAGE_DOWNLOAD_TIMEOUT)
return value return value
@ -117,7 +90,7 @@ def download_image(value):
url = value[CONF_URL] url = value[CONF_URL]
path = compute_local_image_path(value) path = compute_local_image_path(value)
download_content(url, path) external_files.download_content(url, path, IMAGE_DOWNLOAD_TIMEOUT)
return value return value

View file

@ -57,7 +57,7 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
} }
} }
break; break;
#if defined(CONFIG_ESP_CONSOLE_USB_CDC) && (defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)) #if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
case logger::UART_SELECTION_USB_CDC: case logger::UART_SELECTION_USB_CDC:
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
if (esp_usb_console_available_for_read()) { if (esp_usb_console_available_for_read()) {
@ -68,15 +68,15 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
byte = data; byte = data;
} }
break; break;
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 #endif // USE_LOGGER_USB_CDC
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) #ifdef USE_LOGGER_USB_SERIAL_JTAG
case logger::UART_SELECTION_USB_SERIAL_JTAG: { case logger::UART_SELECTION_USB_SERIAL_JTAG: {
if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) { if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) {
byte = data; byte = data;
} }
break; break;
} }
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 #endif // USE_LOGGER_USB_SERIAL_JTAG
default: default:
break; break;
} }
@ -99,19 +99,20 @@ void ImprovSerialComponent::write_data_(std::vector<uint8_t> &data) {
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 #endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3
uart_write_bytes(this->uart_num_, data.data(), data.size()); uart_write_bytes(this->uart_num_, data.data(), data.size());
break; break;
#if defined(CONFIG_ESP_CONSOLE_USB_CDC) && (defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)) #if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
case logger::UART_SELECTION_USB_CDC: { case logger::UART_SELECTION_USB_CDC: {
const char *msg = (char *) data.data(); const char *msg = (char *) data.data();
esp_usb_console_write_buf(msg, data.size()); esp_usb_console_write_buf(msg, data.size());
break; break;
} }
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 #endif // USE_LOGGER_USB_CDC
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) #ifdef USE_LOGGER_USB_SERIAL_JTAG
case logger::UART_SELECTION_USB_SERIAL_JTAG: case logger::UART_SELECTION_USB_SERIAL_JTAG:
usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS); usb_serial_jtag_write_bytes((char *) data.data(), data.size(), 20 / portTICK_PERIOD_MS);
delay(10);
usb_serial_jtag_ll_txfifo_flush(); // fixes for issue in IDF 4.4.7 usb_serial_jtag_ll_txfifo_flush(); // fixes for issue in IDF 4.4.7
break; break;
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3 #endif // USE_LOGGER_USB_SERIAL_JTAG
default: default:
break; break;
} }

View file

@ -31,7 +31,16 @@ void Jsnsr04tComponent::loop() {
} }
void Jsnsr04tComponent::check_buffer_() { void Jsnsr04tComponent::check_buffer_() {
uint8_t checksum = this->buffer_[0] + this->buffer_[1] + this->buffer_[2]; uint8_t checksum = 0;
switch (this->model_) {
case JSN_SR04T:
checksum = this->buffer_[0] + this->buffer_[1] + this->buffer_[2];
break;
case AJ_SR04M:
checksum = this->buffer_[1] + this->buffer_[2];
break;
}
if (this->buffer_[3] == checksum) { if (this->buffer_[3] == checksum) {
uint16_t distance = encode_uint16(this->buffer_[1], this->buffer_[2]); uint16_t distance = encode_uint16(this->buffer_[1], this->buffer_[2]);
if (distance > 250) { if (distance > 250) {
@ -49,6 +58,14 @@ void Jsnsr04tComponent::check_buffer_() {
void Jsnsr04tComponent::dump_config() { void Jsnsr04tComponent::dump_config() {
LOG_SENSOR("", "JST_SR04T Sensor", this); LOG_SENSOR("", "JST_SR04T Sensor", this);
switch (this->model_) {
case JSN_SR04T:
ESP_LOGCONFIG(TAG, " sensor model: jsn_sr04t");
break;
case AJ_SR04M:
ESP_LOGCONFIG(TAG, " sensor model: aj_sr04m");
break;
}
LOG_UPDATE_INTERVAL(this); LOG_UPDATE_INTERVAL(this);
} }

View file

@ -9,9 +9,14 @@
namespace esphome { namespace esphome {
namespace jsn_sr04t { namespace jsn_sr04t {
enum Model {
JSN_SR04T,
AJ_SR04M,
};
class Jsnsr04tComponent : public sensor::Sensor, public PollingComponent, public uart::UARTDevice { class Jsnsr04tComponent : public sensor::Sensor, public PollingComponent, public uart::UARTDevice {
public: public:
// Nothing really public. void set_model(Model model) { this->model_ = model; }
// ========== INTERNAL METHODS ========== // ========== INTERNAL METHODS ==========
void update() override; void update() override;
@ -20,6 +25,7 @@ class Jsnsr04tComponent : public sensor::Sensor, public PollingComponent, public
protected: protected:
void check_buffer_(); void check_buffer_();
Model model_;
std::vector<uint8_t> buffer_; std::vector<uint8_t> buffer_;
}; };

View file

@ -5,6 +5,7 @@ from esphome.const import (
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_METER, UNIT_METER,
ICON_ARROW_EXPAND_VERTICAL, ICON_ARROW_EXPAND_VERTICAL,
CONF_MODEL,
) )
CODEOWNERS = ["@Mafus1"] CODEOWNERS = ["@Mafus1"]
@ -14,6 +15,11 @@ jsn_sr04t_ns = cg.esphome_ns.namespace("jsn_sr04t")
Jsnsr04tComponent = jsn_sr04t_ns.class_( Jsnsr04tComponent = jsn_sr04t_ns.class_(
"Jsnsr04tComponent", sensor.Sensor, cg.PollingComponent, uart.UARTDevice "Jsnsr04tComponent", sensor.Sensor, cg.PollingComponent, uart.UARTDevice
) )
Model = jsn_sr04t_ns.enum("Model")
MODEL = {
"jsn_sr04t": Model.JSN_SR04T,
"aj_sr04m": Model.AJ_SR04M,
}
CONFIG_SCHEMA = ( CONFIG_SCHEMA = (
sensor.sensor_schema( sensor.sensor_schema(
@ -25,6 +31,11 @@ CONFIG_SCHEMA = (
) )
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
.extend(uart.UART_DEVICE_SCHEMA) .extend(uart.UART_DEVICE_SCHEMA)
.extend(
{
cv.Optional(CONF_MODEL, default="jsn_sr04t"): cv.enum(MODEL, upper=False),
}
)
) )
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
@ -42,3 +53,5 @@ async def to_code(config):
var = await sensor.new_sensor(config) var = await sensor.new_sensor(config)
await cg.register_component(var, config) await cg.register_component(var, config)
await uart.register_uart_device(var, config) await uart.register_uart_device(var, config)
cg.add(var.set_model(config[CONF_MODEL]))

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