Compare commits
94 Commits
socket_lat
...
frame_help
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2968e64d1 | ||
|
|
db9a070c42 | ||
|
|
4bff8ef969 | ||
|
|
9ff93142fd | ||
|
|
a4c5384b30 | ||
|
|
b5af2cb4ee | ||
|
|
29b3d7355c | ||
|
|
2201f67045 | ||
|
|
7a364ff63a | ||
|
|
e544f6711e | ||
|
|
31f5bbf623 | ||
|
|
488dc40f2e | ||
|
|
e8e0e34702 | ||
|
|
eebdc9c38f | ||
|
|
65b6d256bc | ||
|
|
428371d685 | ||
|
|
d108219947 | ||
|
|
cdf3ed07ba | ||
|
|
061bbabd09 | ||
|
|
2646ec166b | ||
|
|
bafc57f02e | ||
|
|
549ed6178b | ||
|
|
0c67e06573 | ||
|
|
592a95c565 | ||
|
|
f41ef68b41 | ||
|
|
26af1cf650 | ||
|
|
66b995cffe | ||
|
|
5d0b74db3d | ||
|
|
31e3065600 | ||
|
|
872a70d235 | ||
|
|
7b84eb2903 | ||
|
|
7b4e7108c0 | ||
|
|
d4b42ebf20 | ||
|
|
a8e9c79975 | ||
|
|
47b9c3db1d | ||
|
|
b32ed848b0 | ||
|
|
8fb68804e4 | ||
|
|
e2453dd513 | ||
|
|
ed563b0c83 | ||
|
|
856d679ce2 | ||
|
|
f5ac77634b | ||
|
|
b30d7fb0eb | ||
|
|
fc609f02f3 | ||
|
|
3fbbec81af | ||
|
|
7934618c9c | ||
|
|
c4aee545c3 | ||
|
|
e244b71802 | ||
|
|
2f078d4edf | ||
|
|
4215cc5e6a | ||
|
|
b3911ef37c | ||
|
|
0d1dae175c | ||
|
|
6e95ef06e0 | ||
|
|
d7311b048b | ||
|
|
84a84e769b | ||
|
|
0db37ddf0a | ||
|
|
71577cf6d4 | ||
|
|
8c0546b535 | ||
|
|
9bf527b0b6 | ||
|
|
c7501911bf | ||
|
|
4b82ed5b81 | ||
|
|
1f8ae120d4 | ||
|
|
8769ddcfa9 | ||
|
|
3987b98044 | ||
|
|
0edfa4746a | ||
|
|
c8dcebfb3f | ||
|
|
edd755323c | ||
|
|
4096c943cc | ||
|
|
e4caef77fc | ||
|
|
0d81306481 | ||
|
|
39abbe609a | ||
|
|
326df5752f | ||
|
|
7cf1db1382 | ||
|
|
dcdc2a30c5 | ||
|
|
c0b9f6407c | ||
|
|
85457eeed0 | ||
|
|
3fb10037a9 | ||
|
|
45e08ed584 | ||
|
|
a7449dce92 | ||
|
|
8067caf16f | ||
|
|
5fbb066ee7 | ||
|
|
c9680a1ccb | ||
|
|
03399e6dd6 | ||
|
|
7f838ece00 | ||
|
|
3f87010c0e | ||
|
|
a960d9966d | ||
|
|
02c390c6c3 | ||
|
|
eebefdf026 | ||
|
|
cb748bbb02 | ||
|
|
c35db19995 | ||
|
|
71b493bd8b | ||
|
|
f67e02c653 | ||
|
|
9db52b17f2 | ||
|
|
d728382542 | ||
|
|
d95bbfc6c4 |
@@ -1,4 +1,2 @@
|
||||
[run]
|
||||
omit =
|
||||
esphome/components/*
|
||||
tests/integration/*
|
||||
omit = esphome/components/*
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
ARG BUILD_BASE_VERSION=2025.04.0
|
||||
|
||||
|
||||
FROM ghcr.io/esphome/docker-base:debian-${BUILD_BASE_VERSION} AS base
|
||||
|
||||
RUN git config --system --add safe.directory "*"
|
||||
|
||||
RUN apt update \
|
||||
&& apt install -y \
|
||||
protobuf-compiler
|
||||
|
||||
RUN pip install uv
|
||||
|
||||
RUN useradd esphome -m
|
||||
|
||||
USER esphome
|
||||
ENV VIRTUAL_ENV=/home/esphome/.local/esphome-venv
|
||||
RUN uv venv $VIRTUAL_ENV
|
||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
# Override this set to true in the docker-base image
|
||||
ENV UV_SYSTEM_PYTHON=false
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
COPY requirements.txt ./
|
||||
RUN uv pip install -r requirements.txt
|
||||
COPY requirements_dev.txt requirements_test.txt ./
|
||||
RUN uv pip install -r requirements_dev.txt -r requirements_test.txt
|
||||
|
||||
RUN \
|
||||
platformio settings set enable_telemetry No \
|
||||
&& platformio settings set check_platformio_interval 1000000
|
||||
|
||||
COPY script/platformio_install_deps.py platformio.ini ./
|
||||
RUN ./platformio_install_deps.py platformio.ini --libraries --platforms --tools
|
||||
|
||||
WORKDIR /workspaces
|
||||
@@ -1,17 +1,18 @@
|
||||
{
|
||||
"name": "ESPHome Dev",
|
||||
"context": "..",
|
||||
"dockerFile": "Dockerfile",
|
||||
"image": "ghcr.io/esphome/esphome-lint:dev",
|
||||
"postCreateCommand": [
|
||||
"script/devcontainer-post-create"
|
||||
],
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {}
|
||||
"containerEnv": {
|
||||
"DEVCONTAINER": "1",
|
||||
"PIP_BREAK_SYSTEM_PACKAGES": "1",
|
||||
"PIP_ROOT_USER_ACTION": "ignore"
|
||||
},
|
||||
"runArgs": [
|
||||
"--privileged",
|
||||
"-e",
|
||||
"GIT_EDITOR=code --wait"
|
||||
"ESPHOME_DASHBOARD_USE_PING=1"
|
||||
// uncomment and edit the path in order to pass though local USB serial to the conatiner
|
||||
// , "--device=/dev/ttyACM0"
|
||||
],
|
||||
|
||||
4
.github/actions/build-image/action.yaml
vendored
4
.github/actions/build-image/action.yaml
vendored
@@ -47,7 +47,7 @@ runs:
|
||||
|
||||
- name: Build and push to ghcr by digest
|
||||
id: build-ghcr
|
||||
uses: docker/build-push-action@v6.17.0
|
||||
uses: docker/build-push-action@v6.16.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
@@ -73,7 +73,7 @@ runs:
|
||||
|
||||
- name: Build and push to dockerhub by digest
|
||||
id: build-dockerhub
|
||||
uses: docker/build-push-action@v6.17.0
|
||||
uses: docker/build-push-action@v6.16.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
|
||||
2
.github/workflows/ci-api-proto.yml
vendored
2
.github/workflows/ci-api-proto.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
|
||||
4
.github/workflows/ci-docker.yml
vendored
4
.github/workflows/ci-docker.yml
vendored
@@ -43,11 +43,11 @@ jobs:
|
||||
- "docker"
|
||||
# - "lint"
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: "3.10"
|
||||
python-version: "3.9"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.10.0
|
||||
|
||||
|
||||
46
.github/workflows/ci.yml
vendored
46
.github/workflows/ci.yml
vendored
@@ -20,8 +20,8 @@ permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
DEFAULT_PYTHON: "3.10"
|
||||
PYUPGRADE_TARGET: "--py310-plus"
|
||||
DEFAULT_PYTHON: "3.9"
|
||||
PYUPGRADE_TARGET: "--py39-plus"
|
||||
|
||||
concurrency:
|
||||
# yamllint disable-line rule:line-length
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Generate cache-key
|
||||
id: cache-key
|
||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -152,7 +152,7 @@ jobs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -173,10 +173,10 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macOS-latest
|
||||
@@ -185,24 +185,24 @@ jobs:
|
||||
# Minimize CI resource usage
|
||||
# by only running the Python version
|
||||
# version used for docker images on Windows and macOS
|
||||
- python-version: "3.13"
|
||||
os: windows-latest
|
||||
- python-version: "3.12"
|
||||
os: windows-latest
|
||||
- python-version: "3.10"
|
||||
os: windows-latest
|
||||
- python-version: "3.13"
|
||||
os: macOS-latest
|
||||
- python-version: "3.9"
|
||||
os: windows-latest
|
||||
- python-version: "3.12"
|
||||
os: macOS-latest
|
||||
- python-version: "3.10"
|
||||
os: macOS-latest
|
||||
- python-version: "3.9"
|
||||
os: macOS-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -214,14 +214,14 @@ jobs:
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
./venv/Scripts/activate
|
||||
pytest -vv --cov-report=xml --tb=native -n auto tests
|
||||
pytest -vv --cov-report=xml --tb=native tests
|
||||
- name: Run pytest
|
||||
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pytest -vv --cov-report=xml --tb=native -n auto tests
|
||||
pytest -vv --cov-report=xml --tb=native tests
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5.4.3
|
||||
uses: codecov/codecov-action@v5.4.2
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -232,7 +232,7 @@ jobs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -300,7 +300,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -356,7 +356,7 @@ jobs:
|
||||
count: ${{ steps.list-components.outputs.count }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
with:
|
||||
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
|
||||
fetch-depth: 500
|
||||
@@ -406,7 +406,7 @@ jobs:
|
||||
sudo apt-get install libsdl2-dev
|
||||
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -432,7 +432,7 @@ jobs:
|
||||
matrix: ${{ steps.split.outputs.components }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Split components into 20 groups
|
||||
id: split
|
||||
run: |
|
||||
@@ -462,7 +462,7 @@ jobs:
|
||||
sudo apt-get install libsdl2-dev
|
||||
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
||||
24
.github/workflows/release.yml
vendored
24
.github/workflows/release.yml
vendored
@@ -18,9 +18,8 @@ jobs:
|
||||
outputs:
|
||||
tag: ${{ steps.tag.outputs.tag }}
|
||||
branch_build: ${{ steps.tag.outputs.branch_build }}
|
||||
deploy_env: ${{ steps.tag.outputs.deploy_env }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- name: Get tag
|
||||
id: tag
|
||||
# yamllint disable rule:line-length
|
||||
@@ -28,11 +27,6 @@ jobs:
|
||||
if [[ "${{ github.event_name }}" = "release" ]]; then
|
||||
TAG="${{ github.event.release.tag_name}}"
|
||||
BRANCH_BUILD="false"
|
||||
if [[ "${{ github.event.release.prerelease }}" = "true" ]]; then
|
||||
ENVIRONMENT="beta"
|
||||
else
|
||||
ENVIRONMENT="production"
|
||||
fi
|
||||
else
|
||||
TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
|
||||
today="$(date --utc '+%Y%m%d')"
|
||||
@@ -41,15 +35,12 @@ jobs:
|
||||
if [[ "$BRANCH" != "dev" ]]; then
|
||||
TAG="${TAG}-${BRANCH}"
|
||||
BRANCH_BUILD="true"
|
||||
ENVIRONMENT=""
|
||||
else
|
||||
BRANCH_BUILD="false"
|
||||
ENVIRONMENT="dev"
|
||||
fi
|
||||
fi
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
echo "branch_build=${BRANCH_BUILD}" >> $GITHUB_OUTPUT
|
||||
echo "deploy_env=${ENVIRONMENT}" >> $GITHUB_OUTPUT
|
||||
# yamllint enable rule:line-length
|
||||
|
||||
deploy-pypi:
|
||||
@@ -60,7 +51,7 @@ jobs:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
@@ -92,11 +83,11 @@ jobs:
|
||||
os: "ubuntu-24.04-arm"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: "3.10"
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.10.0
|
||||
@@ -168,7 +159,7 @@ jobs:
|
||||
- ghcr
|
||||
- dockerhub
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
@@ -242,8 +233,9 @@ jobs:
|
||||
deploy-esphome-schema:
|
||||
if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [init]
|
||||
environment: ${{ needs.init.outputs.deploy_env }}
|
||||
needs:
|
||||
- init
|
||||
- deploy-manifest
|
||||
steps:
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@v7.0.1
|
||||
|
||||
6
.github/workflows/sync-device-classes.yml
vendored
6
.github/workflows/sync-device-classes.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Checkout Home Assistant
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
with:
|
||||
repository: home-assistant/core
|
||||
path: lib/home-assistant
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: 3.13
|
||||
python-version: 3.12
|
||||
|
||||
- name: Install Home Assistant
|
||||
run: |
|
||||
|
||||
2
.github/workflows/yaml-lint.yml
vendored
2
.github/workflows/yaml-lint.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Run yamllint
|
||||
uses: frenck/action-yamllint@v1.5.0
|
||||
with:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -143,4 +143,3 @@ sdkconfig.*
|
||||
/components
|
||||
/managed_components
|
||||
|
||||
api-docs/
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.11.10
|
||||
rev: v0.11.9
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
@@ -28,10 +28,10 @@ repos:
|
||||
- --branch=release
|
||||
- --branch=beta
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.20.0
|
||||
rev: v3.15.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py310-plus]
|
||||
args: [--py39-plus]
|
||||
- repo: https://github.com/adrienverge/yamllint.git
|
||||
rev: v1.37.1
|
||||
hooks:
|
||||
|
||||
@@ -96,7 +96,6 @@ esphome/components/ch422g/* @clydebarrow @jesterret
|
||||
esphome/components/chsc6x/* @kkosik20
|
||||
esphome/components/climate/* @esphome/core
|
||||
esphome/components/climate_ir/* @glmnet
|
||||
esphome/components/cm1106/* @andrewjswan
|
||||
esphome/components/color_temperature/* @jesserockz
|
||||
esphome/components/combination/* @Cat-Ion @kahrendt
|
||||
esphome/components/const/* @esphome/core
|
||||
@@ -479,8 +478,6 @@ esphome/components/ufire_ise/* @pvizeli
|
||||
esphome/components/ultrasonic/* @OttoWinter
|
||||
esphome/components/update/* @jesserockz
|
||||
esphome/components/uponor_smatrix/* @kroimon
|
||||
esphome/components/usb_host/* @clydebarrow
|
||||
esphome/components/usb_uart/* @clydebarrow
|
||||
esphome/components/valve/* @esphome/core
|
||||
esphome/components/vbus/* @ssieb
|
||||
esphome/components/veml3235/* @kbx81
|
||||
|
||||
@@ -11,9 +11,7 @@ FROM base-source-${BUILD_TYPE} AS base
|
||||
|
||||
RUN git config --system --add safe.directory "*"
|
||||
|
||||
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||
|
||||
RUN pip install --no-cache-dir -U pip uv==0.6.14
|
||||
RUN pip install uv==0.6.14
|
||||
|
||||
COPY requirements.txt /
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <cinttypes>
|
||||
#include <utility>
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -27,6 +26,7 @@ namespace esphome {
|
||||
namespace api {
|
||||
|
||||
static const char *const TAG = "api.connection";
|
||||
static const char *const STATS_TAG = "api.stats";
|
||||
static const int ESP32_CAMERA_STOP_STREAM = 5000;
|
||||
|
||||
// helper for allowing only unique entries in the queue
|
||||
@@ -63,6 +63,11 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
|
||||
: parent_(parent), deferred_message_queue_(this), initial_state_iterator_(this), list_entities_iterator_(this) {
|
||||
this->proto_write_buffer_.reserve(64);
|
||||
|
||||
// Explicitly initialize stats
|
||||
this->stats_enabled_ = true;
|
||||
this->next_stats_log_ = 0;
|
||||
ESP_LOGD(STATS_TAG, "API Connection created with stats_enabled_=true");
|
||||
|
||||
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
|
||||
auto noise_ctx = parent->get_noise_ctx();
|
||||
if (noise_ctx->has_psk()) {
|
||||
@@ -79,11 +84,7 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
|
||||
#endif
|
||||
}
|
||||
void APIConnection::start() {
|
||||
this->last_traffic_ = App.get_loop_component_start_time();
|
||||
|
||||
// Set next_ping_retry_ to prevent immediate ping
|
||||
// This ensures the first ping happens after the keepalive period
|
||||
this->next_ping_retry_ = this->last_traffic_ + KEEPALIVE_TIMEOUT_MS;
|
||||
this->last_traffic_ = millis();
|
||||
|
||||
APIError err = this->helper_->init();
|
||||
if (err != APIError::OK) {
|
||||
@@ -97,6 +98,64 @@ void APIConnection::start() {
|
||||
this->helper_->set_log_info(this->client_info_);
|
||||
}
|
||||
|
||||
void APIConnection::log_section_stats_() {
|
||||
ESP_LOGI(STATS_TAG, "API Connection Section Runtime Statistics");
|
||||
ESP_LOGI(STATS_TAG, "Period stats (last %" PRIu32 "ms):", this->stats_log_interval_);
|
||||
|
||||
if (this->section_stats_.empty()) {
|
||||
ESP_LOGW(STATS_TAG, "No section stats collected yet");
|
||||
return;
|
||||
}
|
||||
|
||||
// First collect stats we want to display
|
||||
std::vector<std::pair<std::string, const APISectionStats *>> stats_to_display;
|
||||
|
||||
for (const auto &it : this->section_stats_) {
|
||||
const APISectionStats &stats = it.second;
|
||||
if (stats.get_period_count() > 0) {
|
||||
stats_to_display.push_back({it.first, &stats});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by period runtime (descending)
|
||||
std::sort(stats_to_display.begin(), stats_to_display.end(), [](const auto &a, const auto &b) {
|
||||
return a.second->get_period_time_ms() > b.second->get_period_time_ms();
|
||||
});
|
||||
|
||||
// Log top components by period runtime
|
||||
for (const auto &it : stats_to_display) {
|
||||
const std::string §ion = it.first;
|
||||
const APISectionStats *stats = it.second;
|
||||
|
||||
ESP_LOGI(STATS_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms", section.c_str(),
|
||||
stats->get_period_count(), stats->get_period_avg_time_ms(), stats->get_period_max_time_ms(),
|
||||
stats->get_period_time_ms());
|
||||
}
|
||||
|
||||
// Log total stats since boot
|
||||
ESP_LOGI(STATS_TAG, "Total stats (since boot):");
|
||||
|
||||
// Re-sort by total runtime for all-time stats
|
||||
std::sort(stats_to_display.begin(), stats_to_display.end(),
|
||||
[](const auto &a, const auto &b) { return a.second->get_total_time_ms() > b.second->get_total_time_ms(); });
|
||||
|
||||
for (const auto &it : stats_to_display) {
|
||||
const std::string §ion = it.first;
|
||||
const APISectionStats *stats = it.second;
|
||||
|
||||
ESP_LOGI(STATS_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms", section.c_str(),
|
||||
stats->get_total_count(), stats->get_total_avg_time_ms(), stats->get_total_max_time_ms(),
|
||||
stats->get_total_time_ms());
|
||||
}
|
||||
}
|
||||
|
||||
void APIConnection::reset_section_stats_() {
|
||||
ESP_LOGD(STATS_TAG, "Resetting API section stats, sections count: %u", this->section_stats_.size());
|
||||
for (auto &it : this->section_stats_) {
|
||||
it.second.reset_period_stats();
|
||||
}
|
||||
}
|
||||
|
||||
APIConnection::~APIConnection() {
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
if (bluetooth_proxy::global_bluetooth_proxy->get_api_connection() == this) {
|
||||
@@ -111,6 +170,9 @@ APIConnection::~APIConnection() {
|
||||
}
|
||||
|
||||
void APIConnection::loop() {
|
||||
// Measure total time for entire loop function
|
||||
const uint32_t loop_start_time = millis();
|
||||
|
||||
if (this->remove_)
|
||||
return;
|
||||
|
||||
@@ -128,7 +190,16 @@ void APIConnection::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t now = millis();
|
||||
uint32_t start_time;
|
||||
uint32_t duration;
|
||||
|
||||
// Section: Helper Loop
|
||||
start_time = millis();
|
||||
APIError err = this->helper_->loop();
|
||||
duration = millis() - start_time;
|
||||
this->section_stats_["helper_loop"].record_time(duration);
|
||||
|
||||
if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->client_combined_info_.c_str(),
|
||||
@@ -136,55 +207,69 @@ void APIConnection::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if socket has data ready before attempting to read
|
||||
if (this->helper_->is_socket_ready()) {
|
||||
ReadPacketBuffer buffer;
|
||||
err = this->helper_->read_packet(&buffer);
|
||||
if (err == APIError::WOULD_BLOCK) {
|
||||
// pass
|
||||
} else if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
|
||||
ESP_LOGW(TAG, "%s: Connection reset", this->client_combined_info_.c_str());
|
||||
} else if (err == APIError::CONNECTION_CLOSED) {
|
||||
ESP_LOGW(TAG, "%s: Connection closed", this->client_combined_info_.c_str());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err),
|
||||
errno);
|
||||
}
|
||||
return;
|
||||
// Section: Read Packet
|
||||
start_time = millis();
|
||||
ReadPacketBuffer buffer;
|
||||
err = this->helper_->read_packet(&buffer);
|
||||
duration = millis() - start_time;
|
||||
this->section_stats_["read_packet"].record_time(duration);
|
||||
|
||||
if (err == APIError::WOULD_BLOCK) {
|
||||
// pass
|
||||
} else if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
|
||||
ESP_LOGW(TAG, "%s: Connection reset", this->client_combined_info_.c_str());
|
||||
} else if (err == APIError::CONNECTION_CLOSED) {
|
||||
ESP_LOGW(TAG, "%s: Connection closed", this->client_combined_info_.c_str());
|
||||
} else {
|
||||
this->last_traffic_ = App.get_loop_component_start_time();
|
||||
// read a packet
|
||||
if (buffer.data_len > 0) {
|
||||
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
|
||||
} else {
|
||||
this->read_message(0, buffer.type, nullptr);
|
||||
}
|
||||
if (this->remove_)
|
||||
return;
|
||||
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err),
|
||||
errno);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
this->last_traffic_ = now;
|
||||
|
||||
// Section: Process Message
|
||||
start_time = millis();
|
||||
this->read_message(buffer.data_len, buffer.type, &buffer.container[buffer.data_offset]);
|
||||
duration = millis() - start_time;
|
||||
this->section_stats_["process_message"].record_time(duration);
|
||||
|
||||
if (this->remove_)
|
||||
return;
|
||||
}
|
||||
|
||||
// Section: Process Queue
|
||||
start_time = millis();
|
||||
if (!this->deferred_message_queue_.empty() && this->helper_->can_write_without_blocking()) {
|
||||
this->deferred_message_queue_.process_queue();
|
||||
}
|
||||
duration = millis() - start_time;
|
||||
this->section_stats_["process_queue"].record_time(duration);
|
||||
|
||||
// Section: Iterator Advance
|
||||
start_time = millis();
|
||||
if (!this->list_entities_iterator_.completed())
|
||||
this->list_entities_iterator_.advance();
|
||||
if (!this->initial_state_iterator_.completed() && this->list_entities_iterator_.completed())
|
||||
this->initial_state_iterator_.advance();
|
||||
duration = millis() - start_time;
|
||||
this->section_stats_["iterator_advance"].record_time(duration);
|
||||
|
||||
// Section: Keepalive
|
||||
start_time = millis();
|
||||
static uint32_t keepalive = 60000;
|
||||
static uint8_t max_ping_retries = 60;
|
||||
static uint16_t ping_retry_interval = 1000;
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
|
||||
if (this->sent_ping_) {
|
||||
// Disconnect if not responded within 2.5*keepalive
|
||||
if (now - this->last_traffic_ > (KEEPALIVE_TIMEOUT_MS * 5) / 2) {
|
||||
if (now - this->last_traffic_ > (keepalive * 5) / 2) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_combined_info_.c_str());
|
||||
}
|
||||
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && now > this->next_ping_retry_) {
|
||||
} else if (now - this->last_traffic_ > keepalive && now > this->next_ping_retry_) {
|
||||
ESP_LOGVV(TAG, "Sending keepalive PING...");
|
||||
this->sent_ping_ = this->send_ping_request(PingRequest());
|
||||
if (!this->sent_ping_) {
|
||||
@@ -203,8 +288,12 @@ void APIConnection::loop() {
|
||||
}
|
||||
}
|
||||
}
|
||||
duration = millis() - start_time;
|
||||
this->section_stats_["keepalive"].record_time(duration);
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
// Section: Camera
|
||||
start_time = millis();
|
||||
if (this->image_reader_.available() && this->helper_->can_write_without_blocking()) {
|
||||
// Message will use 8 more bytes than the minimum size, and typical
|
||||
// MTU is 1500. Sometimes users will see as low as 1460 MTU.
|
||||
@@ -243,8 +332,12 @@ void APIConnection::loop() {
|
||||
this->image_reader_.return_image();
|
||||
}
|
||||
}
|
||||
duration = millis() - start_time;
|
||||
this->section_stats_["camera"].record_time(duration);
|
||||
#endif
|
||||
|
||||
// Section: State Subscriptions
|
||||
start_time = millis();
|
||||
if (state_subs_at_ != -1) {
|
||||
const auto &subs = this->parent_->get_state_subs();
|
||||
if (state_subs_at_ >= (int) subs.size()) {
|
||||
@@ -260,6 +353,43 @@ void APIConnection::loop() {
|
||||
}
|
||||
}
|
||||
}
|
||||
duration = millis() - start_time;
|
||||
this->section_stats_["state_subs"].record_time(duration);
|
||||
|
||||
// Log stats periodically
|
||||
if (this->stats_enabled_) {
|
||||
// If next_stats_log_ is 0, initialize it
|
||||
if (this->next_stats_log_ == 0) {
|
||||
this->next_stats_log_ = now + this->stats_log_interval_;
|
||||
ESP_LOGI(STATS_TAG, "API section stats logging enabled, next log at %u", this->next_stats_log_);
|
||||
} else if (now >= this->next_stats_log_) {
|
||||
ESP_LOGI(STATS_TAG, "Logging API section stats now (current time: %u, scheduled time: %u)", now,
|
||||
this->next_stats_log_);
|
||||
// Force logging even if no stats are collected yet
|
||||
ESP_LOGI(STATS_TAG, "Stats collection status: enabled=%d, sections=%u", this->stats_enabled_,
|
||||
this->section_stats_.size());
|
||||
|
||||
// Explicitly log some stats we know should exist
|
||||
ESP_LOGI(STATS_TAG, "Record count for key sections: helper_loop=%u, read_packet=%u, total_loop=%u",
|
||||
this->section_stats_["helper_loop"].get_period_count(),
|
||||
this->section_stats_["read_packet"].get_period_count(),
|
||||
this->section_stats_["total_loop"].get_period_count());
|
||||
|
||||
this->log_section_stats_();
|
||||
this->reset_section_stats_();
|
||||
this->next_stats_log_ = now + this->stats_log_interval_;
|
||||
ESP_LOGI(STATS_TAG, "Next API section stats log scheduled for %u", this->next_stats_log_);
|
||||
}
|
||||
}
|
||||
|
||||
// Record total loop execution time
|
||||
const uint32_t total_loop_duration = millis() - loop_start_time;
|
||||
this->section_stats_["total_loop"].record_time(total_loop_duration);
|
||||
|
||||
// Log a warning if the loop takes longer than 30ms
|
||||
if (total_loop_duration > 30) {
|
||||
ESP_LOGW(STATS_TAG, "API loop took %ums, which exceeds the recommended 30ms limit", total_loop_duration);
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_default_unique_id(const std::string &component_type, EntityBase *entity) {
|
||||
@@ -1652,11 +1782,24 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
|
||||
return false;
|
||||
}
|
||||
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) {
|
||||
if (!this->try_to_clear_buffer(message_type != 29)) { // SubscribeLogsResponse
|
||||
// Track send_buffer time
|
||||
const uint32_t start_time = millis();
|
||||
|
||||
if (this->remove_)
|
||||
return false;
|
||||
|
||||
uint32_t check_block_start = millis();
|
||||
if (!this->try_to_clear_buffer(true)) {
|
||||
return false;
|
||||
}
|
||||
uint32_t check_block_duration = millis() - check_block_start;
|
||||
this->section_stats_["try_to_clear_buffer"].record_time(check_block_duration);
|
||||
|
||||
uint32_t write_start = millis();
|
||||
APIError err = this->helper_->write_packet(message_type, buffer.get_buffer()->data(), buffer.get_buffer()->size());
|
||||
uint32_t write_duration = millis() - write_start;
|
||||
this->section_stats_["write_packet"].record_time(write_duration);
|
||||
|
||||
APIError err = this->helper_->write_protobuf_packet(message_type, buffer);
|
||||
if (err == APIError::WOULD_BLOCK)
|
||||
return false;
|
||||
if (err != APIError::OK) {
|
||||
@@ -1669,6 +1812,17 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Measure total send_buffer function time
|
||||
uint32_t total_duration = millis() - start_time;
|
||||
this->section_stats_["send_buffer_total"].record_time(total_duration);
|
||||
|
||||
// Log a warning if send_buffer takes longer than 15ms
|
||||
if (total_duration > 15) {
|
||||
ESP_LOGW(STATS_TAG, "send_buffer took %ums (message_type=%u, size=%u)", total_duration, message_type,
|
||||
buffer.get_buffer()->size());
|
||||
}
|
||||
|
||||
// Do not set last_traffic_ on send
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11,13 +11,14 @@
|
||||
#include "esphome/core/entity_base.h"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
// Keepalive timeout in milliseconds
|
||||
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
|
||||
|
||||
using send_message_t = bool (APIConnection::*)(void *);
|
||||
|
||||
/*
|
||||
@@ -64,6 +65,70 @@ class APIConnection : public APIServerConnection {
|
||||
APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
|
||||
virtual ~APIConnection();
|
||||
|
||||
// Performance statistics class for API loop sections
|
||||
class APISectionStats {
|
||||
public:
|
||||
APISectionStats()
|
||||
: period_count_(0),
|
||||
total_count_(0),
|
||||
period_time_ms_(0),
|
||||
total_time_ms_(0),
|
||||
period_max_time_ms_(0),
|
||||
total_max_time_ms_(0) {}
|
||||
|
||||
void record_time(uint32_t duration_ms) {
|
||||
// Update period counters
|
||||
this->period_count_++;
|
||||
this->period_time_ms_ += duration_ms;
|
||||
if (duration_ms > this->period_max_time_ms_)
|
||||
this->period_max_time_ms_ = duration_ms;
|
||||
|
||||
// Update total counters
|
||||
this->total_count_++;
|
||||
this->total_time_ms_ += duration_ms;
|
||||
if (duration_ms > this->total_max_time_ms_)
|
||||
this->total_max_time_ms_ = duration_ms;
|
||||
|
||||
// Log if this is the first record in this period
|
||||
if (this->period_count_ == 1) {
|
||||
ESP_LOGV("api.stats", "First time recording stats for this section: %u ms", duration_ms);
|
||||
}
|
||||
}
|
||||
|
||||
void reset_period_stats() {
|
||||
this->period_count_ = 0;
|
||||
this->period_time_ms_ = 0;
|
||||
this->period_max_time_ms_ = 0;
|
||||
}
|
||||
|
||||
// Period stats (reset each logging interval)
|
||||
uint32_t get_period_count() const { return this->period_count_; }
|
||||
uint32_t get_period_time_ms() const { return this->period_time_ms_; }
|
||||
uint32_t get_period_max_time_ms() const { return this->period_max_time_ms_; }
|
||||
float get_period_avg_time_ms() const {
|
||||
return this->period_count_ > 0 ? this->period_time_ms_ / static_cast<float>(this->period_count_) : 0.0f;
|
||||
}
|
||||
|
||||
// Total stats (persistent until reboot)
|
||||
uint32_t get_total_count() const { return this->total_count_; }
|
||||
uint32_t get_total_time_ms() const { return this->total_time_ms_; }
|
||||
uint32_t get_total_max_time_ms() const { return this->total_max_time_ms_; }
|
||||
float get_total_avg_time_ms() const {
|
||||
return this->total_count_ > 0 ? this->total_time_ms_ / static_cast<float>(this->total_count_) : 0.0f;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Period stats (reset each logging interval)
|
||||
uint32_t period_count_;
|
||||
uint32_t period_time_ms_;
|
||||
uint32_t period_max_time_ms_;
|
||||
|
||||
// Total stats (persistent until reboot)
|
||||
uint32_t total_count_;
|
||||
uint32_t total_time_ms_;
|
||||
uint32_t total_max_time_ms_;
|
||||
};
|
||||
|
||||
void start();
|
||||
void loop();
|
||||
|
||||
@@ -408,14 +473,7 @@ class APIConnection : public APIServerConnection {
|
||||
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
|
||||
// FIXME: ensure no recursive writes can happen
|
||||
this->proto_write_buffer_.clear();
|
||||
// Get header padding size - used for both reserve and insert
|
||||
uint8_t header_padding = this->helper_->frame_header_padding();
|
||||
// Reserve space for header padding + message + footer
|
||||
// - Header padding: space for protocol headers (7 bytes for Noise, 6 for Plaintext)
|
||||
// - Footer: space for MAC (16 bytes for Noise, 0 for Plaintext)
|
||||
this->proto_write_buffer_.reserve(reserve_size + header_padding + this->helper_->frame_footer_size());
|
||||
// Insert header padding bytes so message encoding starts at the correct position
|
||||
this->proto_write_buffer_.insert(this->proto_write_buffer_.begin(), header_padding, 0);
|
||||
this->proto_write_buffer_.reserve(reserve_size);
|
||||
return {&this->proto_write_buffer_};
|
||||
}
|
||||
bool try_to_clear_buffer(bool log_out_of_space);
|
||||
@@ -556,6 +614,17 @@ class APIConnection : public APIServerConnection {
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
ListEntitiesIterator list_entities_iterator_;
|
||||
int state_subs_at_ = -1;
|
||||
|
||||
// API loop section performance statistics
|
||||
std::map<std::string, APISectionStats> section_stats_;
|
||||
uint32_t stats_log_interval_{60000}; // 60 seconds default
|
||||
uint32_t next_stats_log_{0};
|
||||
bool stats_enabled_{true};
|
||||
void log_section_stats_();
|
||||
void reset_section_stats_();
|
||||
|
||||
// Method to enable/disable section stats
|
||||
void set_stats_enabled(bool enabled) { this->stats_enabled_ = enabled; }
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "proto.h"
|
||||
#include "api_pb2_size.h"
|
||||
#include <cstring>
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
@@ -67,7 +66,7 @@ const char *api_error_to_str(APIError err) {
|
||||
}
|
||||
|
||||
// Helper method to buffer data from IOVs
|
||||
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
|
||||
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, size_t total_write_len) {
|
||||
SendBuffer buffer;
|
||||
buffer.data.reserve(total_write_len);
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
@@ -85,13 +84,13 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
|
||||
if (iovcnt == 0)
|
||||
return APIError::OK; // Nothing to do, success
|
||||
|
||||
uint16_t total_write_len = 0;
|
||||
size_t total_write_len = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
ESP_LOGVV(TAG, "Sending raw: %s",
|
||||
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
|
||||
#endif
|
||||
total_write_len += static_cast<uint16_t>(iov[i].iov_len);
|
||||
total_write_len += iov[i].iov_len;
|
||||
}
|
||||
|
||||
// Try to send any existing buffered data first if there is any
|
||||
@@ -123,22 +122,22 @@ APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
|
||||
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", this->info_.c_str(), errno);
|
||||
this->state_ = State::FAILED;
|
||||
return APIError::SOCKET_WRITE_FAILED; // Socket write failed
|
||||
} else if (static_cast<uint16_t>(sent) < total_write_len) {
|
||||
} else if (static_cast<size_t>(sent) < total_write_len) {
|
||||
// Partially sent, buffer the remaining data
|
||||
SendBuffer buffer;
|
||||
uint16_t to_consume = static_cast<uint16_t>(sent);
|
||||
uint16_t remaining = total_write_len - static_cast<uint16_t>(sent);
|
||||
size_t to_consume = sent;
|
||||
size_t remaining = total_write_len - sent;
|
||||
|
||||
buffer.data.reserve(remaining);
|
||||
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
if (to_consume >= iov[i].iov_len) {
|
||||
// This segment was fully sent
|
||||
to_consume -= static_cast<uint16_t>(iov[i].iov_len);
|
||||
to_consume -= iov[i].iov_len;
|
||||
} else {
|
||||
// This segment was partially sent or not sent at all
|
||||
const uint8_t *data = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume;
|
||||
uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_consume;
|
||||
size_t len = iov[i].iov_len - to_consume;
|
||||
buffer.data.insert(buffer.data.end(), data, data + len);
|
||||
to_consume = 0;
|
||||
}
|
||||
@@ -191,28 +190,6 @@ APIError APIFrameHelper::try_send_tx_buf_() {
|
||||
return APIError::OK; // All buffers sent successfully
|
||||
}
|
||||
|
||||
APIError APIFrameHelper::init_common_() {
|
||||
if (state_ != State::INITIALIZE || this->socket_ == nullptr) {
|
||||
ESP_LOGVV(TAG, "%s: Bad state for init %d", this->info_.c_str(), (int) state_);
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
int err = this->socket_->setblocking(false);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
ESP_LOGVV(TAG, "%s: Setting nonblocking failed with errno %d", this->info_.c_str(), errno);
|
||||
return APIError::TCP_NONBLOCKING_FAILED;
|
||||
}
|
||||
|
||||
int enable = 1;
|
||||
err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
ESP_LOGVV(TAG, "%s: Setting nodelay failed with errno %d", this->info_.c_str(), errno);
|
||||
return APIError::TCP_NODELAY_FAILED;
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->info_.c_str(), ##__VA_ARGS__)
|
||||
// uncomment to log raw packets
|
||||
//#define HELPER_LOG_PACKETS
|
||||
@@ -261,9 +238,23 @@ std::string noise_err_to_str(int err) {
|
||||
|
||||
/// Initialize the frame helper, returns OK if successful.
|
||||
APIError APINoiseFrameHelper::init() {
|
||||
APIError err = init_common_();
|
||||
if (err != APIError::OK) {
|
||||
return err;
|
||||
if (state_ != State::INITIALIZE || this->socket_ == nullptr) {
|
||||
HELPER_LOG("Bad state for init %d", (int) state_);
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
int err = this->socket_->setblocking(false);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Setting nonblocking failed with errno %d", errno);
|
||||
return APIError::TCP_NONBLOCKING_FAILED;
|
||||
}
|
||||
|
||||
int enable = 1;
|
||||
err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Setting nodelay failed with errno %d", errno);
|
||||
return APIError::TCP_NODELAY_FAILED;
|
||||
}
|
||||
|
||||
// init prologue
|
||||
@@ -275,16 +266,16 @@ APIError APINoiseFrameHelper::init() {
|
||||
/// Run through handshake messages (if in that phase)
|
||||
APIError APINoiseFrameHelper::loop() {
|
||||
APIError err = state_action_();
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
if (err == APIError::WOULD_BLOCK)
|
||||
return APIError::OK;
|
||||
if (err != APIError::OK)
|
||||
return err;
|
||||
}
|
||||
if (!this->tx_buf_.empty()) {
|
||||
err = try_send_tx_buf_();
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
||||
if (this->tx_buf_.empty())
|
||||
return APIError::OK;
|
||||
err = try_send_tx_buf_();
|
||||
if (err == APIError::WOULD_BLOCK)
|
||||
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
||||
return err;
|
||||
}
|
||||
|
||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
||||
@@ -310,7 +301,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
// read header
|
||||
if (rx_header_buf_len_ < 3) {
|
||||
// no header information yet
|
||||
uint8_t to_read = 3 - rx_header_buf_len_;
|
||||
size_t to_read = 3 - rx_header_buf_len_;
|
||||
ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
@@ -324,8 +315,8 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
HELPER_LOG("Connection closed");
|
||||
return APIError::CONNECTION_CLOSED;
|
||||
}
|
||||
rx_header_buf_len_ += static_cast<uint8_t>(received);
|
||||
if (static_cast<uint8_t>(received) != to_read) {
|
||||
rx_header_buf_len_ += received;
|
||||
if ((size_t) received != to_read) {
|
||||
// not a full read
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
@@ -357,7 +348,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
|
||||
if (rx_buf_len_ < msg_size) {
|
||||
// more data to read
|
||||
uint16_t to_read = msg_size - rx_buf_len_;
|
||||
size_t to_read = msg_size - rx_buf_len_;
|
||||
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
@@ -371,8 +362,8 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
HELPER_LOG("Connection closed");
|
||||
return APIError::CONNECTION_CLOSED;
|
||||
}
|
||||
rx_buf_len_ += static_cast<uint16_t>(received);
|
||||
if (static_cast<uint16_t>(received) != to_read) {
|
||||
rx_buf_len_ += received;
|
||||
if ((size_t) received != to_read) {
|
||||
// not all read
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
@@ -421,8 +412,6 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
// ignore contents, may be used in future for flags
|
||||
// Reserve space for: existing prologue + 2 size bytes + frame data
|
||||
prologue_.reserve(prologue_.size() + 2 + frame.msg.size());
|
||||
prologue_.push_back((uint8_t) (frame.msg.size() >> 8));
|
||||
prologue_.push_back((uint8_t) frame.msg.size());
|
||||
prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end());
|
||||
@@ -431,20 +420,16 @@ APIError APINoiseFrameHelper::state_action_() {
|
||||
}
|
||||
if (state_ == State::SERVER_HELLO) {
|
||||
// send server hello
|
||||
const std::string &name = App.get_name();
|
||||
const std::string &mac = get_mac_address();
|
||||
|
||||
std::vector<uint8_t> msg;
|
||||
// Reserve space for: 1 byte proto + name + null + mac + null
|
||||
msg.reserve(1 + name.size() + 1 + mac.size() + 1);
|
||||
|
||||
// chosen proto
|
||||
msg.push_back(0x01);
|
||||
|
||||
// node name, terminated by null byte
|
||||
const std::string &name = App.get_name();
|
||||
const uint8_t *name_ptr = reinterpret_cast<const uint8_t *>(name.c_str());
|
||||
msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1);
|
||||
// node mac, terminated by null byte
|
||||
const std::string &mac = get_mac_address();
|
||||
const uint8_t *mac_ptr = reinterpret_cast<const uint8_t *>(mac.c_str());
|
||||
msg.insert(msg.end(), mac_ptr, mac_ptr + mac.size() + 1);
|
||||
|
||||
@@ -539,18 +524,16 @@ void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &rea
|
||||
std::vector<uint8_t> data;
|
||||
data.resize(reason.length() + 1);
|
||||
data[0] = 0x01; // failure
|
||||
|
||||
// Copy error message in bulk
|
||||
if (!reason.empty()) {
|
||||
std::memcpy(data.data() + 1, reason.c_str(), reason.length());
|
||||
for (size_t i = 0; i < reason.length(); i++) {
|
||||
data[i + 1] = (uint8_t) reason[i];
|
||||
}
|
||||
|
||||
// temporarily remove failed state
|
||||
auto orig_state = state_;
|
||||
state_ = State::EXPLICIT_REJECT;
|
||||
write_frame_(data.data(), data.size());
|
||||
state_ = orig_state;
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
int err;
|
||||
APIError aerr;
|
||||
@@ -578,7 +561,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return APIError::CIPHERSTATE_DECRYPT_FAILED;
|
||||
}
|
||||
|
||||
uint16_t msg_size = mbuf.size;
|
||||
size_t msg_size = mbuf.size;
|
||||
uint8_t *msg_data = frame.msg.data();
|
||||
if (msg_size < 4) {
|
||||
state_ = State::FAILED;
|
||||
@@ -604,7 +587,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
buffer->type = type;
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
|
||||
APIError APINoiseFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
|
||||
int err;
|
||||
APIError aerr;
|
||||
aerr = state_action_();
|
||||
@@ -616,36 +599,31 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuf
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||
// Message data starts after padding
|
||||
uint16_t payload_len = raw_buffer->size() - frame_header_padding_;
|
||||
uint16_t padding = 0;
|
||||
uint16_t msg_len = 4 + payload_len + padding;
|
||||
size_t padding = 0;
|
||||
size_t msg_len = 4 + payload_len + padding;
|
||||
size_t frame_len = 3 + msg_len + noise_cipherstate_get_mac_length(send_cipher_);
|
||||
auto tmpbuf = std::unique_ptr<uint8_t[]>{new (std::nothrow) uint8_t[frame_len]};
|
||||
if (tmpbuf == nullptr) {
|
||||
HELPER_LOG("Could not allocate for writing packet");
|
||||
return APIError::OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// We need to resize to include MAC space, but we already reserved it in create_buffer
|
||||
raw_buffer->resize(raw_buffer->size() + frame_footer_size_);
|
||||
|
||||
// Write the noise header in the padded area
|
||||
// Buffer layout:
|
||||
// [0] - 0x01 indicator byte
|
||||
// [1-2] - Size of encrypted payload (filled after encryption)
|
||||
// [3-4] - Message type (encrypted)
|
||||
// [5-6] - Payload length (encrypted)
|
||||
// [7...] - Actual payload data (encrypted)
|
||||
uint8_t *buf_start = raw_buffer->data();
|
||||
buf_start[0] = 0x01; // indicator
|
||||
// buf_start[1], buf_start[2] to be set later after encryption
|
||||
tmpbuf[0] = 0x01; // indicator
|
||||
// tmpbuf[1], tmpbuf[2] to be set later
|
||||
const uint8_t msg_offset = 3;
|
||||
buf_start[msg_offset + 0] = (uint8_t) (type >> 8); // type high byte
|
||||
buf_start[msg_offset + 1] = (uint8_t) type; // type low byte
|
||||
buf_start[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len high byte
|
||||
buf_start[msg_offset + 3] = (uint8_t) payload_len; // data_len low byte
|
||||
// payload data is already in the buffer starting at position 7
|
||||
const uint8_t payload_offset = msg_offset + 4;
|
||||
tmpbuf[msg_offset + 0] = (uint8_t) (type >> 8); // type
|
||||
tmpbuf[msg_offset + 1] = (uint8_t) type;
|
||||
tmpbuf[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len
|
||||
tmpbuf[msg_offset + 3] = (uint8_t) payload_len;
|
||||
// copy data
|
||||
std::copy(payload, payload + payload_len, &tmpbuf[payload_offset]);
|
||||
// fill padding with zeros
|
||||
std::fill(&tmpbuf[payload_offset + payload_len], &tmpbuf[frame_len], 0);
|
||||
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
// The capacity parameter should be msg_len + frame_footer_size_ (MAC length) to allow space for encryption
|
||||
noise_buffer_set_inout(mbuf, buf_start + msg_offset, msg_len, msg_len + frame_footer_size_);
|
||||
noise_buffer_set_inout(mbuf, &tmpbuf[msg_offset], msg_len, frame_len - msg_offset);
|
||||
err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
@@ -653,20 +631,18 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuf
|
||||
return APIError::CIPHERSTATE_ENCRYPT_FAILED;
|
||||
}
|
||||
|
||||
uint16_t total_len = 3 + mbuf.size;
|
||||
buf_start[1] = (uint8_t) (mbuf.size >> 8);
|
||||
buf_start[2] = (uint8_t) mbuf.size;
|
||||
size_t total_len = 3 + mbuf.size;
|
||||
tmpbuf[1] = (uint8_t) (mbuf.size >> 8);
|
||||
tmpbuf[2] = (uint8_t) mbuf.size;
|
||||
|
||||
struct iovec iov;
|
||||
// Point iov_base to the beginning of the buffer (no unused padding in Noise)
|
||||
// We send the entire frame: indicator + size + encrypted(type + data_len + payload + MAC)
|
||||
iov.iov_base = buf_start;
|
||||
iov.iov_base = &tmpbuf[0];
|
||||
iov.iov_len = total_len;
|
||||
|
||||
// write raw to not have two packets sent if NAGLE disabled
|
||||
return this->write_raw_(&iov, 1);
|
||||
return APIFrameHelper::write_raw_(&iov, 1);
|
||||
}
|
||||
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
|
||||
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
|
||||
uint8_t header[3];
|
||||
header[0] = 0x01; // indicator
|
||||
header[1] = (uint8_t) (len >> 8);
|
||||
@@ -676,12 +652,12 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
|
||||
iov[0].iov_base = header;
|
||||
iov[0].iov_len = 3;
|
||||
if (len == 0) {
|
||||
return this->write_raw_(iov, 1);
|
||||
return APIFrameHelper::write_raw_(iov, 1);
|
||||
}
|
||||
iov[1].iov_base = const_cast<uint8_t *>(data);
|
||||
iov[1].iov_len = len;
|
||||
|
||||
return this->write_raw_(iov, 2);
|
||||
return APIFrameHelper::write_raw_(iov, 2);
|
||||
}
|
||||
|
||||
/** Initiate the data structures for the handshake.
|
||||
@@ -752,8 +728,6 @@ APIError APINoiseFrameHelper::check_handshake_finished_() {
|
||||
return APIError::HANDSHAKESTATE_SPLIT_FAILED;
|
||||
}
|
||||
|
||||
frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_);
|
||||
|
||||
HELPER_LOG("Handshake complete!");
|
||||
noise_handshakestate_free(handshake_);
|
||||
handshake_ = nullptr;
|
||||
@@ -792,9 +766,22 @@ void noise_rand_bytes(void *output, size_t len) {
|
||||
|
||||
/// Initialize the frame helper, returns OK if successful.
|
||||
APIError APIPlaintextFrameHelper::init() {
|
||||
APIError err = init_common_();
|
||||
if (err != APIError::OK) {
|
||||
return err;
|
||||
if (state_ != State::INITIALIZE || this->socket_ == nullptr) {
|
||||
HELPER_LOG("Bad state for init %d", (int) state_);
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
int err = this->socket_->setblocking(false);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Setting nonblocking failed with errno %d", errno);
|
||||
return APIError::TCP_NONBLOCKING_FAILED;
|
||||
}
|
||||
int enable = 1;
|
||||
err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Setting nodelay failed with errno %d", errno);
|
||||
return APIError::TCP_NODELAY_FAILED;
|
||||
}
|
||||
|
||||
state_ = State::DATA;
|
||||
@@ -805,13 +792,12 @@ APIError APIPlaintextFrameHelper::loop() {
|
||||
if (state_ != State::DATA) {
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
if (!this->tx_buf_.empty()) {
|
||||
APIError err = try_send_tx_buf_();
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
||||
if (this->tx_buf_.empty())
|
||||
return APIError::OK;
|
||||
APIError err = try_send_tx_buf_();
|
||||
if (err == APIError::WOULD_BLOCK)
|
||||
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
||||
return err;
|
||||
}
|
||||
|
||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
||||
@@ -836,7 +822,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
// there is no data on the wire (which is the common case).
|
||||
// This results in faster failure detection compared to
|
||||
// attempting to read multiple bytes at once.
|
||||
ssize_t received = this->socket_->read(&data, 1);
|
||||
ssize_t received = socket_->read(&data, 1);
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
@@ -887,7 +873,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
// - At least 2 bytes in the buffer for the varints
|
||||
// Buffer layout:
|
||||
// First 1-3 bytes: Message size varint (variable length)
|
||||
// - 2 bytes would only allow up to 16383, which is less than noise's UINT16_MAX (65535)
|
||||
// - 2 bytes would only allow up to 16383, which is less than noise's 65535
|
||||
// - 3 bytes allows up to 2097151, ensuring we support at least as much as noise
|
||||
// Remaining 1-2 bytes: Message type varint (variable length)
|
||||
// We now attempt to parse both varints. If either is incomplete,
|
||||
@@ -900,26 +886,14 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msg_size_varint->as_uint32() > std::numeric_limits<uint16_t>::max()) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad packet: message size %" PRIu32 " exceeds maximum %u", msg_size_varint->as_uint32(),
|
||||
std::numeric_limits<uint16_t>::max());
|
||||
return APIError::BAD_DATA_PACKET;
|
||||
}
|
||||
rx_header_parsed_len_ = msg_size_varint->as_uint16();
|
||||
rx_header_parsed_len_ = msg_size_varint->as_uint32();
|
||||
|
||||
auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[consumed], rx_header_buf_pos_ - 1 - consumed, &consumed);
|
||||
if (!msg_type_varint.has_value()) {
|
||||
// not enough data there yet
|
||||
continue;
|
||||
}
|
||||
if (msg_type_varint->as_uint32() > std::numeric_limits<uint16_t>::max()) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad packet: message type %" PRIu32 " exceeds maximum %u", msg_type_varint->as_uint32(),
|
||||
std::numeric_limits<uint16_t>::max());
|
||||
return APIError::BAD_DATA_PACKET;
|
||||
}
|
||||
rx_header_parsed_type_ = msg_type_varint->as_uint16();
|
||||
rx_header_parsed_type_ = msg_type_varint->as_uint32();
|
||||
rx_header_parsed_ = true;
|
||||
}
|
||||
// header reading done
|
||||
@@ -931,7 +905,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
|
||||
if (rx_buf_len_ < rx_header_parsed_len_) {
|
||||
// more data to read
|
||||
uint16_t to_read = rx_header_parsed_len_ - rx_buf_len_;
|
||||
size_t to_read = rx_header_parsed_len_ - rx_buf_len_;
|
||||
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
@@ -945,8 +919,8 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
HELPER_LOG("Connection closed");
|
||||
return APIError::CONNECTION_CLOSED;
|
||||
}
|
||||
rx_buf_len_ += static_cast<uint16_t>(received);
|
||||
if (static_cast<uint16_t>(received) != to_read) {
|
||||
rx_buf_len_ += received;
|
||||
if ((size_t) received != to_read) {
|
||||
// not all read
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
@@ -964,6 +938,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
rx_header_parsed_ = false;
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
APIError aerr;
|
||||
|
||||
@@ -991,7 +966,7 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
"Bad indicator byte";
|
||||
iov[0].iov_base = (void *) msg;
|
||||
iov[0].iov_len = 19;
|
||||
this->write_raw_(iov, 1);
|
||||
APIFrameHelper::write_raw_(iov, 1);
|
||||
}
|
||||
return aerr;
|
||||
}
|
||||
@@ -1002,66 +977,28 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
buffer->type = rx_header_parsed_type_;
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
|
||||
APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) {
|
||||
if (state_ != State::DATA) {
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||
// Message data starts after padding (frame_header_padding_ = 6)
|
||||
uint16_t payload_len = static_cast<uint16_t>(raw_buffer->size() - frame_header_padding_);
|
||||
std::vector<uint8_t> header;
|
||||
header.reserve(1 + api::ProtoSize::varint(static_cast<uint32_t>(payload_len)) +
|
||||
api::ProtoSize::varint(static_cast<uint32_t>(type)));
|
||||
header.push_back(0x00);
|
||||
ProtoVarInt(payload_len).encode(header);
|
||||
ProtoVarInt(type).encode(header);
|
||||
|
||||
// Calculate varint sizes for header components
|
||||
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len));
|
||||
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type));
|
||||
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
|
||||
|
||||
if (total_header_len > frame_header_padding_) {
|
||||
// Header is too large to fit in the padding
|
||||
return APIError::BAD_ARG;
|
||||
struct iovec iov[2];
|
||||
iov[0].iov_base = &header[0];
|
||||
iov[0].iov_len = header.size();
|
||||
if (payload_len == 0) {
|
||||
return APIFrameHelper::write_raw_(iov, 1);
|
||||
}
|
||||
iov[1].iov_base = const_cast<uint8_t *>(payload);
|
||||
iov[1].iov_len = payload_len;
|
||||
|
||||
// Calculate where to start writing the header
|
||||
// The header starts at the latest possible position to minimize unused padding
|
||||
//
|
||||
// Example 1 (small values): total_header_len = 3, header_offset = 6 - 3 = 3
|
||||
// [0-2] - Unused padding
|
||||
// [3] - 0x00 indicator byte
|
||||
// [4] - Payload size varint (1 byte, for sizes 0-127)
|
||||
// [5] - Message type varint (1 byte, for types 0-127)
|
||||
// [6...] - Actual payload data
|
||||
//
|
||||
// Example 2 (medium values): total_header_len = 4, header_offset = 6 - 4 = 2
|
||||
// [0-1] - Unused padding
|
||||
// [2] - 0x00 indicator byte
|
||||
// [3-4] - Payload size varint (2 bytes, for sizes 128-16383)
|
||||
// [5] - Message type varint (1 byte, for types 0-127)
|
||||
// [6...] - Actual payload data
|
||||
//
|
||||
// Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
|
||||
// [0] - 0x00 indicator byte
|
||||
// [1-3] - Payload size varint (3 bytes, for sizes 16384-2097151)
|
||||
// [4-5] - Message type varint (2 bytes, for types 128-32767)
|
||||
// [6...] - Actual payload data
|
||||
uint8_t *buf_start = raw_buffer->data();
|
||||
uint8_t header_offset = frame_header_padding_ - total_header_len;
|
||||
|
||||
// Write the plaintext header
|
||||
buf_start[header_offset] = 0x00; // indicator
|
||||
|
||||
// Encode size varint directly into buffer
|
||||
ProtoVarInt(payload_len).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
|
||||
|
||||
// Encode type varint directly into buffer
|
||||
ProtoVarInt(type).encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
|
||||
|
||||
struct iovec iov;
|
||||
// Point iov_base to the beginning of our header (skip unused padding)
|
||||
// This ensures we only send the actual header and payload, not the empty padding bytes
|
||||
iov.iov_base = buf_start + header_offset;
|
||||
iov.iov_len = total_header_len + payload_len;
|
||||
|
||||
return write_raw_(&iov, 1);
|
||||
return APIFrameHelper::write_raw_(iov, 2);
|
||||
}
|
||||
|
||||
#endif // USE_API_PLAINTEXT
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -13,18 +12,22 @@
|
||||
|
||||
#include "api_noise_context.h"
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class ProtoWriteBuffer;
|
||||
|
||||
struct ReadPacketBuffer {
|
||||
std::vector<uint8_t> container;
|
||||
uint16_t type;
|
||||
uint16_t data_offset;
|
||||
uint16_t data_len;
|
||||
size_t data_offset;
|
||||
size_t data_len;
|
||||
};
|
||||
|
||||
struct PacketBuffer {
|
||||
const std::vector<uint8_t> container;
|
||||
uint16_t type;
|
||||
uint8_t data_offset;
|
||||
uint8_t data_len;
|
||||
};
|
||||
|
||||
enum class APIError : int {
|
||||
@@ -65,7 +68,29 @@ class APIFrameHelper {
|
||||
virtual APIError init() = 0;
|
||||
virtual APIError loop() = 0;
|
||||
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
|
||||
bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
|
||||
bool can_write_without_blocking() {
|
||||
// First check if we're in the DATA state
|
||||
if (state_ != State::DATA) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Empty buffer can always accept more data
|
||||
if (tx_buf_.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Optimization: Allow writing even with a small buffer backlog to reduce delays in message processing.
|
||||
// This improves throughput for real-time data like sensor readings and prevents high-priority
|
||||
// messages from being unnecessarily delayed by a small queue backlog.
|
||||
// The 256-byte threshold is small enough to not impact memory usage significantly
|
||||
// but large enough to improve overall system responsiveness.
|
||||
if (tx_buf_.size() == 1 && tx_buf_.front().remaining() < 256) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
virtual APIError write_packet(uint16_t type, const uint8_t *data, size_t len) = 0;
|
||||
std::string getpeername() { return socket_->getpeername(); }
|
||||
int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
|
||||
APIError close() {
|
||||
@@ -86,13 +111,6 @@ class APIFrameHelper {
|
||||
}
|
||||
// Give this helper a name for logging
|
||||
void set_log_info(std::string info) { info_ = std::move(info); }
|
||||
virtual APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) = 0;
|
||||
// Get the frame header padding required by this protocol
|
||||
virtual uint8_t frame_header_padding() = 0;
|
||||
// Get the frame footer size required by this protocol
|
||||
virtual uint8_t frame_footer_size() = 0;
|
||||
// Check if socket has data ready to read
|
||||
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
|
||||
|
||||
protected:
|
||||
// Struct for holding parsed frame data
|
||||
@@ -105,7 +123,7 @@ class APIFrameHelper {
|
||||
std::vector<uint8_t> data;
|
||||
uint16_t offset{0}; // Current offset within the buffer (uint16_t to reduce memory usage)
|
||||
|
||||
// Using uint16_t reduces memory usage since ESPHome API messages are limited to UINT16_MAX (65535) bytes
|
||||
// Using uint16_t reduces memory usage since ESPHome API messages are limited to 64KB max
|
||||
uint16_t remaining() const { return static_cast<uint16_t>(data.size()) - offset; }
|
||||
const uint8_t *current_data() const { return data.data() + offset; }
|
||||
};
|
||||
@@ -149,56 +167,34 @@ class APIFrameHelper {
|
||||
APIError try_send_tx_buf_();
|
||||
|
||||
// Helper method to buffer data from IOVs
|
||||
void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len);
|
||||
template<typename StateEnum>
|
||||
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
|
||||
const std::string &info, StateEnum &state, StateEnum failed_state);
|
||||
|
||||
uint8_t frame_header_padding_{0};
|
||||
uint8_t frame_footer_size_{0};
|
||||
|
||||
// Receive buffer for reading frame data
|
||||
std::vector<uint8_t> rx_buf_;
|
||||
uint16_t rx_buf_len_ = 0;
|
||||
|
||||
// Common initialization for both plaintext and noise protocols
|
||||
APIError init_common_();
|
||||
void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, size_t total_write_len);
|
||||
};
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
class APINoiseFrameHelper : public APIFrameHelper {
|
||||
public:
|
||||
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
|
||||
: APIFrameHelper(std::move(socket)), ctx_(std::move(ctx)) {
|
||||
// Noise header structure:
|
||||
// Pos 0: indicator (0x01)
|
||||
// Pos 1-2: encrypted payload size (16-bit big-endian)
|
||||
// Pos 3-6: encrypted type (16-bit) + data_len (16-bit)
|
||||
// Pos 7+: actual payload data
|
||||
frame_header_padding_ = 7;
|
||||
}
|
||||
: APIFrameHelper(std::move(socket)), ctx_(std::move(ctx)) {}
|
||||
~APINoiseFrameHelper() override;
|
||||
APIError init() override;
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
|
||||
// Get the frame header padding required by this protocol
|
||||
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||
// Get the frame footer size required by this protocol
|
||||
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
||||
APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
|
||||
|
||||
protected:
|
||||
APIError state_action_();
|
||||
APIError try_read_frame_(ParsedFrame *frame);
|
||||
APIError write_frame_(const uint8_t *data, uint16_t len);
|
||||
APIError write_frame_(const uint8_t *data, size_t len);
|
||||
APIError init_handshake_();
|
||||
APIError check_handshake_finished_();
|
||||
void send_explicit_handshake_reject_(const std::string &reason);
|
||||
// Fixed-size header buffer for noise protocol:
|
||||
// 1 byte for indicator + 2 bytes for message size (16-bit value, not varint)
|
||||
// Note: Maximum message size is UINT16_MAX (65535), with a limit of 128 bytes during handshake phase
|
||||
// Note: Maximum message size is 65535, with a limit of 128 bytes during handshake phase
|
||||
uint8_t rx_header_buf_[3];
|
||||
uint8_t rx_header_buf_len_ = 0;
|
||||
size_t rx_header_buf_len_ = 0;
|
||||
std::vector<uint8_t> rx_buf_;
|
||||
size_t rx_buf_len_ = 0;
|
||||
|
||||
std::vector<uint8_t> prologue_;
|
||||
|
||||
@@ -213,28 +209,18 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
||||
#ifdef USE_API_PLAINTEXT
|
||||
class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||
public:
|
||||
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : APIFrameHelper(std::move(socket)) {
|
||||
// Plaintext header structure (worst case):
|
||||
// Pos 0: indicator (0x00)
|
||||
// Pos 1-3: payload size varint (up to 3 bytes)
|
||||
// Pos 4-5: message type varint (up to 2 bytes)
|
||||
// Pos 6+: actual payload data
|
||||
frame_header_padding_ = 6;
|
||||
}
|
||||
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : APIFrameHelper(std::move(socket)) {}
|
||||
~APIPlaintextFrameHelper() override = default;
|
||||
APIError init() override;
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
|
||||
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||
// Get the frame footer size required by this protocol
|
||||
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
||||
APIError write_packet(uint16_t type, const uint8_t *payload, size_t len) override;
|
||||
|
||||
protected:
|
||||
APIError try_read_frame_(ParsedFrame *frame);
|
||||
// Fixed-size header buffer for plaintext protocol:
|
||||
// We only need space for the two varints since we validate the indicator byte separately.
|
||||
// To match noise protocol's maximum message size (UINT16_MAX = 65535), we need:
|
||||
// To match noise protocol's maximum message size (65535), we need:
|
||||
// 3 bytes for message size varint (supports up to 2097151) + 2 bytes for message type varint
|
||||
//
|
||||
// While varints could theoretically be up to 10 bytes each for 64-bit values,
|
||||
@@ -243,8 +229,11 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||
uint8_t rx_header_buf_[5]; // 5 bytes for varints (3 for size + 2 for type)
|
||||
uint8_t rx_header_buf_pos_ = 0;
|
||||
bool rx_header_parsed_ = false;
|
||||
uint16_t rx_header_parsed_type_ = 0;
|
||||
uint16_t rx_header_parsed_len_ = 0;
|
||||
uint32_t rx_header_parsed_type_ = 0;
|
||||
uint32_t rx_header_parsed_len_ = 0;
|
||||
|
||||
std::vector<uint8_t> rx_buf_;
|
||||
size_t rx_buf_len_ = 0;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ void APIServer::setup() {
|
||||
}
|
||||
#endif
|
||||
|
||||
this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
|
||||
this->socket_ = socket::socket_ip(SOCK_STREAM, 0);
|
||||
if (this->socket_ == nullptr) {
|
||||
ESP_LOGW(TAG, "Could not create socket");
|
||||
this->mark_failed();
|
||||
@@ -112,20 +112,18 @@ void APIServer::setup() {
|
||||
}
|
||||
|
||||
void APIServer::loop() {
|
||||
// Accept new clients only if the socket has incoming connections
|
||||
if (this->socket_->ready()) {
|
||||
while (true) {
|
||||
struct sockaddr_storage source_addr;
|
||||
socklen_t addr_len = sizeof(source_addr);
|
||||
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
|
||||
if (!sock)
|
||||
break;
|
||||
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
|
||||
// Accept new clients
|
||||
while (true) {
|
||||
struct sockaddr_storage source_addr;
|
||||
socklen_t addr_len = sizeof(source_addr);
|
||||
auto sock = this->socket_->accept((struct sockaddr *) &source_addr, &addr_len);
|
||||
if (!sock)
|
||||
break;
|
||||
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
|
||||
|
||||
auto *conn = new APIConnection(std::move(sock), this);
|
||||
this->clients_.emplace_back(conn);
|
||||
conn->start();
|
||||
}
|
||||
auto *conn = new APIConnection(std::move(sock), this);
|
||||
this->clients_.emplace_back(conn);
|
||||
conn->start();
|
||||
}
|
||||
|
||||
// Process clients and remove disconnected ones in a single pass
|
||||
|
||||
@@ -55,7 +55,6 @@ class ProtoVarInt {
|
||||
return {}; // Incomplete or invalid varint
|
||||
}
|
||||
|
||||
uint16_t as_uint16() const { return this->value_; }
|
||||
uint32_t as_uint32() const { return this->value_; }
|
||||
uint64_t as_uint64() const { return this->value_; }
|
||||
bool as_bool() const { return this->value_; }
|
||||
@@ -84,34 +83,6 @@ class ProtoVarInt {
|
||||
return static_cast<int64_t>(this->value_ >> 1);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Encode the varint value to a pre-allocated buffer without bounds checking.
|
||||
*
|
||||
* @param buffer The pre-allocated buffer to write the encoded varint to
|
||||
* @param len The size of the buffer in bytes
|
||||
*
|
||||
* @note The caller is responsible for ensuring the buffer is large enough
|
||||
* to hold the encoded value. Use ProtoSize::varint() to calculate
|
||||
* the exact size needed before calling this method.
|
||||
* @note No bounds checking is performed for performance reasons.
|
||||
*/
|
||||
void encode_to_buffer_unchecked(uint8_t *buffer, size_t len) {
|
||||
uint64_t val = this->value_;
|
||||
if (val <= 0x7F) {
|
||||
buffer[0] = val;
|
||||
return;
|
||||
}
|
||||
size_t i = 0;
|
||||
while (val && i < len) {
|
||||
uint8_t temp = val & 0x7F;
|
||||
val >>= 7;
|
||||
if (val) {
|
||||
buffer[i++] = temp | 0x80;
|
||||
} else {
|
||||
buffer[i++] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
void encode(std::vector<uint8_t> &out) {
|
||||
uint64_t val = this->value_;
|
||||
if (val <= 0x7F) {
|
||||
|
||||
@@ -14,8 +14,11 @@ namespace esphome {
|
||||
namespace at581x {
|
||||
|
||||
class AT581XComponent : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
#ifdef USE_SWITCH
|
||||
protected:
|
||||
switch_::Switch *rf_power_switch_{nullptr};
|
||||
|
||||
public:
|
||||
void set_rf_power_switch(switch_::Switch *s) {
|
||||
this->rf_power_switch_ = s;
|
||||
s->turn_on();
|
||||
@@ -45,9 +48,6 @@ class AT581XComponent : public Component, public i2c::I2CDevice {
|
||||
bool i2c_read_reg(uint8_t addr, uint8_t &data);
|
||||
|
||||
protected:
|
||||
#ifdef USE_SWITCH
|
||||
switch_::Switch *rf_power_switch_{nullptr};
|
||||
#endif
|
||||
int freq_;
|
||||
int self_check_time_ms_; /*!< Power-on self-test time, range: 0 ~ 65536 ms */
|
||||
int protect_time_ms_; /*!< Protection time, recommended 1000 ms */
|
||||
|
||||
@@ -7,7 +7,7 @@ CODEOWNERS = ["@bazuchan"]
|
||||
ballu_ns = cg.esphome_ns.namespace("ballu")
|
||||
BalluClimate = ballu_ns.class_("BalluClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(BalluClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(BalluClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "bedjet_hub.h"
|
||||
#include "bedjet_child.h"
|
||||
#include "bedjet_const.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -15,21 +15,17 @@ void BinarySensor::publish_state(bool state) {
|
||||
if (!this->publish_dedup_.next(state))
|
||||
return;
|
||||
if (this->filter_list_ == nullptr) {
|
||||
this->send_state_internal(state, false);
|
||||
this->send_state_internal(state);
|
||||
} else {
|
||||
this->filter_list_->input(state, false);
|
||||
this->filter_list_->input(state);
|
||||
}
|
||||
}
|
||||
void BinarySensor::publish_initial_state(bool state) {
|
||||
if (!this->publish_dedup_.next(state))
|
||||
return;
|
||||
if (this->filter_list_ == nullptr) {
|
||||
this->send_state_internal(state, true);
|
||||
} else {
|
||||
this->filter_list_->input(state, true);
|
||||
}
|
||||
this->has_state_ = false;
|
||||
this->publish_state(state);
|
||||
}
|
||||
void BinarySensor::send_state_internal(bool state, bool is_initial) {
|
||||
void BinarySensor::send_state_internal(bool state) {
|
||||
bool is_initial = !this->has_state_;
|
||||
if (is_initial) {
|
||||
ESP_LOGD(TAG, "'%s': Sending initial state %s", this->get_name().c_str(), ONOFF(state));
|
||||
} else {
|
||||
|
||||
@@ -67,7 +67,7 @@ class BinarySensor : public EntityBase, public EntityBase_DeviceClass {
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void send_state_internal(bool state, bool is_initial);
|
||||
void send_state_internal(bool state);
|
||||
|
||||
/// Return whether this binary sensor has outputted a state.
|
||||
virtual bool has_state() const;
|
||||
|
||||
@@ -9,37 +9,37 @@ namespace binary_sensor {
|
||||
|
||||
static const char *const TAG = "sensor.filter";
|
||||
|
||||
void Filter::output(bool value, bool is_initial) {
|
||||
void Filter::output(bool value) {
|
||||
if (!this->dedup_.next(value))
|
||||
return;
|
||||
|
||||
if (this->next_ == nullptr) {
|
||||
this->parent_->send_state_internal(value, is_initial);
|
||||
this->parent_->send_state_internal(value);
|
||||
} else {
|
||||
this->next_->input(value, is_initial);
|
||||
this->next_->input(value);
|
||||
}
|
||||
}
|
||||
void Filter::input(bool value, bool is_initial) {
|
||||
auto b = this->new_value(value, is_initial);
|
||||
void Filter::input(bool value) {
|
||||
auto b = this->new_value(value);
|
||||
if (b.has_value()) {
|
||||
this->output(*b, is_initial);
|
||||
this->output(*b);
|
||||
}
|
||||
}
|
||||
|
||||
optional<bool> DelayedOnOffFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> DelayedOnOffFilter::new_value(bool value) {
|
||||
if (value) {
|
||||
this->set_timeout("ON_OFF", this->on_delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
|
||||
this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); });
|
||||
} else {
|
||||
this->set_timeout("ON_OFF", this->off_delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
|
||||
this->set_timeout("ON_OFF", this->off_delay_.value(), [this]() { this->output(false); });
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> DelayedOnFilter::new_value(bool value) {
|
||||
if (value) {
|
||||
this->set_timeout("ON", this->delay_.value(), [this, is_initial]() { this->output(true, is_initial); });
|
||||
this->set_timeout("ON", this->delay_.value(), [this]() { this->output(true); });
|
||||
return {};
|
||||
} else {
|
||||
this->cancel_timeout("ON");
|
||||
@@ -49,9 +49,9 @@ optional<bool> DelayedOnFilter::new_value(bool value, bool is_initial) {
|
||||
|
||||
float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> DelayedOffFilter::new_value(bool value) {
|
||||
if (!value) {
|
||||
this->set_timeout("OFF", this->delay_.value(), [this, is_initial]() { this->output(false, is_initial); });
|
||||
this->set_timeout("OFF", this->delay_.value(), [this]() { this->output(false); });
|
||||
return {};
|
||||
} else {
|
||||
this->cancel_timeout("OFF");
|
||||
@@ -61,11 +61,11 @@ optional<bool> DelayedOffFilter::new_value(bool value, bool is_initial) {
|
||||
|
||||
float DelayedOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
optional<bool> InvertFilter::new_value(bool value, bool is_initial) { return !value; }
|
||||
optional<bool> InvertFilter::new_value(bool value) { return !value; }
|
||||
|
||||
AutorepeatFilter::AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings) : timings_(std::move(timings)) {}
|
||||
|
||||
optional<bool> AutorepeatFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> AutorepeatFilter::new_value(bool value) {
|
||||
if (value) {
|
||||
// Ignore if already running
|
||||
if (this->active_timing_ != 0)
|
||||
@@ -101,7 +101,7 @@ void AutorepeatFilter::next_timing_() {
|
||||
|
||||
void AutorepeatFilter::next_value_(bool val) {
|
||||
const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2];
|
||||
this->output(val, false); // This is at least the second one so not initial
|
||||
this->output(val);
|
||||
this->set_timeout("ON_OFF", val ? timing.time_on : timing.time_off, [this, val]() { this->next_value_(!val); });
|
||||
}
|
||||
|
||||
@@ -109,18 +109,18 @@ float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARD
|
||||
|
||||
LambdaFilter::LambdaFilter(std::function<optional<bool>(bool)> f) : f_(std::move(f)) {}
|
||||
|
||||
optional<bool> LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); }
|
||||
optional<bool> LambdaFilter::new_value(bool value) { return this->f_(value); }
|
||||
|
||||
optional<bool> SettleFilter::new_value(bool value, bool is_initial) {
|
||||
optional<bool> SettleFilter::new_value(bool value) {
|
||||
if (!this->steady_) {
|
||||
this->set_timeout("SETTLE", this->delay_.value(), [this, value, is_initial]() {
|
||||
this->set_timeout("SETTLE", this->delay_.value(), [this, value]() {
|
||||
this->steady_ = true;
|
||||
this->output(value, is_initial);
|
||||
this->output(value);
|
||||
});
|
||||
return {};
|
||||
} else {
|
||||
this->steady_ = false;
|
||||
this->output(value, is_initial);
|
||||
this->output(value);
|
||||
this->set_timeout("SETTLE", this->delay_.value(), [this]() { this->steady_ = true; });
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@ class BinarySensor;
|
||||
|
||||
class Filter {
|
||||
public:
|
||||
virtual optional<bool> new_value(bool value, bool is_initial) = 0;
|
||||
virtual optional<bool> new_value(bool value) = 0;
|
||||
|
||||
void input(bool value, bool is_initial);
|
||||
void input(bool value);
|
||||
|
||||
void output(bool value, bool is_initial);
|
||||
void output(bool value);
|
||||
|
||||
protected:
|
||||
friend BinarySensor;
|
||||
@@ -30,7 +30,7 @@ class Filter {
|
||||
|
||||
class DelayedOnOffFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -44,7 +44,7 @@ class DelayedOnOffFilter : public Filter, public Component {
|
||||
|
||||
class DelayedOnFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -56,7 +56,7 @@ class DelayedOnFilter : public Filter, public Component {
|
||||
|
||||
class DelayedOffFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -68,7 +68,7 @@ class DelayedOffFilter : public Filter, public Component {
|
||||
|
||||
class InvertFilter : public Filter {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
};
|
||||
|
||||
struct AutorepeatFilterTiming {
|
||||
@@ -86,7 +86,7 @@ class AutorepeatFilter : public Filter, public Component {
|
||||
public:
|
||||
explicit AutorepeatFilter(std::vector<AutorepeatFilterTiming> timings);
|
||||
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
@@ -102,7 +102,7 @@ class LambdaFilter : public Filter {
|
||||
public:
|
||||
explicit LambdaFilter(std::function<optional<bool>(bool)> f);
|
||||
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
protected:
|
||||
std::function<optional<bool>(bool)> f_;
|
||||
@@ -110,7 +110,7 @@ class LambdaFilter : public Filter {
|
||||
|
||||
class SettleFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value, bool is_initial) override;
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_LINE_FREQUENCY,
|
||||
CONF_POWER,
|
||||
CONF_RESET,
|
||||
CONF_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
@@ -28,6 +27,7 @@ from esphome.const import (
|
||||
CONF_CURRENT_REFERENCE = "current_reference"
|
||||
CONF_ENERGY_REFERENCE = "energy_reference"
|
||||
CONF_POWER_REFERENCE = "power_reference"
|
||||
CONF_RESET = "reset"
|
||||
CONF_VOLTAGE_REFERENCE = "voltage_reference"
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/macros.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -178,7 +177,7 @@ void BluetoothProxy::loop() {
|
||||
// Flush any pending BLE advertisements that have been accumulated but not yet sent
|
||||
if (this->raw_advertisements_) {
|
||||
static uint32_t last_flush_time = 0;
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
uint32_t now = millis();
|
||||
|
||||
// Flush accumulated advertisements every 100ms
|
||||
if (now - last_flush_time >= 100) {
|
||||
|
||||
@@ -16,7 +16,7 @@ CODEOWNERS = ["@neffs", "@kbx81"]
|
||||
|
||||
DOMAIN = "bme68x_bsec2"
|
||||
|
||||
BSEC2_LIBRARY_VERSION = "1.10.2610"
|
||||
BSEC2_LIBRARY_VERSION = "v1.8.2610"
|
||||
|
||||
CONF_ALGORITHM_OUTPUT = "algorithm_output"
|
||||
CONF_BME68X_BSEC2_ID = "bme68x_bsec2_id"
|
||||
@@ -145,6 +145,7 @@ CONFIG_SCHEMA_BASE = (
|
||||
): cv.positive_time_period_minutes,
|
||||
},
|
||||
)
|
||||
.add_extra(cv.only_with_arduino)
|
||||
.add_extra(validate_bme68x)
|
||||
.add_extra(download_bme68x_blob)
|
||||
)
|
||||
@@ -178,13 +179,11 @@ async def to_code_base(config):
|
||||
bsec2_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
cg.add(var.set_bsec2_configuration(bsec2_arr, len(rhs)))
|
||||
|
||||
# Although this component does not use SPI, the BSEC2 Arduino library requires the SPI library
|
||||
if core.CORE.using_arduino:
|
||||
cg.add_library("SPI", None)
|
||||
# Although this component does not use SPI, the BSEC2 library requires the SPI library
|
||||
cg.add_library("SPI", None)
|
||||
cg.add_library(
|
||||
"BME68x Sensor library",
|
||||
"1.3.40408",
|
||||
"https://github.com/boschsensortec/Bosch-BME68x-Library",
|
||||
"1.1.40407",
|
||||
)
|
||||
cg.add_library(
|
||||
"BSEC2 Software Library",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ def climate_ir_schema(
|
||||
)
|
||||
|
||||
|
||||
def climate_ir_with_receiver_schema(
|
||||
def climare_ir_with_receiver_schema(
|
||||
class_: MockObjClass,
|
||||
) -> cv.Schema:
|
||||
return climate_ir_schema(class_).extend(
|
||||
@@ -59,7 +59,7 @@ def deprecated_schema_constant(config):
|
||||
type = str(id.type).split("::", maxsplit=1)[0]
|
||||
_LOGGER.warning(
|
||||
"Using `climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA` is deprecated and will be removed in ESPHome 2025.11.0. "
|
||||
"Please use `climate_ir.climate_ir_with_receiver_schema(...)` instead. "
|
||||
"Please use `climate_ir.climare_ir_with_receiver_schema(...)` instead. "
|
||||
"If you are seeing this, report an issue to the external_component author and ask them to update it. "
|
||||
"https://developers.esphome.io/blog/2025/05/14/_schema-deprecations/. "
|
||||
"Component using this schema: %s",
|
||||
@@ -68,7 +68,7 @@ def deprecated_schema_constant(config):
|
||||
return config
|
||||
|
||||
|
||||
CLIMATE_IR_WITH_RECEIVER_SCHEMA = climate_ir_with_receiver_schema(ClimateIR)
|
||||
CLIMATE_IR_WITH_RECEIVER_SCHEMA = climare_ir_with_receiver_schema(ClimateIR)
|
||||
CLIMATE_IR_WITH_RECEIVER_SCHEMA.add_extra(deprecated_schema_constant)
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ CONF_BIT_HIGH = "bit_high"
|
||||
CONF_BIT_ONE_LOW = "bit_one_low"
|
||||
CONF_BIT_ZERO_LOW = "bit_zero_low"
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(LgIrClimate).extend(
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(LgIrClimate).extend(
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_HEADER_HIGH, default="8000us"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
"""CM1106 component for ESPHome."""
|
||||
@@ -1,112 +0,0 @@
|
||||
#include "cm1106.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace cm1106 {
|
||||
|
||||
static const char *const TAG = "cm1106";
|
||||
static const uint8_t C_M1106_CMD_GET_CO2[4] = {0x11, 0x01, 0x01, 0xED};
|
||||
static const uint8_t C_M1106_CMD_SET_CO2_CALIB[6] = {0x11, 0x03, 0x03, 0x00, 0x00, 0x00};
|
||||
static const uint8_t C_M1106_CMD_SET_CO2_CALIB_RESPONSE[4] = {0x16, 0x01, 0x03, 0xE6};
|
||||
|
||||
uint8_t cm1106_checksum(const uint8_t *response, size_t len) {
|
||||
uint8_t crc = 0;
|
||||
for (int i = 0; i < len - 1; i++) {
|
||||
crc -= response[i];
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void CM1106Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up CM1106...");
|
||||
uint8_t response[8] = {0};
|
||||
if (!this->cm1106_write_command_(C_M1106_CMD_GET_CO2, sizeof(C_M1106_CMD_GET_CO2), response, sizeof(response))) {
|
||||
ESP_LOGE(TAG, "Communication with CM1106 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void CM1106Component::update() {
|
||||
uint8_t response[8] = {0};
|
||||
if (!this->cm1106_write_command_(C_M1106_CMD_GET_CO2, sizeof(C_M1106_CMD_GET_CO2), response, sizeof(response))) {
|
||||
ESP_LOGW(TAG, "Reading data from CM1106 failed!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
if (response[0] != 0x16 || response[1] != 0x05 || response[2] != 0x01) {
|
||||
ESP_LOGW(TAG, "Got wrong UART response from CM1106: %02X %02X %02X %02X...", response[0], response[1], response[2],
|
||||
response[3]);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t checksum = cm1106_checksum(response, sizeof(response));
|
||||
if (response[7] != checksum) {
|
||||
ESP_LOGW(TAG, "CM1106 Checksum doesn't match: 0x%02X!=0x%02X", response[7], checksum);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
|
||||
uint16_t ppm = response[3] << 8 | response[4];
|
||||
ESP_LOGD(TAG, "CM1106 Received CO₂=%uppm DF3=%02X DF4=%02X", ppm, response[5], response[6]);
|
||||
if (this->co2_sensor_ != nullptr)
|
||||
this->co2_sensor_->publish_state(ppm);
|
||||
}
|
||||
|
||||
void CM1106Component::calibrate_zero(uint16_t ppm) {
|
||||
uint8_t cmd[6];
|
||||
memcpy(cmd, C_M1106_CMD_SET_CO2_CALIB, sizeof(cmd));
|
||||
cmd[3] = ppm >> 8;
|
||||
cmd[4] = ppm & 0xFF;
|
||||
uint8_t response[4] = {0};
|
||||
|
||||
if (!this->cm1106_write_command_(cmd, sizeof(cmd), response, sizeof(response))) {
|
||||
ESP_LOGW(TAG, "Reading data from CM1106 failed!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
// check if correct response received
|
||||
if (memcmp(response, C_M1106_CMD_SET_CO2_CALIB_RESPONSE, sizeof(response)) != 0) {
|
||||
ESP_LOGW(TAG, "Got wrong UART response from CM1106: %02X %02X %02X %02X", response[0], response[1], response[2],
|
||||
response[3]);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
ESP_LOGD(TAG, "CM1106 Successfully calibrated sensor to %uppm", ppm);
|
||||
}
|
||||
|
||||
bool CM1106Component::cm1106_write_command_(const uint8_t *command, size_t command_len, uint8_t *response,
|
||||
size_t response_len) {
|
||||
// Empty RX Buffer
|
||||
while (this->available())
|
||||
this->read();
|
||||
this->write_array(command, command_len - 1);
|
||||
this->write_byte(cm1106_checksum(command, command_len));
|
||||
this->flush();
|
||||
|
||||
if (response == nullptr)
|
||||
return true;
|
||||
|
||||
return this->read_array(response, response_len);
|
||||
}
|
||||
|
||||
void CM1106Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "CM1106:");
|
||||
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
|
||||
this->check_uart_settings(9600);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with CM1106 failed!");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cm1106
|
||||
} // namespace esphome
|
||||
@@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cm1106 {
|
||||
|
||||
class CM1106Component : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
|
||||
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
|
||||
void calibrate_zero(uint16_t ppm);
|
||||
|
||||
void set_co2_sensor(sensor::Sensor *co2_sensor) { this->co2_sensor_ = co2_sensor; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *co2_sensor_{nullptr};
|
||||
|
||||
bool cm1106_write_command_(const uint8_t *command, size_t command_len, uint8_t *response, size_t response_len);
|
||||
};
|
||||
|
||||
template<typename... Ts> class CM1106CalibrateZeroAction : public Action<Ts...> {
|
||||
public:
|
||||
CM1106CalibrateZeroAction(CM1106Component *cm1106) : cm1106_(cm1106) {}
|
||||
|
||||
void play(Ts... x) override { this->cm1106_->calibrate_zero(400); }
|
||||
|
||||
protected:
|
||||
CM1106Component *cm1106_;
|
||||
};
|
||||
|
||||
} // namespace cm1106
|
||||
} // namespace esphome
|
||||
@@ -1,72 +0,0 @@
|
||||
"""CM1106 Sensor component for ESPHome."""
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
from esphome.components import sensor, uart
|
||||
from esphome.const import (
|
||||
CONF_CO2,
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_CARBON_DIOXIDE,
|
||||
ICON_MOLECULE_CO2,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
CODEOWNERS = ["@andrewjswan"]
|
||||
|
||||
cm1106_ns = cg.esphome_ns.namespace("cm1106")
|
||||
CM1106Component = cm1106_ns.class_(
|
||||
"CM1106Component", cg.PollingComponent, uart.UARTDevice
|
||||
)
|
||||
CM1106CalibrateZeroAction = cm1106_ns.class_(
|
||||
"CM1106CalibrateZeroAction",
|
||||
automation.Action,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(CM1106Component),
|
||||
cv.Optional(CONF_CO2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||
icon=ICON_MOLECULE_CO2,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
},
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config) -> None:
|
||||
"""Code generation entry point."""
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
if co2_config := config.get(CONF_CO2):
|
||||
sens = await sensor.new_sensor(co2_config)
|
||||
cg.add(var.set_co2_sensor(sens))
|
||||
|
||||
|
||||
CALIBRATION_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(CM1106Component),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"cm1106.calibrate_zero",
|
||||
CM1106CalibrateZeroAction,
|
||||
CALIBRATION_ACTION_SCHEMA,
|
||||
)
|
||||
async def cm1106_calibration_to_code(config, action_id, template_arg, args) -> None:
|
||||
"""Service code generation entry point."""
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
@@ -7,7 +7,7 @@ CODEOWNERS = ["@glmnet"]
|
||||
coolix_ns = cg.esphome_ns.namespace("coolix")
|
||||
CoolixClimate = coolix_ns.class_("CoolixClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(CoolixClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(CoolixClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "cse7766.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace cse7766 {
|
||||
@@ -8,7 +7,7 @@ namespace cse7766 {
|
||||
static const char *const TAG = "cse7766";
|
||||
|
||||
void CSE7766Component::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_transmission_ >= 500) {
|
||||
// last transmission too long ago. Reset RX index.
|
||||
this->raw_data_index_ = 0;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "current_based_cover.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include <cfloat>
|
||||
|
||||
namespace esphome {
|
||||
@@ -61,7 +60,7 @@ void CurrentBasedCover::loop() {
|
||||
if (this->current_operation == COVER_OPERATION_IDLE)
|
||||
return;
|
||||
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
|
||||
if (this->current_operation == COVER_OPERATION_OPENING) {
|
||||
if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction
|
||||
|
||||
@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
||||
daikin_ns = cg.esphome_ns.namespace("daikin")
|
||||
DaikinClimate = daikin_ns.class_("DaikinClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
||||
daikin_arc_ns = cg.esphome_ns.namespace("daikin_arc")
|
||||
DaikinArcClimate = daikin_arc_ns.class_("DaikinArcClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinArcClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinArcClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -9,7 +9,7 @@ daikin_brc_ns = cg.esphome_ns.namespace("daikin_brc")
|
||||
DaikinBrcClimate = daikin_brc_ns.class_("DaikinBrcClimate", climate_ir.ClimateIR)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinBrcClimate).extend(
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinBrcClimate).extend(
|
||||
{
|
||||
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "daly_bms.h"
|
||||
#include <vector>
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace daly_bms {
|
||||
@@ -33,7 +32,7 @@ void DalyBmsComponent::update() {
|
||||
}
|
||||
|
||||
void DalyBmsComponent::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
if (this->receiving_ && (now - this->last_transmission_ >= 200)) {
|
||||
// last transmission too long ago. Reset RX index.
|
||||
ESP_LOGW(TAG, "Last transmission too long ago. Reset RX index.");
|
||||
|
||||
@@ -2,6 +2,7 @@ import base64
|
||||
from pathlib import Path
|
||||
import re
|
||||
import secrets
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
from ruamel.yaml import YAML
|
||||
@@ -83,7 +84,7 @@ async def to_code(config):
|
||||
def import_config(
|
||||
path: str,
|
||||
name: str,
|
||||
friendly_name: str | None,
|
||||
friendly_name: Optional[str],
|
||||
project_name: str,
|
||||
import_url: str,
|
||||
network: str = CONF_WIFI,
|
||||
|
||||
@@ -70,7 +70,7 @@ void DebugComponent::loop() {
|
||||
#ifdef USE_SENSOR
|
||||
// calculate loop time - from last call to this one
|
||||
if (this->loop_time_sensor_ != nullptr) {
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
uint32_t now = millis();
|
||||
uint32_t loop_time = now - this->last_loop_timetag_;
|
||||
this->max_loop_time_ = std::max(this->max_loop_time_, loop_time);
|
||||
this->last_loop_timetag_ = now;
|
||||
|
||||
@@ -34,15 +34,13 @@ class DebugComponent : public PollingComponent {
|
||||
#endif
|
||||
void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; }
|
||||
#ifdef USE_ESP32
|
||||
void on_shutdown() override;
|
||||
void set_psram_sensor(sensor::Sensor *psram_sensor) { this->psram_sensor_ = psram_sensor; }
|
||||
#endif // USE_ESP32
|
||||
void set_cpu_frequency_sensor(sensor::Sensor *cpu_frequency_sensor) {
|
||||
this->cpu_frequency_sensor_ = cpu_frequency_sensor;
|
||||
}
|
||||
#endif // USE_SENSOR
|
||||
#ifdef USE_ESP32
|
||||
void on_shutdown() override;
|
||||
#endif // USE_ESP32
|
||||
protected:
|
||||
uint32_t free_heap_{};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
||||
delonghi_ns = cg.esphome_ns.namespace("delonghi")
|
||||
DelonghiClimate = delonghi_ns.class_("DelonghiClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DelonghiClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DelonghiClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -7,7 +7,7 @@ AUTO_LOAD = ["climate_ir"]
|
||||
emmeti_ns = cg.esphome_ns.namespace("emmeti")
|
||||
EmmetiClimate = emmeti_ns.class_("EmmetiClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(EmmetiClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(EmmetiClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "endstop_cover.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace endstop {
|
||||
@@ -66,7 +65,7 @@ void EndstopCover::loop() {
|
||||
if (this->current_operation == COVER_OPERATION_IDLE)
|
||||
return;
|
||||
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
|
||||
if (this->current_operation == COVER_OPERATION_OPENING && this->is_open_()) {
|
||||
float dur = (now - this->start_dir_time_) / 1e3f;
|
||||
|
||||
@@ -3,6 +3,7 @@ import itertools
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
|
||||
from esphome import git
|
||||
import esphome.codegen as cg
|
||||
@@ -57,10 +58,8 @@ from .const import ( # noqa
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
VARIANT_FRIENDLY,
|
||||
@@ -89,10 +88,8 @@ CPU_FREQUENCIES = {
|
||||
VARIANT_ESP32S3: get_cpu_frequencies(80, 160, 240),
|
||||
VARIANT_ESP32C2: get_cpu_frequencies(80, 120),
|
||||
VARIANT_ESP32C3: get_cpu_frequencies(80, 160),
|
||||
VARIANT_ESP32C5: get_cpu_frequencies(80, 160, 240),
|
||||
VARIANT_ESP32C6: get_cpu_frequencies(80, 120, 160),
|
||||
VARIANT_ESP32H2: get_cpu_frequencies(16, 32, 48, 64, 96),
|
||||
VARIANT_ESP32P4: get_cpu_frequencies(40, 360, 400),
|
||||
}
|
||||
|
||||
# Make sure not missed here if a new variant added.
|
||||
@@ -192,7 +189,7 @@ class RawSdkconfigValue:
|
||||
value: str
|
||||
|
||||
|
||||
SdkconfigValueType = bool | int | HexInt | str | RawSdkconfigValue
|
||||
SdkconfigValueType = Union[bool, int, HexInt, str, RawSdkconfigValue]
|
||||
|
||||
|
||||
def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
|
||||
@@ -209,8 +206,8 @@ def add_idf_component(
|
||||
ref: str = None,
|
||||
path: str = None,
|
||||
refresh: TimePeriod = None,
|
||||
components: list[str] | None = None,
|
||||
submodules: list[str] | None = None,
|
||||
components: Optional[list[str]] = None,
|
||||
submodules: Optional[list[str]] = None,
|
||||
):
|
||||
"""Add an esp-idf component to the project."""
|
||||
if not CORE.using_esp_idf:
|
||||
@@ -299,11 +296,11 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0)
|
||||
# The default/recommended esp-idf framework version
|
||||
# - https://github.com/espressif/esp-idf/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
|
||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 3, 2)
|
||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 1, 6)
|
||||
# The platformio/espressif32 version to use for esp-idf frameworks
|
||||
# - https://github.com/platformio/platform-espressif32/releases
|
||||
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
|
||||
ESP_IDF_PLATFORM_VERSION = cv.Version(53, 3, 13)
|
||||
ESP_IDF_PLATFORM_VERSION = cv.Version(51, 3, 7)
|
||||
|
||||
# List based on https://registry.platformio.org/tools/platformio/framework-espidf/versions
|
||||
SUPPORTED_PLATFORMIO_ESP_IDF_5X = [
|
||||
@@ -372,8 +369,8 @@ def _arduino_check_versions(value):
|
||||
def _esp_idf_check_versions(value):
|
||||
value = value.copy()
|
||||
lookups = {
|
||||
"dev": (cv.Version(5, 3, 2), "https://github.com/espressif/esp-idf.git"),
|
||||
"latest": (cv.Version(5, 3, 2), None),
|
||||
"dev": (cv.Version(5, 1, 6), "https://github.com/espressif/esp-idf.git"),
|
||||
"latest": (cv.Version(5, 1, 6), None),
|
||||
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,8 @@ from .const import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
@@ -1594,10 +1592,6 @@ BOARDS = {
|
||||
"name": "Ai-Thinker ESP-C3-M1-I-Kit",
|
||||
"variant": VARIANT_ESP32C3,
|
||||
},
|
||||
"esp32-c5-devkitc-1": {
|
||||
"name": "Espressif ESP32-C5-DevKitC-1",
|
||||
"variant": VARIANT_ESP32C5,
|
||||
},
|
||||
"esp32-c6-devkitc-1": {
|
||||
"name": "Espressif ESP32-C6-DevKitC-1",
|
||||
"variant": VARIANT_ESP32C6,
|
||||
@@ -1638,14 +1632,6 @@ BOARDS = {
|
||||
"name": "Espressif ESP32-H2-DevKit",
|
||||
"variant": VARIANT_ESP32H2,
|
||||
},
|
||||
"esp32-p4": {
|
||||
"name": "Espressif ESP32-P4 generic",
|
||||
"variant": VARIANT_ESP32P4,
|
||||
},
|
||||
"esp32-p4-evboard": {
|
||||
"name": "Espressif ESP32-P4 Function EV Board",
|
||||
"variant": VARIANT_ESP32P4,
|
||||
},
|
||||
"esp32-pico-devkitm-2": {
|
||||
"name": "Espressif ESP32-PICO-DevKitM-2",
|
||||
"variant": VARIANT_ESP32,
|
||||
|
||||
@@ -17,20 +17,16 @@ VARIANT_ESP32S2 = "ESP32S2"
|
||||
VARIANT_ESP32S3 = "ESP32S3"
|
||||
VARIANT_ESP32C2 = "ESP32C2"
|
||||
VARIANT_ESP32C3 = "ESP32C3"
|
||||
VARIANT_ESP32C5 = "ESP32C5"
|
||||
VARIANT_ESP32C6 = "ESP32C6"
|
||||
VARIANT_ESP32H2 = "ESP32H2"
|
||||
VARIANT_ESP32P4 = "ESP32P4"
|
||||
VARIANTS = [
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32P4,
|
||||
]
|
||||
|
||||
VARIANT_FRIENDLY = {
|
||||
@@ -39,10 +35,8 @@ VARIANT_FRIENDLY = {
|
||||
VARIANT_ESP32S3: "ESP32-S3",
|
||||
VARIANT_ESP32C2: "ESP32-C2",
|
||||
VARIANT_ESP32C3: "ESP32-C3",
|
||||
VARIANT_ESP32C5: "ESP32-C5",
|
||||
VARIANT_ESP32C6: "ESP32-C6",
|
||||
VARIANT_ESP32H2: "ESP32-H2",
|
||||
VARIANT_ESP32P4: "ESP32-P4",
|
||||
}
|
||||
|
||||
esp32_ns = cg.esphome_ns.namespace("esp32")
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
#ifdef USE_ARDUINO
|
||||
#include <Esp.h>
|
||||
#else
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
|
||||
#include <esp_clk_tree.h>
|
||||
#endif
|
||||
|
||||
void setup();
|
||||
void loop();
|
||||
#endif
|
||||
@@ -64,13 +63,7 @@ uint32_t arch_get_cpu_cycle_count() { return cpu_hal_get_cycle_count(); }
|
||||
uint32_t arch_get_cpu_freq_hz() {
|
||||
uint32_t freq = 0;
|
||||
#ifdef USE_ESP_IDF
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
|
||||
esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_CPU, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &freq);
|
||||
#else
|
||||
rtc_cpu_freq_config_t config;
|
||||
rtc_clk_cpu_freq_get_config(&config);
|
||||
freq = config.freq_mhz * 1000000U;
|
||||
#endif
|
||||
#elif defined(USE_ARDUINO)
|
||||
freq = ESP.getCpuFreqMHz() * 1000000;
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import Any, Callable
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
@@ -27,10 +26,8 @@ from .const import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
esp32_ns,
|
||||
@@ -38,10 +35,8 @@ from .const import (
|
||||
from .gpio_esp32 import esp32_validate_gpio_pin, esp32_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_c5 import esp32_c5_validate_gpio_pin, esp32_c5_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_p4 import esp32_p4_validate_gpio_pin, esp32_p4_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
|
||||
|
||||
@@ -102,10 +97,6 @@ _esp32_validations = {
|
||||
pin_validation=esp32_c3_validate_gpio_pin,
|
||||
usage_validation=esp32_c3_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32C5: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_c5_validate_gpio_pin,
|
||||
usage_validation=esp32_c5_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32C6: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_c6_validate_gpio_pin,
|
||||
usage_validation=esp32_c6_validate_supports,
|
||||
@@ -114,10 +105,6 @@ _esp32_validations = {
|
||||
pin_validation=esp32_h2_validate_gpio_pin,
|
||||
usage_validation=esp32_h2_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32P4: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_p4_validate_gpio_pin,
|
||||
usage_validation=esp32_p4_validate_supports,
|
||||
),
|
||||
VARIANT_ESP32S2: ESP32ValidationFunctions(
|
||||
pin_validation=esp32_s2_validate_gpio_pin,
|
||||
usage_validation=esp32_s2_validate_supports,
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import logging
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
_ESP32C5_SPI_PSRAM_PINS = {
|
||||
16: "SPICS0",
|
||||
17: "SPIQ",
|
||||
18: "SPIWP",
|
||||
19: "VDD_SPI",
|
||||
20: "SPIHD",
|
||||
21: "SPICLK",
|
||||
22: "SPID",
|
||||
}
|
||||
|
||||
_ESP32C5_STRAPPING_PINS = {2, 7, 27, 28}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_c5_validate_gpio_pin(value):
|
||||
if value < 0 or value > 28:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-28)")
|
||||
if value in _ESP32C5_SPI_PSRAM_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32-C5s and is already used by the SPI/PSRAM interface (function: {_ESP32C5_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def esp32_c5_validate_supports(value):
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 28:
|
||||
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-28)")
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
|
||||
check_strapping_pin(value, _ESP32C5_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
@@ -1,43 +0,0 @@
|
||||
import logging
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
|
||||
|
||||
_ESP32P4_USB_JTAG_PINS = {24, 25}
|
||||
|
||||
_ESP32P4_STRAPPING_PINS = {34, 35, 36, 37, 38}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def esp32_p4_validate_gpio_pin(value):
|
||||
if value < 0 or value > 54:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-54)")
|
||||
if value in _ESP32P4_STRAPPING_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is a Strapping PIN and should be avoided.\n"
|
||||
"Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
|
||||
"See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
|
||||
value,
|
||||
)
|
||||
if value in _ESP32P4_USB_JTAG_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is reserved for the USB-Serial-JTAG interface.\n"
|
||||
"To use this pin as GPIO, USB-Serial-JTAG will be disabled.",
|
||||
value,
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def esp32_p4_validate_supports(value):
|
||||
num = value[CONF_NUMBER]
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
|
||||
if num < 0 or num > 54:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-54)")
|
||||
if is_input:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
return value
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <cstring>
|
||||
#include "ble_uuid.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
@@ -144,7 +143,7 @@ void BLEAdvertising::loop() {
|
||||
if (this->raw_advertisements_callbacks_.empty()) {
|
||||
return;
|
||||
}
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_advertisement_time_ > this->advertising_cycle_time_) {
|
||||
this->stop();
|
||||
this->current_adv_index_ += 1;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, MutableMapping
|
||||
from collections.abc import MutableMapping
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import Any, Callable
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
|
||||
@@ -296,7 +296,7 @@ async def to_code(config):
|
||||
add_idf_component(
|
||||
name="esp32-camera",
|
||||
repo="https://github.com/espressif/esp32-camera.git",
|
||||
ref="v2.0.15",
|
||||
ref="v2.0.9",
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_STREAM_START, []):
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "esp32_camera.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#include <freertos/task.h>
|
||||
|
||||
@@ -55,7 +54,11 @@ void ESP32Camera::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " HREF Pin: %d", conf.pin_href);
|
||||
ESP_LOGCONFIG(TAG, " Pixel Clock Pin: %d", conf.pin_pclk);
|
||||
ESP_LOGCONFIG(TAG, " External Clock: Pin:%d Frequency:%u", conf.pin_xclk, conf.xclk_freq_hz);
|
||||
#ifdef USE_ESP_IDF // Temporary until the espressif/esp32-camera library is updated
|
||||
ESP_LOGCONFIG(TAG, " I2C Pins: SDA:%d SCL:%d", conf.pin_sscb_sda, conf.pin_sscb_scl);
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, " I2C Pins: SDA:%d SCL:%d", conf.pin_sccb_sda, conf.pin_sccb_scl);
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG, " Reset Pin: %d", conf.pin_reset);
|
||||
switch (this->config_.frame_size) {
|
||||
case FRAMESIZE_QQVGA:
|
||||
@@ -159,7 +162,7 @@ void ESP32Camera::loop() {
|
||||
}
|
||||
|
||||
// request idle image every idle_update_interval
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) {
|
||||
this->last_idle_request_ = now;
|
||||
this->request_image(IDLE);
|
||||
@@ -235,8 +238,13 @@ void ESP32Camera::set_external_clock(uint8_t pin, uint32_t frequency) {
|
||||
this->config_.xclk_freq_hz = frequency;
|
||||
}
|
||||
void ESP32Camera::set_i2c_pins(uint8_t sda, uint8_t scl) {
|
||||
#ifdef USE_ESP_IDF // Temporary until the espressif/esp32-camera library is updated
|
||||
this->config_.pin_sscb_sda = sda;
|
||||
this->config_.pin_sscb_scl = scl;
|
||||
#else
|
||||
this->config_.pin_sccb_sda = sda;
|
||||
this->config_.pin_sccb_scl = scl;
|
||||
#endif
|
||||
}
|
||||
void ESP32Camera::set_reset_pin(uint8_t pin) { this->config_.pin_reset = pin; }
|
||||
void ESP32Camera::set_power_down_pin(uint8_t pin) { this->config_.pin_pwdn = pin; }
|
||||
|
||||
@@ -92,7 +92,7 @@ void ESP32ImprovComponent::loop() {
|
||||
|
||||
if (!this->incoming_data_.empty())
|
||||
this->process_incoming_data_();
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
uint32_t now = millis();
|
||||
|
||||
switch (this->state_) {
|
||||
case improv::STATE_STOPPED:
|
||||
|
||||
@@ -288,7 +288,7 @@ uint32_t ESP32TouchComponent::component_touch_pad_read(touch_pad_t tp) {
|
||||
}
|
||||
|
||||
void ESP32TouchComponent::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250;
|
||||
for (auto *child : this->children_) {
|
||||
child->value_ = this->component_touch_pad_read(child->get_touch_pad());
|
||||
|
||||
@@ -26,7 +26,7 @@ void ESPHomeOTAComponent::setup() {
|
||||
ota::register_ota_platform(this);
|
||||
#endif
|
||||
|
||||
server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
|
||||
server_ = socket::socket_ip(SOCK_STREAM, 0);
|
||||
if (server_ == nullptr) {
|
||||
ESP_LOGW(TAG, "Could not create socket");
|
||||
this->mark_failed();
|
||||
@@ -100,12 +100,9 @@ void ESPHomeOTAComponent::handle_() {
|
||||
#endif
|
||||
|
||||
if (client_ == nullptr) {
|
||||
// Check if the server socket is ready before accepting
|
||||
if (this->server_->ready()) {
|
||||
struct sockaddr_storage source_addr;
|
||||
socklen_t addr_len = sizeof(source_addr);
|
||||
client_ = server_->accept((struct sockaddr *) &source_addr, &addr_len);
|
||||
}
|
||||
struct sockaddr_storage source_addr;
|
||||
socklen_t addr_len = sizeof(source_addr);
|
||||
client_ = server_->accept((struct sockaddr *) &source_addr, &addr_len);
|
||||
}
|
||||
if (client_ == nullptr)
|
||||
return;
|
||||
@@ -114,8 +111,6 @@ void ESPHomeOTAComponent::handle_() {
|
||||
int err = client_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket could not enable TCP nodelay, errno %d", errno);
|
||||
client_->close();
|
||||
client_ = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ void EthernetComponent::setup() {
|
||||
}
|
||||
|
||||
void EthernetComponent::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
|
||||
switch (this->state_) {
|
||||
case EthernetComponentState::STOPPED:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "feedback_cover.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace feedback {
|
||||
@@ -221,7 +220,7 @@ void FeedbackCover::set_open_obstacle_sensor(binary_sensor::BinarySensor *open_o
|
||||
void FeedbackCover::loop() {
|
||||
if (this->current_operation == COVER_OPERATION_IDLE)
|
||||
return;
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
|
||||
// Recompute position every loop cycle
|
||||
this->recompute_position_();
|
||||
|
||||
@@ -8,7 +8,7 @@ FujitsuGeneralClimate = fujitsu_general_ns.class_(
|
||||
"FujitsuGeneralClimate", climate_ir.ClimateIR
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(FujitsuGeneralClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(FujitsuGeneralClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
*/
|
||||
#include "gcja5.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome {
|
||||
@@ -17,7 +16,7 @@ static const char *const TAG = "gcja5";
|
||||
void GCJA5Component::setup() { ESP_LOGCONFIG(TAG, "Setting up gcja5..."); }
|
||||
|
||||
void GCJA5Component::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_transmission_ >= 500) {
|
||||
// last transmission too long ago. Reset RX index.
|
||||
this->rx_message_.clear();
|
||||
|
||||
@@ -21,7 +21,7 @@ MODELS = {
|
||||
"yag": Model.GREE_YAG,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(GreeClimate).extend(
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(GreeClimate).extend(
|
||||
{
|
||||
cv.Required(CONF_MODEL): cv.enum(MODELS),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "growatt_solar.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace growatt_solar {
|
||||
@@ -19,7 +18,7 @@ void GrowattSolar::loop() {
|
||||
|
||||
void GrowattSolar::update() {
|
||||
// If our last send has had no reply yet, and it wasn't that long ago, do nothing.
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
uint32_t now = millis();
|
||||
if (now - this->last_send_ < this->get_update_interval() / 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ VERTICAL_DIRECTIONS = {
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
climate_ir.climate_ir_with_receiver_schema(HeatpumpIRClimate).extend(
|
||||
climate_ir.climare_ir_with_receiver_schema(HeatpumpIRClimate).extend(
|
||||
{
|
||||
cv.Required(CONF_PROTOCOL): cv.enum(PROTOCOLS),
|
||||
cv.Required(CONF_HORIZONTAL_DEFAULT): cv.enum(HORIZONTAL_DIRECTIONS),
|
||||
|
||||
@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
||||
hitachi_ac344_ns = cg.esphome_ns.namespace("hitachi_ac344")
|
||||
HitachiClimate = hitachi_ac344_ns.class_("HitachiClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(HitachiClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(HitachiClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
||||
hitachi_ac424_ns = cg.esphome_ns.namespace("hitachi_ac424")
|
||||
HitachiClimate = hitachi_ac424_ns.class_("HitachiClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(HitachiClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(HitachiClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -4,7 +4,6 @@ from esphome.components.esp32 import get_esp32_variant
|
||||
from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
@@ -75,7 +74,6 @@ I2S_PORTS = {
|
||||
VARIANT_ESP32S2: 1,
|
||||
VARIANT_ESP32S3: 2,
|
||||
VARIANT_ESP32C3: 1,
|
||||
VARIANT_ESP32P4: 3,
|
||||
}
|
||||
|
||||
i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t")
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "kuntze.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace kuntze {
|
||||
@@ -61,7 +60,7 @@ void Kuntze::on_modbus_data(const std::vector<uint8_t> &data) {
|
||||
}
|
||||
|
||||
void Kuntze::loop() {
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
uint32_t now = millis();
|
||||
// timeout after 15 seconds
|
||||
if (this->waiting_ && (now - this->last_send_ > 15000)) {
|
||||
ESP_LOGW(TAG, "timed out waiting for response");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
|
||||
import esphome.codegen as cg
|
||||
|
||||
|
||||
@@ -8,10 +8,8 @@ from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C2,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32C5,
|
||||
VARIANT_ESP32C6,
|
||||
VARIANT_ESP32H2,
|
||||
VARIANT_ESP32P4,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
@@ -26,7 +24,6 @@ from esphome.const import (
|
||||
CONF_HARDWARE_UART,
|
||||
CONF_ID,
|
||||
CONF_LEVEL,
|
||||
CONF_LOGGER,
|
||||
CONF_LOGS,
|
||||
CONF_ON_MESSAGE,
|
||||
CONF_TAG,
|
||||
@@ -90,10 +87,8 @@ UART_SELECTION_ESP32 = {
|
||||
VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32C3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32C2: [UART0, UART1],
|
||||
VARIANT_ESP32C5: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32H2: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
VARIANT_ESP32P4: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||
}
|
||||
|
||||
UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1]
|
||||
@@ -209,10 +204,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
esp32_s3_idf=USB_SERIAL_JTAG,
|
||||
esp32_c3_arduino=USB_CDC,
|
||||
esp32_c3_idf=USB_SERIAL_JTAG,
|
||||
esp32_c5_idf=USB_SERIAL_JTAG,
|
||||
esp32_c6_arduino=USB_CDC,
|
||||
esp32_c6_idf=USB_SERIAL_JTAG,
|
||||
esp32_p4_idf=USB_SERIAL_JTAG,
|
||||
rp2040=USB_CDC,
|
||||
bk72xx=DEFAULT,
|
||||
rtl87xx=DEFAULT,
|
||||
@@ -254,7 +247,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config):
|
||||
baud_rate = config[CONF_BAUD_RATE]
|
||||
level = config[CONF_LEVEL]
|
||||
CORE.data.setdefault(CONF_LOGGER, {})[CONF_LEVEL] = level
|
||||
initial_level = LOG_LEVELS[config.get(CONF_INITIAL_LEVEL, level)]
|
||||
log = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "esp_idf_version.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_idf_version.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <fcntl.h>
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
|
||||
@@ -174,11 +174,11 @@ void Logger::pre_setup() {
|
||||
#ifdef USE_ESP_IDF
|
||||
void HOT Logger::write_msg_(const char *msg) {
|
||||
if (
|
||||
#if defined(USE_LOGGER_USB_CDC) && !defined(USE_LOGGER_USB_SERIAL_JTAG)
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2)
|
||||
this->uart_ == UART_SELECTION_USB_CDC
|
||||
#elif defined(USE_LOGGER_USB_SERIAL_JTAG) && !defined(USE_LOGGER_USB_CDC)
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
|
||||
#elif defined(USE_LOGGER_USB_CDC) && defined(USE_LOGGER_USB_SERIAL_JTAG)
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
this->uart_ == UART_SELECTION_USB_CDC || this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
|
||||
#else
|
||||
/* DISABLES CODE */ (false) // NOLINT
|
||||
|
||||
@@ -5,7 +5,7 @@ from esphome.const import CONF_LEVEL, CONF_LOGGER, ENTITY_CATEGORY_CONFIG, ICON_
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_helpers import register_component, register_parented
|
||||
|
||||
from .. import CONF_LOGGER_ID, LOG_LEVELS, Logger, logger_ns
|
||||
from .. import CONF_LOGGER_ID, LOG_LEVEL_SEVERITY, Logger, logger_ns
|
||||
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
|
||||
@@ -21,10 +21,9 @@ CONFIG_SCHEMA = select.select_schema(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_LOGGER_ID])
|
||||
levels = list(LOG_LEVELS)
|
||||
index = levels.index(CORE.data[CONF_LOGGER][CONF_LEVEL])
|
||||
levels = LOG_LEVEL_SEVERITY
|
||||
index = levels.index(CORE.config[CONF_LOGGER][CONF_LEVEL])
|
||||
levels = levels[: index + 1]
|
||||
var = await select.new_select(config, options=levels)
|
||||
await register_parented(var, parent)
|
||||
await register_parented(var, config[CONF_LOGGER_ID])
|
||||
await register_component(var, config)
|
||||
|
||||
@@ -321,7 +321,7 @@ async def to_code(configs):
|
||||
frac = 2
|
||||
elif frac > 0.19:
|
||||
frac = 4
|
||||
elif frac != 0:
|
||||
else:
|
||||
frac = 8
|
||||
displays = [
|
||||
await cg.get_variable(display) for display in config[df.CONF_DISPLAYS]
|
||||
@@ -422,7 +422,7 @@ LVGL_SCHEMA = cv.All(
|
||||
): lvalid.lv_font,
|
||||
cv.Optional(df.CONF_FULL_REFRESH, default=False): cv.boolean,
|
||||
cv.Optional(CONF_DRAW_ROUNDING, default=2): cv.positive_int,
|
||||
cv.Optional(CONF_BUFFER_SIZE, default=0): cv.percentage,
|
||||
cv.Optional(CONF_BUFFER_SIZE, default="100%"): cv.percentage,
|
||||
cv.Optional(df.CONF_LOG_LEVEL, default="WARN"): cv.one_of(
|
||||
*df.LV_LOG_LEVELS, upper=True
|
||||
),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
from typing import Any, Callable
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from typing import Union
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import image
|
||||
from esphome.components.color import CONF_HEX, ColorStruct, from_rgbw
|
||||
@@ -359,7 +361,7 @@ lv_image_list = LValidator(
|
||||
lv_bool = LValidator(cv.boolean, cg.bool_, retmapper=literal)
|
||||
|
||||
|
||||
def lv_pct(value: int | float):
|
||||
def lv_pct(value: Union[int, float]):
|
||||
if isinstance(value, float):
|
||||
value = int(value * 100)
|
||||
return literal(f"lv_pct({value})")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import abc
|
||||
from typing import Union
|
||||
|
||||
from esphome import codegen as cg
|
||||
from esphome.config import Config
|
||||
@@ -74,7 +75,7 @@ class CodeContext(abc.ABC):
|
||||
code_context = None
|
||||
|
||||
@abc.abstractmethod
|
||||
def add(self, expression: Expression | Statement):
|
||||
def add(self, expression: Union[Expression, Statement]):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
@@ -88,13 +89,13 @@ class CodeContext(abc.ABC):
|
||||
CodeContext.append(RawStatement("}"))
|
||||
|
||||
@staticmethod
|
||||
def append(expression: Expression | Statement):
|
||||
def append(expression: Union[Expression, Statement]):
|
||||
if CodeContext.code_context is not None:
|
||||
CodeContext.code_context.add(expression)
|
||||
return expression
|
||||
|
||||
def __init__(self):
|
||||
self.previous: CodeContext | None = None
|
||||
self.previous: Union[CodeContext | None] = None
|
||||
self.indent_level = 0
|
||||
|
||||
async def __aenter__(self):
|
||||
@@ -120,7 +121,7 @@ class MainContext(CodeContext):
|
||||
Code generation into the main() function
|
||||
"""
|
||||
|
||||
def add(self, expression: Expression | Statement):
|
||||
def add(self, expression: Union[Expression, Statement]):
|
||||
return cg.add(self.indented_statement(expression))
|
||||
|
||||
|
||||
@@ -143,7 +144,7 @@ class LambdaContext(CodeContext):
|
||||
self.capture = capture
|
||||
self.where = where
|
||||
|
||||
def add(self, expression: Expression | Statement):
|
||||
def add(self, expression: Union[Expression, Statement]):
|
||||
self.code_list.append(self.indented_statement(expression))
|
||||
return expression
|
||||
|
||||
@@ -185,7 +186,7 @@ class LvContext(LambdaContext):
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
await super().__aexit__(exc_type, exc_val, exc_tb)
|
||||
|
||||
def add(self, expression: Expression | Statement):
|
||||
def add(self, expression: Union[Expression, Statement]):
|
||||
cg.add(expression)
|
||||
return expression
|
||||
|
||||
@@ -302,7 +303,7 @@ lvgl_static = MockObj("LvglComponent", "::")
|
||||
|
||||
|
||||
# equivalent to cg.add() for the current code context
|
||||
def lv_add(expression: Expression | Statement):
|
||||
def lv_add(expression: Union[Expression, Statement]):
|
||||
return CodeContext.append(expression)
|
||||
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ namespace esphome {
|
||||
namespace lvgl {
|
||||
static const char *const TAG = "lvgl";
|
||||
|
||||
static const size_t MIN_BUFFER_FRAC = 8;
|
||||
|
||||
static const char *const EVENT_NAMES[] = {
|
||||
"NONE",
|
||||
"PRESSED",
|
||||
@@ -87,7 +85,6 @@ lv_event_code_t lv_update_event; // NOLINT
|
||||
void LvglComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "LVGL:");
|
||||
ESP_LOGCONFIG(TAG, " Display width/height: %d x %d", this->disp_drv_.hor_res, this->disp_drv_.ver_res);
|
||||
ESP_LOGCONFIG(TAG, " Buffer size: %zu%%", 100 / this->buffer_frac_);
|
||||
ESP_LOGCONFIG(TAG, " Rotation: %d", this->rotation);
|
||||
ESP_LOGCONFIG(TAG, " Draw rounding: %d", (int) this->draw_rounding);
|
||||
}
|
||||
@@ -435,28 +432,18 @@ void LvglComponent::setup() {
|
||||
auto *display = this->displays_[0];
|
||||
auto width = display->get_width();
|
||||
auto height = display->get_height();
|
||||
auto frac = this->buffer_frac_;
|
||||
if (frac == 0)
|
||||
frac = 1;
|
||||
size_t buffer_pixels = width * height / frac;
|
||||
size_t buffer_pixels = width * height / this->buffer_frac_;
|
||||
auto buf_bytes = buffer_pixels * LV_COLOR_DEPTH / 8;
|
||||
void *buffer = nullptr;
|
||||
if (this->buffer_frac_ >= MIN_BUFFER_FRAC / 2)
|
||||
if (this->buffer_frac_ >= 4)
|
||||
buffer = malloc(buf_bytes); // NOLINT
|
||||
if (buffer == nullptr)
|
||||
buffer = lv_custom_mem_alloc(buf_bytes); // NOLINT
|
||||
// if specific buffer size not set and can't get 100%, try for a smaller one
|
||||
if (buffer == nullptr && this->buffer_frac_ == 0) {
|
||||
frac = MIN_BUFFER_FRAC;
|
||||
buffer_pixels /= MIN_BUFFER_FRAC;
|
||||
buffer = lv_custom_mem_alloc(buf_bytes / MIN_BUFFER_FRAC); // NOLINT
|
||||
}
|
||||
if (buffer == nullptr) {
|
||||
this->status_set_error("Memory allocation failure");
|
||||
this->mark_failed();
|
||||
this->status_set_error("Memory allocation failure");
|
||||
return;
|
||||
}
|
||||
this->buffer_frac_ = frac;
|
||||
lv_disp_draw_buf_init(&this->draw_buf_, buffer, nullptr, buffer_pixels);
|
||||
this->disp_drv_.hor_res = width;
|
||||
this->disp_drv_.ver_res = height;
|
||||
@@ -466,8 +453,8 @@ void LvglComponent::setup() {
|
||||
if (this->rotation != display::DISPLAY_ROTATION_0_DEGREES) {
|
||||
this->rotate_buf_ = static_cast<lv_color_t *>(lv_custom_mem_alloc(buf_bytes)); // NOLINT
|
||||
if (this->rotate_buf_ == nullptr) {
|
||||
this->status_set_error("Memory allocation failure");
|
||||
this->mark_failed();
|
||||
this->status_set_error("Memory allocation failure");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,43 +36,29 @@ from .types import (
|
||||
# this will be populated later, in __init__.py to avoid circular imports.
|
||||
WIDGET_TYPES: dict = {}
|
||||
|
||||
TIME_TEXT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_TIME_FORMAT): cv.string,
|
||||
cv.GenerateID(CONF_TIME): cv.templatable(cv.use_id(RealTimeClock)),
|
||||
}
|
||||
)
|
||||
|
||||
PRINTF_TEXT_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_FORMAT): cv.string,
|
||||
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
|
||||
},
|
||||
),
|
||||
validate_printf,
|
||||
)
|
||||
|
||||
|
||||
def _validate_text(value):
|
||||
"""
|
||||
Do some sanity checking of the format to get better error messages
|
||||
than using cv.Any
|
||||
"""
|
||||
if value is None:
|
||||
raise cv.Invalid("No text specified")
|
||||
if isinstance(value, dict):
|
||||
if CONF_TIME_FORMAT in value:
|
||||
return TIME_TEXT_SCHEMA(value)
|
||||
return PRINTF_TEXT_SCHEMA(value)
|
||||
|
||||
return cv.templatable(cv.string)(value)
|
||||
|
||||
|
||||
# A schema for text properties
|
||||
TEXT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_TEXT): _validate_text,
|
||||
cv.Optional(CONF_TEXT): cv.Any(
|
||||
cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_FORMAT): cv.string,
|
||||
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(
|
||||
cv.lambda_
|
||||
),
|
||||
},
|
||||
),
|
||||
validate_printf,
|
||||
),
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_TIME_FORMAT): cv.string,
|
||||
cv.GenerateID(CONF_TIME): cv.templatable(cv.use_id(RealTimeClock)),
|
||||
}
|
||||
),
|
||||
cv.templatable(cv.string),
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -261,13 +247,11 @@ FLAG_LIST = cv.ensure_list(df.LvConstant("LV_OBJ_FLAG_", *df.OBJ_FLAGS).one_of)
|
||||
def part_schema(parts):
|
||||
"""
|
||||
Generate a schema for the various parts (e.g. main:, indicator:) of a widget type
|
||||
:param parts: The parts to include
|
||||
:param parts: The parts to include in the schema
|
||||
:return: The schema
|
||||
"""
|
||||
return (
|
||||
cv.Schema({cv.Optional(part): STATE_SCHEMA for part in parts})
|
||||
.extend(STATE_SCHEMA)
|
||||
.extend(FLAG_SCHEMA)
|
||||
return cv.Schema({cv.Optional(part): STATE_SCHEMA for part in parts}).extend(
|
||||
STATE_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
@@ -304,18 +288,22 @@ def base_update_schema(widget_type, parts):
|
||||
:param parts: The allowable parts to specify
|
||||
:return:
|
||||
"""
|
||||
return part_schema(parts).extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.ensure_list(
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(widget_type),
|
||||
},
|
||||
key=CONF_ID,
|
||||
)
|
||||
),
|
||||
cv.Optional(CONF_STATE): SET_STATE_SCHEMA,
|
||||
}
|
||||
return (
|
||||
part_schema(parts)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.ensure_list(
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(widget_type),
|
||||
},
|
||||
key=CONF_ID,
|
||||
)
|
||||
),
|
||||
cv.Optional(CONF_STATE): SET_STATE_SCHEMA,
|
||||
}
|
||||
)
|
||||
.extend(FLAG_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
@@ -333,6 +321,7 @@ def obj_schema(widget_type: WidgetType):
|
||||
"""
|
||||
return (
|
||||
part_schema(widget_type.parts)
|
||||
.extend(FLAG_SCHEMA)
|
||||
.extend(LAYOUT_SCHEMA)
|
||||
.extend(ALIGN_TO_SCHEMA)
|
||||
.extend(automation_schema(widget_type.w_type))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import sys
|
||||
from typing import Any
|
||||
from typing import Any, Union
|
||||
|
||||
from esphome import codegen as cg, config_validation as cv
|
||||
from esphome.config_validation import Invalid
|
||||
@@ -262,7 +262,7 @@ async def wait_for_widgets():
|
||||
await FakeAwaitable(widgets_wait_generator())
|
||||
|
||||
|
||||
async def get_widgets(config: dict | list, id: str = CONF_ID) -> list[Widget]:
|
||||
async def get_widgets(config: Union[dict, list], id: str = CONF_ID) -> list[Widget]:
|
||||
if not config:
|
||||
return []
|
||||
if not isinstance(config, list):
|
||||
|
||||
@@ -24,7 +24,6 @@ from .obj import obj_spec
|
||||
|
||||
CONF_TABVIEW = "tabview"
|
||||
CONF_TAB_STYLE = "tab_style"
|
||||
CONF_CONTENT_STYLE = "content_style"
|
||||
|
||||
lv_tab_t = LvType("lv_obj_t")
|
||||
|
||||
@@ -40,7 +39,6 @@ TABVIEW_SCHEMA = cv.Schema(
|
||||
)
|
||||
),
|
||||
cv.Optional(CONF_TAB_STYLE): part_schema(buttonmatrix_spec.parts),
|
||||
cv.Optional(CONF_CONTENT_STYLE): part_schema(obj_spec.parts),
|
||||
cv.Optional(CONF_POSITION, default="top"): DIRECTIONS.one_of,
|
||||
cv.Optional(CONF_SIZE, default="10%"): size,
|
||||
}
|
||||
@@ -81,11 +79,6 @@ class TabviewType(WidgetType):
|
||||
"tabview_btnmatrix", lv_obj_t, rhs=lv_expr.tabview_get_tab_btns(w.obj)
|
||||
) as btnmatrix_obj:
|
||||
await set_obj_properties(Widget(btnmatrix_obj, obj_spec), button_style)
|
||||
if content_style := config.get(CONF_CONTENT_STYLE):
|
||||
with LocalVariable(
|
||||
"tabview_content", lv_obj_t, rhs=lv_expr.tabview_get_content(w.obj)
|
||||
) as content_obj:
|
||||
await set_obj_properties(Widget(content_obj, obj_spec), content_style)
|
||||
|
||||
def obj_creator(self, parent: MockObjClass, config: dict):
|
||||
return lv_expr.call(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "matrix_keypad.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace matrix_keypad {
|
||||
@@ -29,7 +28,7 @@ void MatrixKeypad::setup() {
|
||||
void MatrixKeypad::loop() {
|
||||
static uint32_t active_start = 0;
|
||||
static int active_key = -1;
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
uint32_t now = millis();
|
||||
int key = -1;
|
||||
bool error = false;
|
||||
int pos = 0, row, col;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "max7219font.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -64,7 +63,7 @@ void MAX7219Component::dump_config() {
|
||||
}
|
||||
|
||||
void MAX7219Component::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
const uint32_t millis_since_last_scroll = now - this->last_scroll_;
|
||||
const size_t first_line_size = this->max_displaybuffer_[0].size();
|
||||
// check if the buffer has shrunk past the current position since last update
|
||||
|
||||
@@ -147,11 +147,7 @@ bool StreamingModel::perform_streaming_inference(const int8_t features[PREPROCES
|
||||
this->recent_streaming_probabilities_[this->last_n_index_] = output->data.uint8[0]; // probability;
|
||||
this->unprocessed_probability_status_ = true;
|
||||
}
|
||||
if (this->recent_streaming_probabilities_[this->last_n_index_] < this->probability_cutoff_) {
|
||||
// Only increment ignore windows if less than the probability cutoff; this forces the model to "cool-off" from a
|
||||
// previous detection and calling ``reset_probabilities`` so it avoids duplicate detections
|
||||
this->ignore_windows_ = std::min(this->ignore_windows_ + 1, 0);
|
||||
}
|
||||
this->ignore_windows_ = std::min(this->ignore_windows_ + 1, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ midea_ir_ns = cg.esphome_ns.namespace("midea_ir")
|
||||
MideaIR = midea_ir_ns.class_("MideaIR", climate_ir.ClimateIR)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(MideaIR).extend(
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(MideaIR).extend(
|
||||
{
|
||||
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ VERTICAL_DIRECTIONS = {
|
||||
}
|
||||
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(MitsubishiClimate).extend(
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(MitsubishiClimate).extend(
|
||||
{
|
||||
cv.Optional(CONF_SET_FAN_MODE, default="3levels"): cv.enum(SETFANMODE),
|
||||
cv.Optional(CONF_SUPPORTS_DRY, default=False): cv.boolean,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "modbus.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace modbus {
|
||||
@@ -14,7 +13,7 @@ void Modbus::setup() {
|
||||
}
|
||||
}
|
||||
void Modbus::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
|
||||
while (this->available()) {
|
||||
uint8_t byte;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import switch
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS, CONF_ASSUMED_STATE, CONF_ID
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID
|
||||
|
||||
from .. import (
|
||||
MODBUS_REGISTER_TYPE,
|
||||
@@ -36,7 +36,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
.extend(ModbusItemBaseSchema)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
|
||||
cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||
@@ -63,10 +62,7 @@ async def to_code(config):
|
||||
paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
|
||||
cg.add(var.set_parent(paren))
|
||||
cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE]))
|
||||
assumed_state = config[CONF_ASSUMED_STATE]
|
||||
cg.add(var.set_assumed_state(assumed_state))
|
||||
if not assumed_state:
|
||||
cg.add(paren.add_sensor_item(var))
|
||||
cg.add(paren.add_sensor_item(var))
|
||||
if CONF_WRITE_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
config[CONF_WRITE_LAMBDA],
|
||||
|
||||
@@ -19,10 +19,6 @@ void ModbusSwitch::setup() {
|
||||
}
|
||||
void ModbusSwitch::dump_config() { LOG_SWITCH(TAG, "Modbus Controller Switch", this); }
|
||||
|
||||
void ModbusSwitch::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
|
||||
|
||||
bool ModbusSwitch::assumed_state() { return this->assumed_state_; }
|
||||
|
||||
void ModbusSwitch::parse_and_publish(const std::vector<uint8_t> &data) {
|
||||
bool value = false;
|
||||
switch (this->register_type) {
|
||||
|
||||
@@ -29,7 +29,6 @@ class ModbusSwitch : public Component, public switch_::Switch, public SensorItem
|
||||
void setup() override;
|
||||
void write_state(bool state) override;
|
||||
void dump_config() override;
|
||||
void set_assumed_state(bool assumed_state);
|
||||
void set_state(bool state) { this->state = state; }
|
||||
void parse_and_publish(const std::vector<uint8_t> &data) override;
|
||||
void set_parent(ModbusController *parent) { this->parent_ = parent; }
|
||||
@@ -41,12 +40,10 @@ class ModbusSwitch : public Component, public switch_::Switch, public SensorItem
|
||||
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
|
||||
|
||||
protected:
|
||||
bool assumed_state() override;
|
||||
ModbusController *parent_{nullptr};
|
||||
bool use_write_multiple_{false};
|
||||
optional<transform_func_t> publish_transform_func_{nullopt};
|
||||
optional<write_transform_func_t> write_transform_func_{nullopt};
|
||||
bool assumed_state_{false};
|
||||
};
|
||||
|
||||
} // namespace modbus_controller
|
||||
|
||||
@@ -345,7 +345,7 @@ void MQTTClientComponent::loop() {
|
||||
this->disconnect_reason_.reset();
|
||||
}
|
||||
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
const uint32_t now = millis();
|
||||
|
||||
switch (this->state_) {
|
||||
case MQTT_CLIENT_DISABLED:
|
||||
|
||||
@@ -6,7 +6,7 @@ AUTO_LOAD = ["climate_ir"]
|
||||
noblex_ns = cg.esphome_ns.namespace("noblex")
|
||||
NoblexClimate = noblex_ns.class_("NoblexClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(NoblexClimate)
|
||||
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(NoblexClimate)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user