Merge remote-tracking branch 'upstream/dev' into memory

This commit is contained in:
J. Nick Koston 2025-07-15 12:36:21 -10:00
commit 9f6e4a554e
No known key found for this signature in database
163 changed files with 3057 additions and 6103 deletions

View File

@ -1 +1 @@
a3cdfc378d28b53b416a1d5bf0ab9077ee18867f0d39436ea8013cf5a4ead87a 07f621354fe1350ba51953c80273cd44a04aa44f15cc30bd7b8fe2a641427b7a

View File

@ -1,4 +1,4 @@
[run] [run]
omit = omit =
esphome/components/* esphome/components/*
tests/integration/* tests/integration/*

92
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,92 @@
name: Report an issue with ESPHome
description: Report an issue with ESPHome.
body:
- type: markdown
attributes:
value: |
This issue form is for reporting bugs only!
If you have a feature request or enhancement, please [request them here instead][fr].
[fr]: https://github.com/orgs/esphome/discussions
- type: textarea
validations:
required: true
id: problem
attributes:
label: The problem
description: >-
Describe the issue you are experiencing here to communicate to the
maintainers. Tell us what you were trying to do and what happened.
Provide a clear and concise description of what the problem is.
- type: markdown
attributes:
value: |
## Environment
- type: input
id: version
validations:
required: true
attributes:
label: Which version of ESPHome has the issue?
description: >
ESPHome version like 1.19, 2025.6.0 or 2025.XX.X-dev.
- type: dropdown
validations:
required: true
id: installation
attributes:
label: What type of installation are you using?
options:
- Home Assistant Add-on
- Docker
- pip
- type: dropdown
validations:
required: true
id: platform
attributes:
label: What platform are you using?
options:
- ESP8266
- ESP32
- RP2040
- BK72XX
- RTL87XX
- LN882X
- Host
- Other
- type: input
id: component_name
attributes:
label: Component causing the issue
description: >
The name of the component or platform. For example, api/i2c or ultrasonic.
- type: markdown
attributes:
value: |
# Details
- type: textarea
id: config
attributes:
label: YAML Config
description: |
Include a complete YAML configuration file demonstrating the problem here. Preferably post the *entire* file - don't make assumptions about what is unimportant. However, if it's a large or complicated config then you will need to reduce it to the smallest possible file *that still demonstrates the problem*. If you don't provide enough information to *easily* reproduce the problem, it's unlikely your bug report will get any attention. Logs do not belong here, attach them below.
render: yaml
- type: textarea
id: logs
attributes:
label: Anything in the logs that might be useful for us?
description: For example, error message, or stack traces. Serial or USB logs are much more useful than WiFi logs.
render: txt
- type: textarea
id: additional
attributes:
label: Additional information
description: >
If you have any additional information for us, use the field below.
Please note, you can attach screenshots or screen recordings here, by
dragging and dropping files in the field below.

View File

@ -1,15 +1,21 @@
--- ---
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: Issue Tracker - name: Report an issue with the ESPHome documentation
url: https://github.com/esphome/issues url: https://github.com/esphome/esphome-docs/issues/new/choose
about: Please create bug reports in the dedicated issue tracker. about: Report an issue with the ESPHome documentation.
- name: Feature Request Tracker - name: Report an issue with the ESPHome web server
url: https://github.com/esphome/feature-requests url: https://github.com/esphome/esphome-webserver/issues/new/choose
about: | about: Report an issue with the ESPHome web server.
Please create feature requests in the dedicated feature request tracker. - name: Report an issue with the ESPHome Builder / Dashboard
url: https://github.com/esphome/dashboard/issues/new/choose
about: Report an issue with the ESPHome Builder / Dashboard.
- name: Report an issue with the ESPHome API client
url: https://github.com/esphome/aioesphomeapi/issues/new/choose
about: Report an issue with the ESPHome API client.
- name: Make a Feature Request
url: https://github.com/orgs/esphome/discussions
about: Please create feature requests in the dedicated feature request tracker.
- name: Frequently Asked Question - name: Frequently Asked Question
url: https://esphome.io/guides/faq.html url: https://esphome.io/guides/faq.html
about: | about: Please view the FAQ for common questions and what to include in a bug report.
Please view the FAQ for common questions and what
to include in a bug report.

View File

@ -73,4 +73,3 @@ jobs:
}); });
} }
} }

View File

@ -39,7 +39,7 @@ jobs:
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.2
- name: Generate cache-key - name: Generate cache-key
id: cache-key id: cache-key
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v5.6.0 uses: actions/setup-python@v5.6.0
@ -58,55 +58,9 @@ jobs:
python -m venv venv python -m venv venv
. venv/bin/activate . venv/bin/activate
python --version python --version
pip install -r requirements.txt -r requirements_test.txt pip install -r requirements.txt -r requirements_test.txt pre-commit
pip install -e . pip install -e .
ruff:
name: Check ruff
runs-on: ubuntu-24.04
needs:
- common
- determine-jobs
if: needs.determine-jobs.outputs.python-linters == 'true'
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run Ruff
run: |
. venv/bin/activate
ruff format esphome tests
- name: Suggested changes
run: script/ci-suggest-changes
if: always()
flake8:
name: Check flake8
runs-on: ubuntu-24.04
needs:
- common
- determine-jobs
if: needs.determine-jobs.outputs.python-linters == 'true'
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run flake8
run: |
. venv/bin/activate
flake8 esphome
- name: Suggested changes
run: script/ci-suggest-changes
if: always()
pylint: pylint:
name: Check pylint name: Check pylint
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@ -130,29 +84,6 @@ jobs:
run: script/ci-suggest-changes run: script/ci-suggest-changes
if: always() if: always()
pyupgrade:
name: Check pyupgrade
runs-on: ubuntu-24.04
needs:
- common
- determine-jobs
if: needs.determine-jobs.outputs.python-linters == 'true'
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run pyupgrade
run: |
. venv/bin/activate
pyupgrade ${{ env.PYUPGRADE_TARGET }} `find esphome -name "*.py" -type f`
- name: Suggested changes
run: script/ci-suggest-changes
if: always()
ci-custom: ci-custom:
name: Run script/ci-custom name: Run script/ci-custom
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@ -248,7 +179,6 @@ jobs:
outputs: outputs:
integration-tests: ${{ steps.determine.outputs.integration-tests }} integration-tests: ${{ steps.determine.outputs.integration-tests }}
clang-tidy: ${{ steps.determine.outputs.clang-tidy }} clang-tidy: ${{ steps.determine.outputs.clang-tidy }}
clang-format: ${{ steps.determine.outputs.clang-format }}
python-linters: ${{ steps.determine.outputs.python-linters }} python-linters: ${{ steps.determine.outputs.python-linters }}
changed-components: ${{ steps.determine.outputs.changed-components }} changed-components: ${{ steps.determine.outputs.changed-components }}
component-test-count: ${{ steps.determine.outputs.component-test-count }} component-test-count: ${{ steps.determine.outputs.component-test-count }}
@ -276,7 +206,6 @@ jobs:
# Extract individual fields # Extract individual fields
echo "integration-tests=$(echo "$output" | jq -r '.integration_tests')" >> $GITHUB_OUTPUT echo "integration-tests=$(echo "$output" | jq -r '.integration_tests')" >> $GITHUB_OUTPUT
echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT
echo "clang-format=$(echo "$output" | jq -r '.clang_format')" >> $GITHUB_OUTPUT
echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT
echo "component-test-count=$(echo "$output" | jq -r '.component_test_count')" >> $GITHUB_OUTPUT echo "component-test-count=$(echo "$output" | jq -r '.component_test_count')" >> $GITHUB_OUTPUT
@ -317,46 +246,11 @@ jobs:
. venv/bin/activate . venv/bin/activate
pytest -vv --no-cov --tb=native -n auto tests/integration/ pytest -vv --no-cov --tb=native -n auto tests/integration/
clang-format:
name: Check clang-format
runs-on: ubuntu-24.04
needs:
- common
- determine-jobs
if: needs.determine-jobs.outputs.clang-format == 'true'
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Install clang-format
run: |
. venv/bin/activate
pip install clang-format -c requirements_dev.txt
- name: Run clang-format
run: |
. venv/bin/activate
script/clang-format -i
git diff-index --quiet HEAD --
- name: Suggested changes
run: script/ci-suggest-changes
if: always()
clang-tidy: clang-tidy:
name: ${{ matrix.name }} name: ${{ matrix.name }}
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: needs:
- common - common
- ruff
- ci-custom
- clang-format
- flake8
- pylint
- pytest
- pyupgrade
- determine-jobs - determine-jobs
if: needs.determine-jobs.outputs.clang-tidy == 'true' if: needs.determine-jobs.outputs.clang-tidy == 'true'
env: env:
@ -562,24 +456,41 @@ jobs:
./script/test_build_components -e compile -c $component ./script/test_build_components -e compile -c $component
done done
pre-commit-ci-lite:
name: pre-commit.ci lite
runs-on: ubuntu-latest
needs:
- common
if: github.event_name == 'pull_request' && github.base_ref != 'beta' && github.base_ref != 'release'
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- uses: pre-commit/action@v3.0.1
env:
SKIP: pylint,clang-tidy-hash
- uses: pre-commit-ci/lite-action@v1.1.0
if: always()
ci-status: ci-status:
name: CI Status name: CI Status
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: needs:
- common - common
- ruff
- ci-custom - ci-custom
- clang-format
- flake8
- pylint - pylint
- pytest - pytest
- integration-tests - integration-tests
- pyupgrade
- clang-tidy - clang-tidy
- determine-jobs - determine-jobs
- test-build-components - test-build-components
- test-build-components-splitter - test-build-components-splitter
- test-build-components-split - test-build-components-split
- pre-commit-ci-lite
if: always() if: always()
steps: steps:
- name: Success - name: Success

View File

@ -1,25 +0,0 @@
---
name: YAML lint
on:
push:
branches: [dev, beta, release]
paths:
- "**.yaml"
- "**.yml"
pull_request:
paths:
- "**.yaml"
- "**.yml"
jobs:
yamllint:
name: yamllint
runs-on: ubuntu-latest
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.2.2
- name: Run yamllint
uses: frenck/action-yamllint@v1.5.0
with:
strict: true

View File

@ -1,10 +1,17 @@
--- ---
# See https://pre-commit.com for more information # See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks # See https://pre-commit.com/hooks.html for more hooks
ci:
autoupdate_commit_msg: 'pre-commit: autoupdate'
autoupdate_schedule: off # Disabled until ruff versions are synced between deps and pre-commit
# Skip hooks that have issues in pre-commit CI environment
skip: [pylint, clang-tidy-hash]
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: v0.12.2 rev: v0.12.3
hooks: hooks:
# Run the linter. # Run the linter.
- id: ruff - id: ruff
@ -20,13 +27,15 @@ repos:
- pydocstyle==5.1.1 - pydocstyle==5.1.1
files: ^(esphome|tests)/.+\.py$ files: ^(esphome|tests)/.+\.py$
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0 rev: v5.0.0
hooks: hooks:
- id: no-commit-to-branch - id: no-commit-to-branch
args: args:
- --branch=dev - --branch=dev
- --branch=release - --branch=release
- --branch=beta - --branch=beta
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.20.0 rev: v3.20.0
hooks: hooks:
@ -36,6 +45,7 @@ repos:
rev: v1.37.1 rev: v1.37.1
hooks: hooks:
- id: yamllint - id: yamllint
exclude: ^(\.clang-format|\.clang-tidy)$
- repo: https://github.com/pre-commit/mirrors-clang-format - repo: https://github.com/pre-commit/mirrors-clang-format
rev: v13.0.1 rev: v13.0.1
hooks: hooks:

View File

@ -24,8 +24,9 @@ from esphome.const import (
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_VARIABLES, CONF_VARIABLES,
) )
from esphome.core import coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
DOMAIN = "api"
DEPENDENCIES = ["network"] DEPENDENCIES = ["network"]
AUTO_LOAD = ["socket"] AUTO_LOAD = ["socket"]
CODEOWNERS = ["@OttoWinter"] CODEOWNERS = ["@OttoWinter"]
@ -51,6 +52,7 @@ SERVICE_ARG_NATIVE_TYPES = {
} }
CONF_ENCRYPTION = "encryption" CONF_ENCRYPTION = "encryption"
CONF_BATCH_DELAY = "batch_delay" CONF_BATCH_DELAY = "batch_delay"
CONF_CUSTOM_SERVICES = "custom_services"
def validate_encryption_key(value): def validate_encryption_key(value):
@ -115,6 +117,7 @@ CONFIG_SCHEMA = cv.All(
cv.positive_time_period_milliseconds, cv.positive_time_period_milliseconds,
cv.Range(max=cv.TimePeriod(milliseconds=65535)), cv.Range(max=cv.TimePeriod(milliseconds=65535)),
), ),
cv.Optional(CONF_CUSTOM_SERVICES, default=False): cv.boolean,
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
single=True single=True
), ),
@ -139,8 +142,11 @@ async def to_code(config):
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY])) cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
# Set USE_API_SERVICES if any services are enabled
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
cg.add_define("USE_API_SERVICES")
if actions := config.get(CONF_ACTIONS, []): if actions := config.get(CONF_ACTIONS, []):
cg.add_define("USE_API_YAML_SERVICES")
for conf in actions: for conf in actions:
template_args = [] template_args = []
func_args = [] func_args = []
@ -317,7 +323,10 @@ async def api_connected_to_code(config, condition_id, template_arg, args):
def FILTER_SOURCE_FILES() -> list[str]: def FILTER_SOURCE_FILES() -> list[str]:
"""Filter out api_pb2_dump.cpp when proto message dumping is not enabled.""" """Filter out api_pb2_dump.cpp when proto message dumping is not enabled
and user_services.cpp when no services are defined."""
files_to_filter = []
# api_pb2_dump.cpp is only needed when HAS_PROTO_MESSAGE_DUMP is defined # api_pb2_dump.cpp is only needed when HAS_PROTO_MESSAGE_DUMP is defined
# This is a particularly large file that still needs to be opened and read # This is a particularly large file that still needs to be opened and read
# all the way to the end even when ifdef'd out # all the way to the end even when ifdef'd out
@ -325,6 +334,11 @@ def FILTER_SOURCE_FILES() -> list[str]:
# HAS_PROTO_MESSAGE_DUMP is defined when ESPHOME_LOG_HAS_VERY_VERBOSE is set, # HAS_PROTO_MESSAGE_DUMP is defined when ESPHOME_LOG_HAS_VERY_VERBOSE is set,
# which happens when the logger level is VERY_VERBOSE # which happens when the logger level is VERY_VERBOSE
if get_logger_level() != "VERY_VERBOSE": if get_logger_level() != "VERY_VERBOSE":
return ["api_pb2_dump.cpp"] files_to_filter.append("api_pb2_dump.cpp")
return [] # user_services.cpp is only needed when services are defined
config = CORE.config.get(DOMAIN, {})
if config and not config.get(CONF_ACTIONS) and not config[CONF_CUSTOM_SERVICES]:
files_to_filter.append("user_services.cpp")
return files_to_filter

View File

@ -807,18 +807,21 @@ enum ServiceArgType {
SERVICE_ARG_TYPE_STRING_ARRAY = 7; SERVICE_ARG_TYPE_STRING_ARRAY = 7;
} }
message ListEntitiesServicesArgument { message ListEntitiesServicesArgument {
option (ifdef) = "USE_API_SERVICES";
string name = 1; string name = 1;
ServiceArgType type = 2; ServiceArgType type = 2;
} }
message ListEntitiesServicesResponse { message ListEntitiesServicesResponse {
option (id) = 41; option (id) = 41;
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_API_SERVICES";
string name = 1; string name = 1;
fixed32 key = 2; fixed32 key = 2;
repeated ListEntitiesServicesArgument args = 3; repeated ListEntitiesServicesArgument args = 3;
} }
message ExecuteServiceArgument { message ExecuteServiceArgument {
option (ifdef) = "USE_API_SERVICES";
bool bool_ = 1; bool bool_ = 1;
int32 legacy_int = 2; int32 legacy_int = 2;
float float_ = 3; float float_ = 3;
@ -834,6 +837,7 @@ message ExecuteServiceRequest {
option (id) = 42; option (id) = 42;
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (no_delay) = true; option (no_delay) = true;
option (ifdef) = "USE_API_SERVICES";
fixed32 key = 1; fixed32 key = 1;
repeated ExecuteServiceArgument args = 2; repeated ExecuteServiceArgument args = 2;

View File

@ -193,14 +193,15 @@ void APIConnection::loop() {
// If we can't send the ping request directly (tx_buffer full), // If we can't send the ping request directly (tx_buffer full),
// schedule it at the front of the batch so it will be sent with priority // schedule it at the front of the batch so it will be sent with priority
ESP_LOGW(TAG, "Buffer full, ping queued"); ESP_LOGW(TAG, "Buffer full, ping queued");
this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE); this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE,
PingRequest::ESTIMATED_SIZE);
this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings
} }
} }
#ifdef USE_CAMERA #ifdef USE_CAMERA
if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) { if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) {
uint32_t to_send = std::min((size_t) MAX_PACKET_SIZE, this->image_reader_->available()); uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
bool done = this->image_reader_->available() == to_send; bool done = this->image_reader_->available() == to_send;
uint32_t msg_size = 0; uint32_t msg_size = 0;
ProtoSize::add_fixed_field<4>(msg_size, 1, true); ProtoSize::add_fixed_field<4>(msg_size, 1, true);
@ -265,7 +266,7 @@ void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
// Encodes a message to the buffer and returns the total number of bytes used, // Encodes a message to the buffer and returns the total number of bytes used,
// including header and footer overhead. Returns 0 if the message doesn't fit. // including header and footer overhead. Returns 0 if the message doesn't fit.
uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn, uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
uint32_t remaining_size, bool is_single) { uint32_t remaining_size, bool is_single) {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
// If in log-only mode, just log and return // If in log-only mode, just log and return
@ -316,7 +317,7 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t mes
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) { bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) {
return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state, return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state,
BinarySensorStateResponse::MESSAGE_TYPE); BinarySensorStateResponse::MESSAGE_TYPE, BinarySensorStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@ -343,7 +344,8 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne
#ifdef USE_COVER #ifdef USE_COVER
bool APIConnection::send_cover_state(cover::Cover *cover) { bool APIConnection::send_cover_state(cover::Cover *cover) {
return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE); return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE,
CoverStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@ -400,7 +402,8 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) {
#ifdef USE_FAN #ifdef USE_FAN
bool APIConnection::send_fan_state(fan::Fan *fan) { bool APIConnection::send_fan_state(fan::Fan *fan) {
return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE); return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE,
FanStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@ -455,7 +458,8 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
#ifdef USE_LIGHT #ifdef USE_LIGHT
bool APIConnection::send_light_state(light::LightState *light) { bool APIConnection::send_light_state(light::LightState *light) {
return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE); return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE,
LightStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@ -543,7 +547,8 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
#ifdef USE_SENSOR #ifdef USE_SENSOR
bool APIConnection::send_sensor_state(sensor::Sensor *sensor) { bool APIConnection::send_sensor_state(sensor::Sensor *sensor) {
return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE); return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE,
SensorStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@ -575,7 +580,8 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *
#ifdef USE_SWITCH #ifdef USE_SWITCH
bool APIConnection::send_switch_state(switch_::Switch *a_switch) { bool APIConnection::send_switch_state(switch_::Switch *a_switch) {
return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE); return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE,
SwitchStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@ -611,7 +617,7 @@ void APIConnection::switch_command(const SwitchCommandRequest &msg) {
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) { bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) {
return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state, return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state,
TextSensorStateResponse::MESSAGE_TYPE); TextSensorStateResponse::MESSAGE_TYPE, TextSensorStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@ -638,7 +644,8 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
bool APIConnection::send_climate_state(climate::Climate *climate) { bool APIConnection::send_climate_state(climate::Climate *climate) {
return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE); return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE,
ClimateStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@ -734,7 +741,8 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
#ifdef USE_NUMBER #ifdef USE_NUMBER
bool APIConnection::send_number_state(number::Number *number) { bool APIConnection::send_number_state(number::Number *number) {
return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE); return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE,
NumberStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@ -770,7 +778,8 @@ void APIConnection::number_command(const NumberCommandRequest &msg) {
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
bool APIConnection::send_date_state(datetime::DateEntity *date) { bool APIConnection::send_date_state(datetime::DateEntity *date) {
return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE); return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE,
DateStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@ -800,7 +809,8 @@ void APIConnection::date_command(const DateCommandRequest &msg) {
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
bool APIConnection::send_time_state(datetime::TimeEntity *time) { bool APIConnection::send_time_state(datetime::TimeEntity *time) {
return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE); return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE,
TimeStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@ -831,7 +841,7 @@ void APIConnection::time_command(const TimeCommandRequest &msg) {
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) { bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state, return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state,
DateTimeStateResponse::MESSAGE_TYPE); DateTimeStateResponse::MESSAGE_TYPE, DateTimeStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@ -862,7 +872,8 @@ void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
#ifdef USE_TEXT #ifdef USE_TEXT
bool APIConnection::send_text_state(text::Text *text) { bool APIConnection::send_text_state(text::Text *text) {
return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE); return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE,
TextStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@ -896,7 +907,8 @@ void APIConnection::text_command(const TextCommandRequest &msg) {
#ifdef USE_SELECT #ifdef USE_SELECT
bool APIConnection::send_select_state(select::Select *select) { bool APIConnection::send_select_state(select::Select *select) {
return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE); return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE,
SelectStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@ -944,7 +956,8 @@ void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg
#ifdef USE_LOCK #ifdef USE_LOCK
bool APIConnection::send_lock_state(lock::Lock *a_lock) { bool APIConnection::send_lock_state(lock::Lock *a_lock) {
return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE); return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE,
LockStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@ -986,7 +999,8 @@ void APIConnection::lock_command(const LockCommandRequest &msg) {
#ifdef USE_VALVE #ifdef USE_VALVE
bool APIConnection::send_valve_state(valve::Valve *valve) { bool APIConnection::send_valve_state(valve::Valve *valve) {
return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE); return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE,
ValveStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@ -1023,7 +1037,7 @@ void APIConnection::valve_command(const ValveCommandRequest &msg) {
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) { bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) {
return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state, return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state,
MediaPlayerStateResponse::MESSAGE_TYPE); MediaPlayerStateResponse::MESSAGE_TYPE, MediaPlayerStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@ -1262,7 +1276,8 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state, return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state,
AlarmControlPanelStateResponse::MESSAGE_TYPE); AlarmControlPanelStateResponse::MESSAGE_TYPE,
AlarmControlPanelStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn, uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn,
uint32_t remaining_size, bool is_single) { uint32_t remaining_size, bool is_single) {
@ -1316,7 +1331,8 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
#ifdef USE_EVENT #ifdef USE_EVENT
void APIConnection::send_event(event::Event *event, const std::string &event_type) { void APIConnection::send_event(event::Event *event, const std::string &event_type) {
this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE); this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
EventResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn, uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
uint32_t remaining_size, bool is_single) { uint32_t remaining_size, bool is_single) {
@ -1341,7 +1357,8 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
#ifdef USE_UPDATE #ifdef USE_UPDATE
bool APIConnection::send_update_state(update::UpdateEntity *update) { bool APIConnection::send_update_state(update::UpdateEntity *update) {
return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE); return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE,
UpdateStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@ -1534,6 +1551,7 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
} }
} }
} }
#ifdef USE_API_SERVICES
void APIConnection::execute_service(const ExecuteServiceRequest &msg) { void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
bool found = false; bool found = false;
for (auto *service : this->parent_->get_user_services()) { for (auto *service : this->parent_->get_user_services()) {
@ -1545,6 +1563,7 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
ESP_LOGV(TAG, "Could not find service"); ESP_LOGV(TAG, "Could not find service");
} }
} }
#endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
NoiseEncryptionSetKeyResponse APIConnection::noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) { NoiseEncryptionSetKeyResponse APIConnection::noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) {
psk_t psk{}; psk_t psk{};
@ -1588,7 +1607,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
} }
return false; return false;
} }
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) { bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse
return false; return false;
} }
@ -1622,7 +1641,8 @@ void APIConnection::on_fatal_error() {
this->flags_.remove = true; this->flags_.remove = true;
} }
void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type) { void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type,
uint8_t estimated_size) {
// Check if we already have a message of this type for this entity // Check if we already have a message of this type for this entity
// This provides deduplication per entity/message_type combination // This provides deduplication per entity/message_type combination
// O(n) but optimized for RAM and not performance. // O(n) but optimized for RAM and not performance.
@ -1637,12 +1657,13 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c
} }
// No existing item found, add new one // No existing item found, add new one
items.emplace_back(entity, std::move(creator), message_type); items.emplace_back(entity, std::move(creator), message_type, estimated_size);
} }
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type) { void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type,
uint8_t estimated_size) {
// Insert at front for high priority messages (no deduplication check) // Insert at front for high priority messages (no deduplication check)
items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type)); items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type, estimated_size));
} }
bool APIConnection::schedule_batch_() { bool APIConnection::schedule_batch_() {
@ -1714,7 +1735,7 @@ void APIConnection::process_batch_() {
uint32_t total_estimated_size = 0; uint32_t total_estimated_size = 0;
for (size_t i = 0; i < this->deferred_batch_.size(); i++) { for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
const auto &item = this->deferred_batch_[i]; const auto &item = this->deferred_batch_[i];
total_estimated_size += get_estimated_message_size(item.message_type); total_estimated_size += item.estimated_size;
} }
// Calculate total overhead for all messages // Calculate total overhead for all messages
@ -1752,9 +1773,9 @@ void APIConnection::process_batch_() {
// Update tracking variables // Update tracking variables
items_processed++; items_processed++;
// After first message, set remaining size to MAX_PACKET_SIZE to avoid fragmentation // After first message, set remaining size to MAX_BATCH_PACKET_SIZE to avoid fragmentation
if (items_processed == 1) { if (items_processed == 1) {
remaining_size = MAX_PACKET_SIZE; remaining_size = MAX_BATCH_PACKET_SIZE;
} }
remaining_size -= payload_size; remaining_size -= payload_size;
// Calculate where the next message's header padding will start // Calculate where the next message's header padding will start
@ -1808,7 +1829,7 @@ void APIConnection::process_batch_() {
} }
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single, uint16_t message_type) const { bool is_single, uint8_t message_type) const {
#ifdef USE_EVENT #ifdef USE_EVENT
// Special case: EventResponse uses string pointer // Special case: EventResponse uses string pointer
if (message_type == EventResponse::MESSAGE_TYPE) { if (message_type == EventResponse::MESSAGE_TYPE) {
@ -1839,149 +1860,6 @@ uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection
return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
uint16_t APIConnection::get_estimated_message_size(uint16_t message_type) {
// Use generated ESTIMATED_SIZE constants from each message type
switch (message_type) {
#ifdef USE_BINARY_SENSOR
case BinarySensorStateResponse::MESSAGE_TYPE:
return BinarySensorStateResponse::ESTIMATED_SIZE;
case ListEntitiesBinarySensorResponse::MESSAGE_TYPE:
return ListEntitiesBinarySensorResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_SENSOR
case SensorStateResponse::MESSAGE_TYPE:
return SensorStateResponse::ESTIMATED_SIZE;
case ListEntitiesSensorResponse::MESSAGE_TYPE:
return ListEntitiesSensorResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_SWITCH
case SwitchStateResponse::MESSAGE_TYPE:
return SwitchStateResponse::ESTIMATED_SIZE;
case ListEntitiesSwitchResponse::MESSAGE_TYPE:
return ListEntitiesSwitchResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_TEXT_SENSOR
case TextSensorStateResponse::MESSAGE_TYPE:
return TextSensorStateResponse::ESTIMATED_SIZE;
case ListEntitiesTextSensorResponse::MESSAGE_TYPE:
return ListEntitiesTextSensorResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_NUMBER
case NumberStateResponse::MESSAGE_TYPE:
return NumberStateResponse::ESTIMATED_SIZE;
case ListEntitiesNumberResponse::MESSAGE_TYPE:
return ListEntitiesNumberResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_TEXT
case TextStateResponse::MESSAGE_TYPE:
return TextStateResponse::ESTIMATED_SIZE;
case ListEntitiesTextResponse::MESSAGE_TYPE:
return ListEntitiesTextResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_SELECT
case SelectStateResponse::MESSAGE_TYPE:
return SelectStateResponse::ESTIMATED_SIZE;
case ListEntitiesSelectResponse::MESSAGE_TYPE:
return ListEntitiesSelectResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_LOCK
case LockStateResponse::MESSAGE_TYPE:
return LockStateResponse::ESTIMATED_SIZE;
case ListEntitiesLockResponse::MESSAGE_TYPE:
return ListEntitiesLockResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_EVENT
case EventResponse::MESSAGE_TYPE:
return EventResponse::ESTIMATED_SIZE;
case ListEntitiesEventResponse::MESSAGE_TYPE:
return ListEntitiesEventResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_COVER
case CoverStateResponse::MESSAGE_TYPE:
return CoverStateResponse::ESTIMATED_SIZE;
case ListEntitiesCoverResponse::MESSAGE_TYPE:
return ListEntitiesCoverResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_FAN
case FanStateResponse::MESSAGE_TYPE:
return FanStateResponse::ESTIMATED_SIZE;
case ListEntitiesFanResponse::MESSAGE_TYPE:
return ListEntitiesFanResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_LIGHT
case LightStateResponse::MESSAGE_TYPE:
return LightStateResponse::ESTIMATED_SIZE;
case ListEntitiesLightResponse::MESSAGE_TYPE:
return ListEntitiesLightResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_CLIMATE
case ClimateStateResponse::MESSAGE_TYPE:
return ClimateStateResponse::ESTIMATED_SIZE;
case ListEntitiesClimateResponse::MESSAGE_TYPE:
return ListEntitiesClimateResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_CAMERA
case ListEntitiesCameraResponse::MESSAGE_TYPE:
return ListEntitiesCameraResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_BUTTON
case ListEntitiesButtonResponse::MESSAGE_TYPE:
return ListEntitiesButtonResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_MEDIA_PLAYER
case MediaPlayerStateResponse::MESSAGE_TYPE:
return MediaPlayerStateResponse::ESTIMATED_SIZE;
case ListEntitiesMediaPlayerResponse::MESSAGE_TYPE:
return ListEntitiesMediaPlayerResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
case AlarmControlPanelStateResponse::MESSAGE_TYPE:
return AlarmControlPanelStateResponse::ESTIMATED_SIZE;
case ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE:
return ListEntitiesAlarmControlPanelResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_DATETIME_DATE
case DateStateResponse::MESSAGE_TYPE:
return DateStateResponse::ESTIMATED_SIZE;
case ListEntitiesDateResponse::MESSAGE_TYPE:
return ListEntitiesDateResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_DATETIME_TIME
case TimeStateResponse::MESSAGE_TYPE:
return TimeStateResponse::ESTIMATED_SIZE;
case ListEntitiesTimeResponse::MESSAGE_TYPE:
return ListEntitiesTimeResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_DATETIME_DATETIME
case DateTimeStateResponse::MESSAGE_TYPE:
return DateTimeStateResponse::ESTIMATED_SIZE;
case ListEntitiesDateTimeResponse::MESSAGE_TYPE:
return ListEntitiesDateTimeResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_VALVE
case ValveStateResponse::MESSAGE_TYPE:
return ValveStateResponse::ESTIMATED_SIZE;
case ListEntitiesValveResponse::MESSAGE_TYPE:
return ListEntitiesValveResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_UPDATE
case UpdateStateResponse::MESSAGE_TYPE:
return UpdateStateResponse::ESTIMATED_SIZE;
case ListEntitiesUpdateResponse::MESSAGE_TYPE:
return ListEntitiesUpdateResponse::ESTIMATED_SIZE;
#endif
case ListEntitiesServicesResponse::MESSAGE_TYPE:
return ListEntitiesServicesResponse::ESTIMATED_SIZE;
case ListEntitiesDoneResponse::MESSAGE_TYPE:
return ListEntitiesDoneResponse::ESTIMATED_SIZE;
case DisconnectRequest::MESSAGE_TYPE:
return DisconnectRequest::ESTIMATED_SIZE;
default:
// Fallback for unknown message types
return 24;
}
}
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif #endif

View File

@ -33,7 +33,7 @@ class APIConnection : public APIServerConnection {
bool send_list_info_done() { bool send_list_info_done() {
return this->schedule_message_(nullptr, &APIConnection::try_send_list_info_done, return this->schedule_message_(nullptr, &APIConnection::try_send_list_info_done,
ListEntitiesDoneResponse::MESSAGE_TYPE); ListEntitiesDoneResponse::MESSAGE_TYPE, ListEntitiesDoneResponse::ESTIMATED_SIZE);
} }
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor); bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor);
@ -195,7 +195,9 @@ class APIConnection : public APIServerConnection {
// TODO // TODO
return {}; return {};
} }
#ifdef USE_API_SERVICES
void execute_service(const ExecuteServiceRequest &msg) override; void execute_service(const ExecuteServiceRequest &msg) override;
#endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override; NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override;
#endif #endif
@ -256,7 +258,7 @@ class APIConnection : public APIServerConnection {
} }
bool try_to_clear_buffer(bool log_out_of_space); bool try_to_clear_buffer(bool log_out_of_space);
bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) override; bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
std::string get_client_combined_info() const { std::string get_client_combined_info() const {
if (this->client_info_ == this->client_peername_) { if (this->client_info_ == this->client_peername_) {
@ -298,7 +300,7 @@ class APIConnection : public APIServerConnection {
} }
// Non-template helper to encode any ProtoMessage // Non-template helper to encode any ProtoMessage
static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn, static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
uint32_t remaining_size, bool is_single); uint32_t remaining_size, bool is_single);
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
@ -443,9 +445,6 @@ class APIConnection : public APIServerConnection {
static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single); bool is_single);
// Helper function to get estimated message size for buffer pre-allocation
static uint16_t get_estimated_message_size(uint16_t message_type);
// Batch message method for ping requests // Batch message method for ping requests
static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single); bool is_single);
@ -505,10 +504,10 @@ class APIConnection : public APIServerConnection {
// Call operator - uses message_type to determine union type // Call operator - uses message_type to determine union type
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
uint16_t message_type) const; uint8_t message_type) const;
// Manual cleanup method - must be called before destruction for string types // Manual cleanup method - must be called before destruction for string types
void cleanup(uint16_t message_type) { void cleanup(uint8_t message_type) {
#ifdef USE_EVENT #ifdef USE_EVENT
if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) { if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) {
delete data_.string_ptr; delete data_.string_ptr;
@ -529,11 +528,12 @@ class APIConnection : public APIServerConnection {
struct BatchItem { struct BatchItem {
EntityBase *entity; // Entity pointer EntityBase *entity; // Entity pointer
MessageCreator creator; // Function that creates the message when needed MessageCreator creator; // Function that creates the message when needed
uint16_t message_type; // Message type for overhead calculation uint8_t message_type; // Message type for overhead calculation (max 255)
uint8_t estimated_size; // Estimated message size (max 255 bytes)
// Constructor for creating BatchItem // Constructor for creating BatchItem
BatchItem(EntityBase *entity, MessageCreator creator, uint16_t message_type) BatchItem(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size)
: entity(entity), creator(std::move(creator)), message_type(message_type) {} : entity(entity), creator(std::move(creator)), message_type(message_type), estimated_size(estimated_size) {}
}; };
std::vector<BatchItem> items; std::vector<BatchItem> items;
@ -559,9 +559,9 @@ class APIConnection : public APIServerConnection {
} }
// Add item to the batch // Add item to the batch
void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type); void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
// Add item to the front of the batch (for high priority messages like ping) // Add item to the front of the batch (for high priority messages like ping)
void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type); void add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
// Clear all items with proper cleanup // Clear all items with proper cleanup
void clear() { void clear() {
@ -630,7 +630,7 @@ class APIConnection : public APIServerConnection {
// to send in one go. This is the maximum size of a single packet // to send in one go. This is the maximum size of a single packet
// that can be sent over the network. // that can be sent over the network.
// This is to avoid fragmentation of the packet. // This is to avoid fragmentation of the packet.
static constexpr size_t MAX_PACKET_SIZE = 1390; // MTU static constexpr size_t MAX_BATCH_PACKET_SIZE = 1390; // MTU
bool schedule_batch_(); bool schedule_batch_();
void process_batch_(); void process_batch_();
@ -641,9 +641,9 @@ class APIConnection : public APIServerConnection {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
// Helper to log a proto message from a MessageCreator object // Helper to log a proto message from a MessageCreator object
void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint16_t message_type) { void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint8_t message_type) {
this->flags_.log_only_mode = true; this->flags_.log_only_mode = true;
creator(entity, this, MAX_PACKET_SIZE, true, message_type); creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type);
this->flags_.log_only_mode = false; this->flags_.log_only_mode = false;
} }
@ -654,7 +654,8 @@ class APIConnection : public APIServerConnection {
#endif #endif
// Helper method to send a message either immediately or via batching // Helper method to send a message either immediately or via batching
bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint16_t message_type) { bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type,
uint8_t estimated_size) {
// Try to send immediately if: // Try to send immediately if:
// 1. We should try to send immediately (should_try_send_immediately = true) // 1. We should try to send immediately (should_try_send_immediately = true)
// 2. Batch delay is 0 (user has opted in to immediate sending) // 2. Batch delay is 0 (user has opted in to immediate sending)
@ -662,7 +663,7 @@ class APIConnection : public APIServerConnection {
if (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0 && if (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0 &&
this->helper_->can_write_without_blocking()) { this->helper_->can_write_without_blocking()) {
// Now actually encode and send // Now actually encode and send
if (creator(entity, this, MAX_PACKET_SIZE, true) && if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) &&
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) { this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
// Log the message in verbose mode // Log the message in verbose mode
@ -675,23 +676,25 @@ class APIConnection : public APIServerConnection {
} }
// Fall back to scheduled batching // Fall back to scheduled batching
return this->schedule_message_(entity, creator, message_type); return this->schedule_message_(entity, creator, message_type, estimated_size);
} }
// Helper function to schedule a deferred message with known message type // Helper function to schedule a deferred message with known message type
bool schedule_message_(EntityBase *entity, MessageCreator creator, uint16_t message_type) { bool schedule_message_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) {
this->deferred_batch_.add_item(entity, std::move(creator), message_type); this->deferred_batch_.add_item(entity, std::move(creator), message_type, estimated_size);
return this->schedule_batch_(); return this->schedule_batch_();
} }
// Overload for function pointers (for info messages and current state reads) // Overload for function pointers (for info messages and current state reads)
bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) { bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type,
return schedule_message_(entity, MessageCreator(function_ptr), message_type); uint8_t estimated_size) {
return schedule_message_(entity, MessageCreator(function_ptr), message_type, estimated_size);
} }
// Helper function to schedule a high priority message at the front of the batch // Helper function to schedule a high priority message at the front of the batch
bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) { bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type,
this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type); uint8_t estimated_size) {
this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type, estimated_size);
return this->schedule_batch_(); return this->schedule_batch_();
} }
}; };

View File

@ -5,7 +5,6 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "proto.h" #include "proto.h"
#include "api_pb2_size.h"
#include <cstring> #include <cstring>
#include <cinttypes> #include <cinttypes>
@ -613,7 +612,7 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
buffer->type = type; buffer->type = type;
return APIError::OK; return APIError::OK;
} }
APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) { APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
// Resize to include MAC space (required for Noise encryption) // Resize to include MAC space (required for Noise encryption)
buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_); buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_);
PacketInfo packet{type, 0, PacketInfo packet{type, 0,
@ -1002,7 +1001,7 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
buffer->type = rx_header_parsed_type_; buffer->type = rx_header_parsed_type_;
return APIError::OK; return APIError::OK;
} }
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) { APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
PacketInfo packet{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_)}; PacketInfo packet{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_)};
return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1)); return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
} }

View File

@ -30,13 +30,11 @@ struct ReadPacketBuffer {
// Packed packet info structure to minimize memory usage // Packed packet info structure to minimize memory usage
struct PacketInfo { struct PacketInfo {
uint16_t message_type; // 2 bytes uint16_t offset; // Offset in buffer where message starts
uint16_t offset; // 2 bytes (sufficient for packet size ~1460 bytes) uint16_t payload_size; // Size of the message payload
uint16_t payload_size; // 2 bytes (up to 65535 bytes) uint8_t message_type; // Message type (0-255)
uint16_t padding; // 2 byte (for alignment)
PacketInfo(uint16_t type, uint16_t off, uint16_t size) PacketInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {}
: message_type(type), offset(off), payload_size(size), padding(0) {}
}; };
enum class APIError : uint16_t { enum class APIError : uint16_t {
@ -98,7 +96,7 @@ class APIFrameHelper {
} }
// Give this helper a name for logging // Give this helper a name for logging
void set_log_info(std::string info) { info_ = std::move(info); } void set_log_info(std::string info) { info_ = std::move(info); }
virtual APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) = 0; virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0;
// Write multiple protobuf packets in a single operation // Write multiple protobuf packets in a single operation
// packets contains (message_type, offset, length) for each message in the buffer // packets contains (message_type, offset, length) for each message in the buffer
// The buffer contains all messages with appropriate padding before each // The buffer contains all messages with appropriate padding before each
@ -197,7 +195,7 @@ class APINoiseFrameHelper : public APIFrameHelper {
APIError init() override; APIError init() override;
APIError loop() override; APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override; APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override; APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override; APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
// Get the frame header padding required by this protocol // Get the frame header padding required by this protocol
uint8_t frame_header_padding() override { return frame_header_padding_; } uint8_t frame_header_padding() override { return frame_header_padding_; }
@ -251,7 +249,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
APIError init() override; APIError init() override;
APIError loop() override; APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override; APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override; APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override; APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
uint8_t frame_header_padding() override { return frame_header_padding_; } uint8_t frame_header_padding() override { return frame_header_padding_; }
// Get the frame footer size required by this protocol // Get the frame footer size required by this protocol

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -162,6 +162,7 @@ template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel val
return "UNKNOWN"; return "UNKNOWN";
} }
} }
#ifdef USE_API_SERVICES
template<> const char *proto_enum_to_string<enums::ServiceArgType>(enums::ServiceArgType value) { template<> const char *proto_enum_to_string<enums::ServiceArgType>(enums::ServiceArgType value) {
switch (value) { switch (value) {
case enums::SERVICE_ARG_TYPE_BOOL: case enums::SERVICE_ARG_TYPE_BOOL:
@ -184,6 +185,7 @@ template<> const char *proto_enum_to_string<enums::ServiceArgType>(enums::Servic
return "UNKNOWN"; return "UNKNOWN";
} }
} }
#endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMode value) { template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMode value) {
switch (value) { switch (value) {
@ -1811,6 +1813,7 @@ void GetTimeResponse::dump_to(std::string &out) const {
out.append("\n"); out.append("\n");
out.append("}"); out.append("}");
} }
#ifdef USE_API_SERVICES
void ListEntitiesServicesArgument::dump_to(std::string &out) const { void ListEntitiesServicesArgument::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
out.append("ListEntitiesServicesArgument {\n"); out.append("ListEntitiesServicesArgument {\n");
@ -1910,6 +1913,7 @@ void ExecuteServiceRequest::dump_to(std::string &out) const {
} }
out.append("}"); out.append("}");
} }
#endif
#ifdef USE_CAMERA #ifdef USE_CAMERA
void ListEntitiesCameraResponse::dump_to(std::string &out) const { void ListEntitiesCameraResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];

View File

@ -195,6 +195,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
this->on_home_assistant_state_response(msg); this->on_home_assistant_state_response(msg);
break; break;
} }
#ifdef USE_API_SERVICES
case 42: { case 42: {
ExecuteServiceRequest msg; ExecuteServiceRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
@ -204,6 +205,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
this->on_execute_service_request(msg); this->on_execute_service_request(msg);
break; break;
} }
#endif
#ifdef USE_CAMERA #ifdef USE_CAMERA
case 45: { case 45: {
CameraImageRequest msg; CameraImageRequest msg;
@ -660,11 +662,13 @@ void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
} }
} }
} }
#ifdef USE_API_SERVICES
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) {
if (this->check_authenticated_()) { if (this->check_authenticated_()) {
this->execute_service(msg); this->execute_service(msg);
} }
} }
#endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) { void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
if (this->check_authenticated_()) { if (this->check_authenticated_()) {

View File

@ -69,7 +69,9 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_get_time_request(const GetTimeRequest &value){}; virtual void on_get_time_request(const GetTimeRequest &value){};
virtual void on_get_time_response(const GetTimeResponse &value){}; virtual void on_get_time_response(const GetTimeResponse &value){};
#ifdef USE_API_SERVICES
virtual void on_execute_service_request(const ExecuteServiceRequest &value){}; virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
#endif
#ifdef USE_CAMERA #ifdef USE_CAMERA
virtual void on_camera_image_request(const CameraImageRequest &value){}; virtual void on_camera_image_request(const CameraImageRequest &value){};
@ -216,7 +218,9 @@ class APIServerConnection : public APIServerConnectionBase {
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0; virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0; virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0; virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
#ifdef USE_API_SERVICES
virtual void execute_service(const ExecuteServiceRequest &msg) = 0; virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
#endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0; virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0;
#endif #endif
@ -333,7 +337,9 @@ class APIServerConnection : public APIServerConnectionBase {
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override; void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override; void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
void on_get_time_request(const GetTimeRequest &msg) override; void on_get_time_request(const GetTimeRequest &msg) override;
#ifdef USE_API_SERVICES
void on_execute_service_request(const ExecuteServiceRequest &msg) override; void on_execute_service_request(const ExecuteServiceRequest &msg) override;
#endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override; void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
#endif #endif

View File

@ -1,359 +0,0 @@
#pragma once
#include "proto.h"
#include <cstdint>
#include <string>
namespace esphome {
namespace api {
class ProtoSize {
public:
/**
* @brief ProtoSize class for Protocol Buffer serialization size calculation
*
* This class provides static methods to calculate the exact byte counts needed
* for encoding various Protocol Buffer field types. All methods are designed to be
* efficient for the common case where many fields have default values.
*
* Implements Protocol Buffer encoding size calculation according to:
* https://protobuf.dev/programming-guides/encoding/
*
* Key features:
* - Early-return optimization for zero/default values
* - Direct total_size updates to avoid unnecessary additions
* - Specialized handling for different field types according to protobuf spec
* - Templated helpers for repeated fields and messages
*/
/**
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
*
* @param value The uint32_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static inline uint32_t varint(uint32_t value) {
// Optimized varint size calculation using leading zeros
// Each 7 bits requires one byte in the varint encoding
if (value < 128)
return 1; // 7 bits, common case for small values
// For larger values, count bytes needed based on the position of the highest bit set
if (value < 16384) {
return 2; // 14 bits
} else if (value < 2097152) {
return 3; // 21 bits
} else if (value < 268435456) {
return 4; // 28 bits
} else {
return 5; // 32 bits (maximum for uint32_t)
}
}
/**
* @brief Calculates the size in bytes needed to encode a uint64_t value as a varint
*
* @param value The uint64_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static inline uint32_t varint(uint64_t value) {
// Handle common case of values fitting in uint32_t (vast majority of use cases)
if (value <= UINT32_MAX) {
return varint(static_cast<uint32_t>(value));
}
// For larger values, determine size based on highest bit position
if (value < (1ULL << 35)) {
return 5; // 35 bits
} else if (value < (1ULL << 42)) {
return 6; // 42 bits
} else if (value < (1ULL << 49)) {
return 7; // 49 bits
} else if (value < (1ULL << 56)) {
return 8; // 56 bits
} else if (value < (1ULL << 63)) {
return 9; // 63 bits
} else {
return 10; // 64 bits (maximum for uint64_t)
}
}
/**
* @brief Calculates the size in bytes needed to encode an int32_t value as a varint
*
* Special handling is needed for negative values, which are sign-extended to 64 bits
* in Protocol Buffers, resulting in a 10-byte varint.
*
* @param value The int32_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static inline uint32_t varint(int32_t value) {
// Negative values are sign-extended to 64 bits in protocol buffers,
// which always results in a 10-byte varint for negative int32
if (value < 0) {
return 10; // Negative int32 is always 10 bytes long
}
// For non-negative values, use the uint32_t implementation
return varint(static_cast<uint32_t>(value));
}
/**
* @brief Calculates the size in bytes needed to encode an int64_t value as a varint
*
* @param value The int64_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static inline uint32_t varint(int64_t value) {
// For int64_t, we convert to uint64_t and calculate the size
// This works because the bit pattern determines the encoding size,
// and we've handled negative int32 values as a special case above
return varint(static_cast<uint64_t>(value));
}
/**
* @brief Calculates the size in bytes needed to encode a field ID and wire type
*
* @param field_id The field identifier
* @param type The wire type value (from the WireType enum in the protobuf spec)
* @return The number of bytes needed to encode the field ID and wire type
*/
static inline uint32_t field(uint32_t field_id, uint32_t type) {
uint32_t tag = (field_id << 3) | (type & 0b111);
return varint(tag);
}
/**
* @brief Common parameters for all add_*_field methods
*
* All add_*_field methods follow these common patterns:
*
* @param total_size Reference to the total message size to update
* @param field_id_size Pre-calculated size of the field ID in bytes
* @param value The value to calculate size for (type varies)
* @param force Whether to calculate size even if the value is default/zero/empty
*
* Each method follows this implementation pattern:
* 1. Skip calculation if value is default (0, false, empty) and not forced
* 2. Calculate the size based on the field's encoding rules
* 3. Add the field_id_size + calculated value size to total_size
*/
/**
* @brief Calculates and adds the size of an int32 field to the total message size
*/
static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value, bool force = false) {
// Skip calculation if value is zero and not forced
if (value == 0 && !force) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
if (value < 0) {
// Negative values are encoded as 10-byte varints in protobuf
total_size += field_id_size + 10;
} else {
// For non-negative values, use the standard varint size
total_size += field_id_size + varint(static_cast<uint32_t>(value));
}
}
/**
* @brief Calculates and adds the size of a uint32 field to the total message size
*/
static inline void add_uint32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value,
bool force = false) {
// Skip calculation if value is zero and not forced
if (value == 0 && !force) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a boolean field to the total message size
*/
static inline void add_bool_field(uint32_t &total_size, uint32_t field_id_size, bool value, bool force = false) {
// Skip calculation if value is false and not forced
if (!value && !force) {
return; // No need to update total_size
}
// Boolean fields always use 1 byte when true
total_size += field_id_size + 1;
}
/**
* @brief Calculates and adds the size of a fixed field to the total message size
*
* Fixed fields always take exactly N bytes (4 for fixed32/float, 8 for fixed64/double).
*
* @tparam NumBytes The number of bytes for this fixed field (4 or 8)
* @param is_nonzero Whether the value is non-zero
*/
template<uint32_t NumBytes>
static inline void add_fixed_field(uint32_t &total_size, uint32_t field_id_size, bool is_nonzero,
bool force = false) {
// Skip calculation if value is zero and not forced
if (!is_nonzero && !force) {
return; // No need to update total_size
}
// Fixed fields always take exactly NumBytes
total_size += field_id_size + NumBytes;
}
/**
* @brief Calculates and adds the size of an enum field to the total message size
*
* Enum fields are encoded as uint32 varints.
*/
static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value, bool force = false) {
// Skip calculation if value is zero and not forced
if (value == 0 && !force) {
return; // No need to update total_size
}
// Enums are encoded as uint32
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a sint32 field to the total message size
*
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value, bool force = false) {
// Skip calculation if value is zero and not forced
if (value == 0 && !force) {
return; // No need to update total_size
}
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
total_size += field_id_size + varint(zigzag);
}
/**
* @brief Calculates and adds the size of an int64 field to the total message size
*/
static inline void add_int64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value, bool force = false) {
// Skip calculation if value is zero and not forced
if (value == 0 && !force) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a uint64 field to the total message size
*/
static inline void add_uint64_field(uint32_t &total_size, uint32_t field_id_size, uint64_t value,
bool force = false) {
// Skip calculation if value is zero and not forced
if (value == 0 && !force) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a sint64 field to the total message size
*
* Sint64 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value, bool force = false) {
// Skip calculation if value is zero and not forced
if (value == 0 && !force) {
return; // No need to update total_size
}
// ZigZag encoding for sint64: (n << 1) ^ (n >> 63)
uint64_t zigzag = (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
total_size += field_id_size + varint(zigzag);
}
/**
* @brief Calculates and adds the size of a string/bytes field to the total message size
*/
static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, const std::string &str,
bool force = false) {
// Skip calculation if string is empty and not forced
if (str.empty() && !force) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
const uint32_t str_size = static_cast<uint32_t>(str.size());
total_size += field_id_size + varint(str_size) + str_size;
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size
*
* This helper function directly updates the total_size reference if the nested size
* is greater than zero or force is true.
*
* @param nested_size The pre-calculated size of the nested message
*/
static inline void add_message_field(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size,
bool force = false) {
// Skip calculation if nested message is empty and not forced
if (nested_size == 0 && !force) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
// Field ID + length varint + nested message content
total_size += field_id_size + varint(nested_size) + nested_size;
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size
*
* This version takes a ProtoMessage object, calculates its size internally,
* and updates the total_size reference. This eliminates the need for a temporary variable
* at the call site.
*
* @param message The nested message object
*/
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message,
bool force = false) {
uint32_t nested_size = 0;
message.calculate_size(nested_size);
// Use the base implementation with the calculated nested_size
add_message_field(total_size, field_id_size, nested_size, force);
}
/**
* @brief Calculates and adds the sizes of all messages in a repeated field to the total message size
*
* This helper processes a vector of message objects, calculating the size for each message
* and adding it to the total size.
*
* @tparam MessageType The type of the nested messages in the vector
* @param messages Vector of message objects
*/
template<typename MessageType>
static inline void add_repeated_message(uint32_t &total_size, uint32_t field_id_size,
const std::vector<MessageType> &messages) {
// Skip if the vector is empty
if (messages.empty()) {
return;
}
// For repeated fields, always use force=true
for (const auto &message : messages) {
add_message_object(total_size, field_id_size, message, true);
}
}
};
} // namespace api
} // namespace esphome

View File

@ -24,14 +24,6 @@ static const char *const TAG = "api";
// APIServer // APIServer
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
#ifndef USE_API_YAML_SERVICES
// Global empty vector to avoid guard variables (saves 8 bytes)
// This is initialized at program startup before any threads
static const std::vector<UserServiceDescriptor *> empty_user_services{};
const std::vector<UserServiceDescriptor *> &get_empty_user_services_instance() { return empty_user_services; }
#endif
APIServer::APIServer() { APIServer::APIServer() {
global_api_server = this; global_api_server = this;
// Pre-allocate shared write buffer // Pre-allocate shared write buffer
@ -475,7 +467,8 @@ void APIServer::on_shutdown() {
if (!c->send_message(DisconnectRequest())) { if (!c->send_message(DisconnectRequest())) {
// If we can't send the disconnect request directly (tx_buffer full), // If we can't send the disconnect request directly (tx_buffer full),
// schedule it at the front of the batch so it will be sent with priority // schedule it at the front of the batch so it will be sent with priority
c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE); c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE,
DisconnectRequest::ESTIMATED_SIZE);
} }
} }
} }

View File

@ -12,7 +12,9 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "list_entities.h" #include "list_entities.h"
#include "subscribe_state.h" #include "subscribe_state.h"
#ifdef USE_API_SERVICES
#include "user_services.h" #include "user_services.h"
#endif
#include <vector> #include <vector>
@ -25,11 +27,6 @@ struct SavedNoisePsk {
} PACKED; // NOLINT } PACKED; // NOLINT
#endif #endif
#ifndef USE_API_YAML_SERVICES
// Forward declaration of helper function
const std::vector<UserServiceDescriptor *> &get_empty_user_services_instance();
#endif
class APIServer : public Component, public Controller { class APIServer : public Component, public Controller {
public: public:
APIServer(); APIServer();
@ -112,18 +109,9 @@ class APIServer : public Component, public Controller {
void on_media_player_update(media_player::MediaPlayer *obj) override; void on_media_player_update(media_player::MediaPlayer *obj) override;
#endif #endif
void send_homeassistant_service_call(const HomeassistantServiceResponse &call); void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
void register_user_service(UserServiceDescriptor *descriptor) { #ifdef USE_API_SERVICES
#ifdef USE_API_YAML_SERVICES void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
// Vector is pre-allocated when services are defined in YAML
this->user_services_.push_back(descriptor);
#else
// Lazy allocate vector on first use for CustomAPIDevice
if (!this->user_services_) {
this->user_services_ = std::make_unique<std::vector<UserServiceDescriptor *>>();
}
this->user_services_->push_back(descriptor);
#endif #endif
}
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
void request_time(); void request_time();
#endif #endif
@ -152,17 +140,9 @@ class APIServer : public Component, public Controller {
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute, void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f); std::function<void(std::string)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const; const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
const std::vector<UserServiceDescriptor *> &get_user_services() const { #ifdef USE_API_SERVICES
#ifdef USE_API_YAML_SERVICES const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
return this->user_services_;
#else
if (this->user_services_) {
return *this->user_services_;
}
// Return reference to global empty instance (no guard needed)
return get_empty_user_services_instance();
#endif #endif
}
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER #ifdef USE_API_CLIENT_CONNECTED_TRIGGER
Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->client_connected_trigger_; } Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->client_connected_trigger_; }
@ -194,14 +174,8 @@ class APIServer : public Component, public Controller {
#endif #endif
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
std::vector<HomeAssistantStateSubscription> state_subs_; std::vector<HomeAssistantStateSubscription> state_subs_;
#ifdef USE_API_YAML_SERVICES #ifdef USE_API_SERVICES
// When services are defined in YAML, we know at compile time that services will be registered
std::vector<UserServiceDescriptor *> user_services_; std::vector<UserServiceDescriptor *> user_services_;
#else
// Services can still be registered at runtime by CustomAPIDevice components even when not
// defined in YAML. Using unique_ptr allows lazy allocation, saving 12 bytes in the common
// case where no services (YAML or custom) are used.
std::unique_ptr<std::vector<UserServiceDescriptor *>> user_services_;
#endif #endif
// Group smaller types together // Group smaller types together

View File

@ -3,10 +3,13 @@
#include <map> #include <map>
#include "api_server.h" #include "api_server.h"
#ifdef USE_API #ifdef USE_API
#ifdef USE_API_SERVICES
#include "user_services.h" #include "user_services.h"
#endif
namespace esphome { namespace esphome {
namespace api { namespace api {
#ifdef USE_API_SERVICES
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> { template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
public: public:
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj, CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
@ -19,6 +22,7 @@ template<typename T, typename... Ts> class CustomAPIDeviceService : public UserS
T *obj_; T *obj_;
void (T::*callback_)(Ts...); void (T::*callback_)(Ts...);
}; };
#endif // USE_API_SERVICES
class CustomAPIDevice { class CustomAPIDevice {
public: public:
@ -46,12 +50,14 @@ class CustomAPIDevice {
* @param name The name of the service to register. * @param name The name of the service to register.
* @param arg_names The name of the arguments for the service, must match the arguments of the function. * @param arg_names The name of the arguments for the service, must match the arguments of the function.
*/ */
#ifdef USE_API_SERVICES
template<typename T, typename... Ts> template<typename T, typename... Ts>
void register_service(void (T::*callback)(Ts...), const std::string &name, void register_service(void (T::*callback)(Ts...), const std::string &name,
const std::array<std::string, sizeof...(Ts)> &arg_names) { const std::array<std::string, sizeof...(Ts)> &arg_names) {
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
global_api_server->register_user_service(service); global_api_server->register_user_service(service);
} }
#endif
/** Register a custom native API service that will show up in Home Assistant. /** Register a custom native API service that will show up in Home Assistant.
* *
@ -71,10 +77,12 @@ class CustomAPIDevice {
* @param callback The member function to call when the service is triggered. * @param callback The member function to call when the service is triggered.
* @param name The name of the arguments for the service, must match the arguments of the function. * @param name The name of the arguments for the service, must match the arguments of the function.
*/ */
#ifdef USE_API_SERVICES
template<typename T> void register_service(void (T::*callback)(), const std::string &name) { template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
global_api_server->register_user_service(service); global_api_server->register_user_service(service);
} }
#endif
/** Subscribe to the state (or attribute state) of an entity from Home Assistant. /** Subscribe to the state (or attribute state) of an entity from Home Assistant.
* *

View File

@ -83,10 +83,12 @@ bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(
ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {} ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {}
#ifdef USE_API_SERVICES
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
auto resp = service->encode_list_service_response(); auto resp = service->encode_list_service_response();
return this->client_->send_message(resp); return this->client_->send_message(resp);
} }
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View File

@ -14,7 +14,7 @@ class APIConnection;
#define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \ #define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \
bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \ bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \
return this->client_->schedule_message_(entity, &APIConnection::try_send_##entity_type##_info, \ return this->client_->schedule_message_(entity, &APIConnection::try_send_##entity_type##_info, \
ResponseType::MESSAGE_TYPE); \ ResponseType::MESSAGE_TYPE, ResponseType::ESTIMATED_SIZE); \
} }
class ListEntitiesIterator : public ComponentIterator { class ListEntitiesIterator : public ComponentIterator {
@ -44,7 +44,9 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *entity) override; bool on_text_sensor(text_sensor::TextSensor *entity) override;
#endif #endif
#ifdef USE_API_SERVICES
bool on_service(UserServiceDescriptor *service) override; bool on_service(UserServiceDescriptor *service) override;
#endif
#ifdef USE_CAMERA #ifdef USE_CAMERA
bool on_camera(camera::Camera *entity) override; bool on_camera(camera::Camera *entity) override;
#endif #endif

View File

@ -4,6 +4,7 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <cassert>
#include <vector> #include <vector>
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
@ -59,7 +60,6 @@ class ProtoVarInt {
uint32_t as_uint32() const { return this->value_; } uint32_t as_uint32() const { return this->value_; }
uint64_t as_uint64() const { return this->value_; } uint64_t as_uint64() const { return this->value_; }
bool as_bool() const { return this->value_; } bool as_bool() const { return this->value_; }
template<typename T> T as_enum() const { return static_cast<T>(this->as_uint32()); }
int32_t as_int32() const { int32_t as_int32() const {
// Not ZigZag encoded // Not ZigZag encoded
return static_cast<int32_t>(this->as_int64()); return static_cast<int32_t>(this->as_int64());
@ -133,15 +133,24 @@ class ProtoVarInt {
uint64_t value_; uint64_t value_;
}; };
// Forward declaration for decode_to_message and encode_to_writer
class ProtoMessage;
class ProtoLengthDelimited { class ProtoLengthDelimited {
public: public:
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {} explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); } std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
template<class C> C as_message() const {
auto msg = C(); /**
msg.decode(this->value_, this->length_); * Decode the length-delimited data into an existing ProtoMessage instance.
return msg; *
} * This method allows decoding without templates, enabling use in contexts
* where the message type is not known at compile time. The ProtoMessage's
* decode() method will be called with the raw data and length.
*
* @param msg The ProtoMessage instance to decode into
*/
void decode_to_message(ProtoMessage &msg) const;
protected: protected:
const uint8_t *const value_; const uint8_t *const value_;
@ -263,9 +272,6 @@ class ProtoWriteBuffer {
this->write((value >> 48) & 0xFF); this->write((value >> 48) & 0xFF);
this->write((value >> 56) & 0xFF); this->write((value >> 56) & 0xFF);
} }
template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) {
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
}
void encode_float(uint32_t field_id, float value, bool force = false) { void encode_float(uint32_t field_id, float value, bool force = false) {
if (value == 0.0f && !force) if (value == 0.0f && !force)
return; return;
@ -306,18 +312,7 @@ class ProtoWriteBuffer {
} }
this->encode_uint64(field_id, uvalue, force); this->encode_uint64(field_id, uvalue, force);
} }
template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) { void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = false);
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
size_t begin = this->buffer_->size();
value.encode(*this);
const uint32_t nested_length = this->buffer_->size() - begin;
// add size varint
std::vector<uint8_t> var;
ProtoVarInt(nested_length).encode(var);
this->buffer_->insert(this->buffer_->begin() + begin, var.begin(), var.end());
}
std::vector<uint8_t> *get_buffer() const { return buffer_; } std::vector<uint8_t> *get_buffer() const { return buffer_; }
protected: protected:
@ -345,6 +340,494 @@ class ProtoMessage {
virtual bool decode_64bit(uint32_t field_id, Proto64Bit value) { return false; } virtual bool decode_64bit(uint32_t field_id, Proto64Bit value) { return false; }
}; };
class ProtoSize {
public:
/**
* @brief ProtoSize class for Protocol Buffer serialization size calculation
*
* This class provides static methods to calculate the exact byte counts needed
* for encoding various Protocol Buffer field types. All methods are designed to be
* efficient for the common case where many fields have default values.
*
* Implements Protocol Buffer encoding size calculation according to:
* https://protobuf.dev/programming-guides/encoding/
*
* Key features:
* - Early-return optimization for zero/default values
* - Direct total_size updates to avoid unnecessary additions
* - Specialized handling for different field types according to protobuf spec
* - Templated helpers for repeated fields and messages
*/
/**
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
*
* @param value The uint32_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static inline uint32_t varint(uint32_t value) {
// Optimized varint size calculation using leading zeros
// Each 7 bits requires one byte in the varint encoding
if (value < 128)
return 1; // 7 bits, common case for small values
// For larger values, count bytes needed based on the position of the highest bit set
if (value < 16384) {
return 2; // 14 bits
} else if (value < 2097152) {
return 3; // 21 bits
} else if (value < 268435456) {
return 4; // 28 bits
} else {
return 5; // 32 bits (maximum for uint32_t)
}
}
/**
* @brief Calculates the size in bytes needed to encode a uint64_t value as a varint
*
* @param value The uint64_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static inline uint32_t varint(uint64_t value) {
// Handle common case of values fitting in uint32_t (vast majority of use cases)
if (value <= UINT32_MAX) {
return varint(static_cast<uint32_t>(value));
}
// For larger values, determine size based on highest bit position
if (value < (1ULL << 35)) {
return 5; // 35 bits
} else if (value < (1ULL << 42)) {
return 6; // 42 bits
} else if (value < (1ULL << 49)) {
return 7; // 49 bits
} else if (value < (1ULL << 56)) {
return 8; // 56 bits
} else if (value < (1ULL << 63)) {
return 9; // 63 bits
} else {
return 10; // 64 bits (maximum for uint64_t)
}
}
/**
* @brief Calculates the size in bytes needed to encode an int32_t value as a varint
*
* Special handling is needed for negative values, which are sign-extended to 64 bits
* in Protocol Buffers, resulting in a 10-byte varint.
*
* @param value The int32_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static inline uint32_t varint(int32_t value) {
// Negative values are sign-extended to 64 bits in protocol buffers,
// which always results in a 10-byte varint for negative int32
if (value < 0) {
return 10; // Negative int32 is always 10 bytes long
}
// For non-negative values, use the uint32_t implementation
return varint(static_cast<uint32_t>(value));
}
/**
* @brief Calculates the size in bytes needed to encode an int64_t value as a varint
*
* @param value The int64_t value to calculate size for
* @return The number of bytes needed to encode the value
*/
static inline uint32_t varint(int64_t value) {
// For int64_t, we convert to uint64_t and calculate the size
// This works because the bit pattern determines the encoding size,
// and we've handled negative int32 values as a special case above
return varint(static_cast<uint64_t>(value));
}
/**
* @brief Calculates the size in bytes needed to encode a field ID and wire type
*
* @param field_id The field identifier
* @param type The wire type value (from the WireType enum in the protobuf spec)
* @return The number of bytes needed to encode the field ID and wire type
*/
static inline uint32_t field(uint32_t field_id, uint32_t type) {
uint32_t tag = (field_id << 3) | (type & 0b111);
return varint(tag);
}
/**
* @brief Common parameters for all add_*_field methods
*
* All add_*_field methods follow these common patterns:
*
* @param total_size Reference to the total message size to update
* @param field_id_size Pre-calculated size of the field ID in bytes
* @param value The value to calculate size for (type varies)
* @param force Whether to calculate size even if the value is default/zero/empty
*
* Each method follows this implementation pattern:
* 1. Skip calculation if value is default (0, false, empty) and not forced
* 2. Calculate the size based on the field's encoding rules
* 3. Add the field_id_size + calculated value size to total_size
*/
/**
* @brief Calculates and adds the size of an int32 field to the total message size
*/
static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
if (value < 0) {
// Negative values are encoded as 10-byte varints in protobuf
total_size += field_id_size + 10;
} else {
// For non-negative values, use the standard varint size
total_size += field_id_size + varint(static_cast<uint32_t>(value));
}
}
/**
* @brief Calculates and adds the size of an int32 field to the total message size (repeated field version)
*/
static inline void add_int32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Always calculate size for repeated fields
if (value < 0) {
// Negative values are encoded as 10-byte varints in protobuf
total_size += field_id_size + 10;
} else {
// For non-negative values, use the standard varint size
total_size += field_id_size + varint(static_cast<uint32_t>(value));
}
}
/**
* @brief Calculates and adds the size of a uint32 field to the total message size
*/
static inline void add_uint32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a uint32 field to the total message size (repeated field version)
*/
static inline void add_uint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Always calculate size for repeated fields
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a boolean field to the total message size
*/
static inline void add_bool_field(uint32_t &total_size, uint32_t field_id_size, bool value) {
// Skip calculation if value is false
if (!value) {
return; // No need to update total_size
}
// Boolean fields always use 1 byte when true
total_size += field_id_size + 1;
}
/**
* @brief Calculates and adds the size of a boolean field to the total message size (repeated field version)
*/
static inline void add_bool_field_repeated(uint32_t &total_size, uint32_t field_id_size, bool value) {
// Always calculate size for repeated fields
// Boolean fields always use 1 byte
total_size += field_id_size + 1;
}
/**
* @brief Calculates and adds the size of a fixed field to the total message size
*
* Fixed fields always take exactly N bytes (4 for fixed32/float, 8 for fixed64/double).
*
* @tparam NumBytes The number of bytes for this fixed field (4 or 8)
* @param is_nonzero Whether the value is non-zero
*/
template<uint32_t NumBytes>
static inline void add_fixed_field(uint32_t &total_size, uint32_t field_id_size, bool is_nonzero) {
// Skip calculation if value is zero
if (!is_nonzero) {
return; // No need to update total_size
}
// Fixed fields always take exactly NumBytes
total_size += field_id_size + NumBytes;
}
/**
* @brief Calculates and adds the size of an enum field to the total message size
*
* Enum fields are encoded as uint32 varints.
*/
static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// Enums are encoded as uint32
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of an enum field to the total message size (repeated field version)
*
* Enum fields are encoded as uint32 varints.
*/
static inline void add_enum_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Always calculate size for repeated fields
// Enums are encoded as uint32
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a sint32 field to the total message size
*
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
total_size += field_id_size + varint(zigzag);
}
/**
* @brief Calculates and adds the size of a sint32 field to the total message size (repeated field version)
*
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Always calculate size for repeated fields
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
total_size += field_id_size + varint(zigzag);
}
/**
* @brief Calculates and adds the size of an int64 field to the total message size
*/
static inline void add_int64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of an int64 field to the total message size (repeated field version)
*/
static inline void add_int64_field_repeated(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
// Always calculate size for repeated fields
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a uint64 field to the total message size
*/
static inline void add_uint64_field(uint32_t &total_size, uint32_t field_id_size, uint64_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a uint64 field to the total message size (repeated field version)
*/
static inline void add_uint64_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint64_t value) {
// Always calculate size for repeated fields
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of a sint64 field to the total message size
*
* Sint64 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// ZigZag encoding for sint64: (n << 1) ^ (n >> 63)
uint64_t zigzag = (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
total_size += field_id_size + varint(zigzag);
}
/**
* @brief Calculates and adds the size of a sint64 field to the total message size (repeated field version)
*
* Sint64 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint64_field_repeated(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
// Always calculate size for repeated fields
// ZigZag encoding for sint64: (n << 1) ^ (n >> 63)
uint64_t zigzag = (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
total_size += field_id_size + varint(zigzag);
}
/**
* @brief Calculates and adds the size of a string/bytes field to the total message size
*/
static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, const std::string &str) {
// Skip calculation if string is empty
if (str.empty()) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
const uint32_t str_size = static_cast<uint32_t>(str.size());
total_size += field_id_size + varint(str_size) + str_size;
}
/**
* @brief Calculates and adds the size of a string/bytes field to the total message size (repeated field version)
*/
static inline void add_string_field_repeated(uint32_t &total_size, uint32_t field_id_size, const std::string &str) {
// Always calculate size for repeated fields
const uint32_t str_size = static_cast<uint32_t>(str.size());
total_size += field_id_size + varint(str_size) + str_size;
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size
*
* This helper function directly updates the total_size reference if the nested size
* is greater than zero.
*
* @param nested_size The pre-calculated size of the nested message
*/
static inline void add_message_field(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) {
// Skip calculation if nested message is empty
if (nested_size == 0) {
return; // No need to update total_size
}
// Calculate and directly add to total_size
// Field ID + length varint + nested message content
total_size += field_id_size + varint(nested_size) + nested_size;
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size (repeated field version)
*
* @param nested_size The pre-calculated size of the nested message
*/
static inline void add_message_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) {
// Always calculate size for repeated fields
// Field ID + length varint + nested message content
total_size += field_id_size + varint(nested_size) + nested_size;
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size
*
* This version takes a ProtoMessage object, calculates its size internally,
* and updates the total_size reference. This eliminates the need for a temporary variable
* at the call site.
*
* @param message The nested message object
*/
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message) {
uint32_t nested_size = 0;
message.calculate_size(nested_size);
// Use the base implementation with the calculated nested_size
add_message_field(total_size, field_id_size, nested_size);
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size (repeated field version)
*
* @param message The nested message object
*/
static inline void add_message_object_repeated(uint32_t &total_size, uint32_t field_id_size,
const ProtoMessage &message) {
uint32_t nested_size = 0;
message.calculate_size(nested_size);
// Use the base implementation with the calculated nested_size
add_message_field_repeated(total_size, field_id_size, nested_size);
}
/**
* @brief Calculates and adds the sizes of all messages in a repeated field to the total message size
*
* This helper processes a vector of message objects, calculating the size for each message
* and adding it to the total size.
*
* @tparam MessageType The type of the nested messages in the vector
* @param messages Vector of message objects
*/
template<typename MessageType>
static inline void add_repeated_message(uint32_t &total_size, uint32_t field_id_size,
const std::vector<MessageType> &messages) {
// Skip if the vector is empty
if (messages.empty()) {
return;
}
// Use the repeated field version for all messages
for (const auto &message : messages) {
add_message_object_repeated(total_size, field_id_size, message);
}
}
};
// Implementation of encode_message - must be after ProtoMessage is defined
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value, bool force) {
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
// Calculate the message size first
uint32_t msg_length_bytes = 0;
value.calculate_size(msg_length_bytes);
// Calculate how many bytes the length varint needs
uint32_t varint_length_bytes = ProtoSize::varint(msg_length_bytes);
// Reserve exact space for the length varint
size_t begin = this->buffer_->size();
this->buffer_->resize(this->buffer_->size() + varint_length_bytes);
// Write the length varint directly
ProtoVarInt(msg_length_bytes).encode_to_buffer_unchecked(this->buffer_->data() + begin, varint_length_bytes);
// Now encode the message content - it will append to the buffer
value.encode(*this);
// Verify that the encoded size matches what we calculated
assert(this->buffer_->size() == begin + varint_length_bytes + msg_length_bytes);
}
// Implementation of decode_to_message - must be after ProtoMessage is defined
inline void ProtoLengthDelimited::decode_to_message(ProtoMessage &msg) const {
msg.decode(this->value_, this->length_);
}
template<typename T> const char *proto_enum_to_string(T value); template<typename T> const char *proto_enum_to_string(T value);
class ProtoService { class ProtoService {
@ -363,11 +846,11 @@ class ProtoService {
* @return A ProtoWriteBuffer object with the reserved size. * @return A ProtoWriteBuffer object with the reserved size.
*/ */
virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0; virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0;
virtual bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) = 0; virtual bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) = 0;
virtual void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0; virtual void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
// Optimized method that pre-allocates buffer based on message size // Optimized method that pre-allocates buffer based on message size
bool send_message_(const ProtoMessage &msg, uint16_t message_type) { bool send_message_(const ProtoMessage &msg, uint8_t message_type) {
uint32_t msg_size = 0; uint32_t msg_size = 0;
msg.calculate_size(msg_size); msg.calculate_size(msg_size);

View File

@ -7,6 +7,7 @@
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "api_pb2.h" #include "api_pb2.h"
#ifdef USE_API_SERVICES
namespace esphome { namespace esphome {
namespace api { namespace api {
@ -73,3 +74,4 @@ template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif // USE_API_SERVICES

View File

@ -3,8 +3,6 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/as3935/as3935.h" #include "esphome/components/as3935/as3935.h"
#include "esphome/components/spi/spi.h" #include "esphome/components/spi/spi.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome { namespace esphome {
namespace as3935_spi { namespace as3935_spi {

View File

@ -31,7 +31,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
if CORE.is_esp32 or CORE.is_libretiny: if CORE.is_esp32 or CORE.is_libretiny:
# https://github.com/ESP32Async/AsyncTCP # https://github.com/ESP32Async/AsyncTCP
cg.add_library("ESP32Async/AsyncTCP", "3.4.4") cg.add_library("ESP32Async/AsyncTCP", "3.4.5")
elif CORE.is_esp8266: elif CORE.is_esp8266:
# https://github.com/ESP32Async/ESPAsyncTCP # https://github.com/ESP32Async/ESPAsyncTCP
cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0") cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0")

View File

@ -20,14 +20,16 @@ adjusted_ids = set()
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.ensure_list( cv.ensure_list(
{ cv.COMPONENT_SCHEMA.extend(
cv.GenerateID(): cv.declare_id(EspLdo), {
cv.Required(CONF_VOLTAGE): cv.All( cv.GenerateID(): cv.declare_id(EspLdo),
cv.voltage, cv.float_range(min=0.5, max=2.7) cv.Required(CONF_VOLTAGE): cv.All(
), cv.voltage, cv.float_range(min=0.5, max=2.7)
cv.Required(CONF_CHANNEL): cv.one_of(*CHANNELS, int=True), ),
cv.Optional(CONF_ADJUSTABLE, default=False): cv.boolean, cv.Required(CONF_CHANNEL): cv.one_of(*CHANNELS, int=True),
} cv.Optional(CONF_ADJUSTABLE, default=False): cv.boolean,
}
)
), ),
cv.only_with_esp_idf, cv.only_with_esp_idf,
only_on_variant(supported=[VARIANT_ESP32P4]), only_on_variant(supported=[VARIANT_ESP32P4]),

View File

@ -17,6 +17,9 @@ class EspLdo : public Component {
void set_adjustable(bool adjustable) { this->adjustable_ = adjustable; } void set_adjustable(bool adjustable) { this->adjustable_ = adjustable; }
void set_voltage(float voltage) { this->voltage_ = voltage; } void set_voltage(float voltage) { this->voltage_ = voltage; }
void adjust_voltage(float voltage); void adjust_voltage(float voltage);
float get_setup_priority() const override {
return setup_priority::BUS; // LDO setup should be done early
}
protected: protected:
int channel_; int channel_;

View File

@ -342,5 +342,11 @@ async def to_code(config):
cg.add_define("USE_ETHERNET") cg.add_define("USE_ETHERNET")
# Disable WiFi when using Ethernet to save memory
if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENABLED", False)
# Also disable WiFi/BT coexistence since WiFi is disabled
add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", False)
if CORE.using_arduino: if CORE.using_arduino:
cg.add_library("WiFi", None) cg.add_library("WiFi", None)

View File

@ -177,6 +177,10 @@ optional<FanRestoreState> Fan::restore_state_() {
return {}; return {};
} }
void Fan::save_state_() { void Fan::save_state_() {
if (this->restore_mode_ == FanRestoreMode::NO_RESTORE) {
return;
}
FanRestoreState state{}; FanRestoreState state{};
state.state = this->state; state.state = this->state;
state.oscillating = this->oscillating; state.oscillating = this->oscillating;

View File

@ -1,6 +1,6 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_ID,
DEVICE_CLASS_DISTANCE, DEVICE_CLASS_DISTANCE,

View File

@ -1,11 +1,16 @@
import logging
from esphome import pins from esphome import pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import binary_sensor from esphome.components import binary_sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_PIN from esphome.const import CONF_ID, CONF_NAME, CONF_NUMBER, CONF_PIN
from esphome.core import CORE
from .. import gpio_ns from .. import gpio_ns
_LOGGER = logging.getLogger(__name__)
GPIOBinarySensor = gpio_ns.class_( GPIOBinarySensor = gpio_ns.class_(
"GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component "GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component
) )
@ -41,6 +46,22 @@ async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN]) pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin)) cg.add(var.set_pin(pin))
cg.add(var.set_use_interrupt(config[CONF_USE_INTERRUPT])) # Check for ESP8266 GPIO16 interrupt limitation
if config[CONF_USE_INTERRUPT]: # GPIO16 on ESP8266 is a special pin that doesn't support interrupts through
# the Arduino attachInterrupt() function. This is the only known GPIO pin
# across all supported platforms that has this limitation, so we handle it
# here instead of in the platform-specific code.
use_interrupt = config[CONF_USE_INTERRUPT]
if use_interrupt and CORE.is_esp8266 and config[CONF_PIN][CONF_NUMBER] == 16:
_LOGGER.warning(
"GPIO binary_sensor '%s': GPIO16 on ESP8266 doesn't support interrupts. "
"Falling back to polling mode (same as in ESPHome <2025.7). "
"The sensor will work exactly as before, but other pins have better "
"performance with interrupts.",
config.get(CONF_NAME, config[CONF_ID]),
)
use_interrupt = False
cg.add(var.set_use_interrupt(use_interrupt))
if use_interrupt:
cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE])) cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE]))

View File

@ -83,7 +83,7 @@ void HttpRequestUpdate::update_task(void *params) {
container.reset(); // Release ownership of the container's shared_ptr container.reset(); // Release ownership of the container's shared_ptr
valid = json::parse_json(response, [this_update](JsonObject root) -> bool { valid = json::parse_json(response, [this_update](JsonObject root) -> bool {
if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) { if (!root["name"].is<const char *>() || !root["version"].is<const char *>() || !root["builds"].is<JsonArray>()) {
ESP_LOGE(TAG, "Manifest does not contain required fields"); ESP_LOGE(TAG, "Manifest does not contain required fields");
return false; return false;
} }
@ -91,26 +91,26 @@ void HttpRequestUpdate::update_task(void *params) {
this_update->update_info_.latest_version = root["version"].as<std::string>(); this_update->update_info_.latest_version = root["version"].as<std::string>();
for (auto build : root["builds"].as<JsonArray>()) { for (auto build : root["builds"].as<JsonArray>()) {
if (!build.containsKey("chipFamily")) { if (!build["chipFamily"].is<const char *>()) {
ESP_LOGE(TAG, "Manifest does not contain required fields"); ESP_LOGE(TAG, "Manifest does not contain required fields");
return false; return false;
} }
if (build["chipFamily"] == ESPHOME_VARIANT) { if (build["chipFamily"] == ESPHOME_VARIANT) {
if (!build.containsKey("ota")) { if (!build["ota"].is<JsonObject>()) {
ESP_LOGE(TAG, "Manifest does not contain required fields"); ESP_LOGE(TAG, "Manifest does not contain required fields");
return false; return false;
} }
auto ota = build["ota"]; JsonObject ota = build["ota"].as<JsonObject>();
if (!ota.containsKey("path") || !ota.containsKey("md5")) { if (!ota["path"].is<const char *>() || !ota["md5"].is<const char *>()) {
ESP_LOGE(TAG, "Manifest does not contain required fields"); ESP_LOGE(TAG, "Manifest does not contain required fields");
return false; return false;
} }
this_update->update_info_.firmware_url = ota["path"].as<std::string>(); this_update->update_info_.firmware_url = ota["path"].as<std::string>();
this_update->update_info_.md5 = ota["md5"].as<std::string>(); this_update->update_info_.md5 = ota["md5"].as<std::string>();
if (ota.containsKey("summary")) if (ota["summary"].is<const char *>())
this_update->update_info_.summary = ota["summary"].as<std::string>(); this_update->update_info_.summary = ota["summary"].as<std::string>();
if (ota.containsKey("release_url")) if (ota["release_url"].is<const char *>())
this_update->update_info_.release_url = ota["release_url"].as<std::string>(); this_update->update_info_.release_url = ota["release_url"].as<std::string>();
return true; return true;

View File

@ -180,7 +180,7 @@ async def to_code(config):
await speaker.register_speaker(var, config) await speaker.register_speaker(var, config)
if config[CONF_DAC_TYPE] == "internal": if config[CONF_DAC_TYPE] == "internal":
cg.add(var.set_internal_dac_mode(config[CONF_CHANNEL])) cg.add(var.set_internal_dac_mode(config[CONF_MODE]))
else: else:
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN])) cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
if use_legacy(): if use_legacy():

View File

@ -12,6 +12,6 @@ CONFIG_SCHEMA = cv.All(
@coroutine_with_priority(1.0) @coroutine_with_priority(1.0)
async def to_code(config): async def to_code(config):
cg.add_library("bblanchon/ArduinoJson", "6.18.5") cg.add_library("bblanchon/ArduinoJson", "7.4.2")
cg.add_define("USE_JSON") cg.add_define("USE_JSON")
cg.add_global(json_ns.using) cg.add_global(json_ns.using)

View File

@ -1,83 +1,76 @@
#include "json_util.h" #include "json_util.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
// ArduinoJson::Allocator is included via ArduinoJson.h in json_util.h
namespace esphome { namespace esphome {
namespace json { namespace json {
static const char *const TAG = "json"; static const char *const TAG = "json";
static std::vector<char> global_json_build_buffer; // NOLINT // Build an allocator for the JSON Library using the RAMAllocator class
static const auto ALLOCATOR = RAMAllocator<uint8_t>(RAMAllocator<uint8_t>::ALLOC_INTERNAL); struct SpiRamAllocator : ArduinoJson::Allocator {
void *allocate(size_t size) override { return this->allocator_.allocate(size); }
void deallocate(void *pointer) override {
// ArduinoJson's Allocator interface doesn't provide the size parameter in deallocate.
// RAMAllocator::deallocate() requires the size, which we don't have access to here.
// RAMAllocator::deallocate implementation just calls free() regardless of whether
// the memory was allocated with heap_caps_malloc or malloc.
// This is safe because ESP-IDF's heap implementation internally tracks the memory region
// and routes free() to the appropriate heap.
free(pointer); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
}
void *reallocate(void *ptr, size_t new_size) override {
return this->allocator_.reallocate(static_cast<uint8_t *>(ptr), new_size);
}
protected:
RAMAllocator<uint8_t> allocator_{RAMAllocator<uint8_t>(RAMAllocator<uint8_t>::NONE)};
};
std::string build_json(const json_build_t &f) { std::string build_json(const json_build_t &f) {
// Here we are allocating up to 5kb of memory, // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
// with the heap size minus 2kb to be safe if less than 5kb auto doc_allocator = SpiRamAllocator();
// as we can not have a true dynamic sized document. JsonDocument json_document(&doc_allocator);
// The excess memory is freed below with `shrinkToFit()` if (json_document.overflowed()) {
auto free_heap = ALLOCATOR.get_max_free_block_size(); ESP_LOGE(TAG, "Could not allocate memory for JSON document!");
size_t request_size = std::min(free_heap, (size_t) 512); return "{}";
while (true) {
ESP_LOGV(TAG, "Attempting to allocate %zu bytes for JSON serialization", request_size);
DynamicJsonDocument json_document(request_size);
if (json_document.capacity() == 0) {
ESP_LOGE(TAG, "Could not allocate memory for document! Requested %zu bytes, largest free heap block: %zu bytes",
request_size, free_heap);
return "{}";
}
JsonObject root = json_document.to<JsonObject>();
f(root);
if (json_document.overflowed()) {
if (request_size == free_heap) {
ESP_LOGE(TAG, "Could not allocate memory for document! Overflowed largest free heap block: %zu bytes",
free_heap);
return "{}";
}
request_size = std::min(request_size * 2, free_heap);
continue;
}
json_document.shrinkToFit();
ESP_LOGV(TAG, "Size after shrink %zu bytes", json_document.capacity());
std::string output;
serializeJson(json_document, output);
return output;
} }
JsonObject root = json_document.to<JsonObject>();
f(root);
if (json_document.overflowed()) {
ESP_LOGE(TAG, "Could not allocate memory for JSON document!");
return "{}";
}
std::string output;
serializeJson(json_document, output);
return output;
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
} }
bool parse_json(const std::string &data, const json_parse_t &f) { bool parse_json(const std::string &data, const json_parse_t &f) {
// Here we are allocating 1.5 times the data size, // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
// with the heap size minus 2kb to be safe if less than that auto doc_allocator = SpiRamAllocator();
// as we can not have a true dynamic sized document. JsonDocument json_document(&doc_allocator);
// The excess memory is freed below with `shrinkToFit()` if (json_document.overflowed()) {
auto free_heap = ALLOCATOR.get_max_free_block_size(); ESP_LOGE(TAG, "Could not allocate memory for JSON document!");
size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5)); return false;
while (true) { }
DynamicJsonDocument json_document(request_size); DeserializationError err = deserializeJson(json_document, data);
if (json_document.capacity() == 0) {
ESP_LOGE(TAG, "Could not allocate memory for document! Requested %zu bytes, free heap: %zu", request_size,
free_heap);
return false;
}
DeserializationError err = deserializeJson(json_document, data);
json_document.shrinkToFit();
JsonObject root = json_document.as<JsonObject>(); JsonObject root = json_document.as<JsonObject>();
if (err == DeserializationError::Ok) { if (err == DeserializationError::Ok) {
return f(root); return f(root);
} else if (err == DeserializationError::NoMemory) { } else if (err == DeserializationError::NoMemory) {
if (request_size * 2 >= free_heap) { ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller");
ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller"); return false;
return false; }
} ESP_LOGE(TAG, "Parse error: %s", err.c_str());
ESP_LOGV(TAG, "Increasing memory allocation.");
request_size *= 2;
continue;
} else {
ESP_LOGE(TAG, "Parse error: %s", err.c_str());
return false;
}
};
return false; return false;
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
} }
} // namespace json } // namespace json

View File

@ -178,13 +178,8 @@ static constexpr uint8_t NO_MAC[] = {0x08, 0x05, 0x04, 0x03, 0x02, 0x01};
static inline int two_byte_to_int(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; } static inline int two_byte_to_int(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; }
static bool validate_header_footer(const uint8_t *header_footer, const uint8_t *buffer) { static inline bool validate_header_footer(const uint8_t *header_footer, const uint8_t *buffer) {
for (uint8_t i = 0; i < HEADER_FOOTER_SIZE; i++) { return std::memcmp(header_footer, buffer, HEADER_FOOTER_SIZE) == 0;
if (header_footer[i] != buffer[i]) {
return false; // Mismatch in header/footer
}
}
return true; // Valid header/footer
} }
void LD2410Component::dump_config() { void LD2410Component::dump_config() {
@ -300,14 +295,12 @@ void LD2410Component::send_command_(uint8_t command, const uint8_t *command_valu
if (command_value != nullptr) { if (command_value != nullptr) {
len += command_value_len; len += command_value_len;
} }
uint8_t len_cmd[] = {lowbyte(len), highbyte(len), command, 0x00}; // 2 length bytes (low, high) + 2 command bytes (low, high)
uint8_t len_cmd[] = {len, 0x00, command, 0x00};
this->write_array(len_cmd, sizeof(len_cmd)); this->write_array(len_cmd, sizeof(len_cmd));
// command value bytes // command value bytes
if (command_value != nullptr) { if (command_value != nullptr) {
for (uint8_t i = 0; i < command_value_len; i++) { this->write_array(command_value, command_value_len);
this->write_byte(command_value[i]);
}
} }
// frame footer bytes // frame footer bytes
this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)); this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER));
@ -401,7 +394,7 @@ void LD2410Component::handle_periodic_data_() {
/* /*
Moving distance range: 18th byte Moving distance range: 18th byte
Still distance range: 19th byte Still distance range: 19th byte
Moving enery: 20~28th bytes Moving energy: 20~28th bytes
*/ */
for (std::vector<sensor::Sensor *>::size_type i = 0; i != this->gate_move_sensors_.size(); i++) { for (std::vector<sensor::Sensor *>::size_type i = 0; i != this->gate_move_sensors_.size(); i++) {
sensor::Sensor *s = this->gate_move_sensors_[i]; sensor::Sensor *s = this->gate_move_sensors_[i];
@ -480,7 +473,7 @@ bool LD2410Component::handle_ack_data_() {
ESP_LOGE(TAG, "Invalid status"); ESP_LOGE(TAG, "Invalid status");
return true; return true;
} }
if (ld2410::two_byte_to_int(this->buffer_data_[8], this->buffer_data_[9]) != 0x00) { if (this->buffer_data_[8] || this->buffer_data_[9]) {
ESP_LOGW(TAG, "Invalid command: %02X, %02X", this->buffer_data_[8], this->buffer_data_[9]); ESP_LOGW(TAG, "Invalid command: %02X, %02X", this->buffer_data_[8], this->buffer_data_[9]);
return true; return true;
} }
@ -534,8 +527,8 @@ bool LD2410Component::handle_ack_data_() {
const auto *light_function_str = find_str(LIGHT_FUNCTIONS_BY_UINT, this->light_function_); const auto *light_function_str = find_str(LIGHT_FUNCTIONS_BY_UINT, this->light_function_);
const auto *out_pin_level_str = find_str(OUT_PIN_LEVELS_BY_UINT, this->out_pin_level_); const auto *out_pin_level_str = find_str(OUT_PIN_LEVELS_BY_UINT, this->out_pin_level_);
ESP_LOGV(TAG, ESP_LOGV(TAG,
"Light function is: %s\n" "Light function: %s\n"
"Light threshold is: %u\n" "Light threshold: %u\n"
"Out pin level: %s", "Out pin level: %s",
light_function_str, this->light_threshold_, out_pin_level_str); light_function_str, this->light_threshold_, out_pin_level_str);
#ifdef USE_SELECT #ifdef USE_SELECT
@ -600,7 +593,7 @@ bool LD2410Component::handle_ack_data_() {
break; break;
case CMD_QUERY: { // Query parameters response case CMD_QUERY: { // Query parameters response
if (this->buffer_data_[10] != 0xAA) if (this->buffer_data_[10] != HEADER)
return true; // value head=0xAA return true; // value head=0xAA
#ifdef USE_NUMBER #ifdef USE_NUMBER
/* /*
@ -656,17 +649,11 @@ void LD2410Component::readline_(int readch) {
if (this->buffer_pos_ < 4) { if (this->buffer_pos_ < 4) {
return; // Not enough data to process yet return; // Not enough data to process yet
} }
if (this->buffer_data_[this->buffer_pos_ - 4] == DATA_FRAME_FOOTER[0] && if (ld2410::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
this->buffer_data_[this->buffer_pos_ - 3] == DATA_FRAME_FOOTER[1] &&
this->buffer_data_[this->buffer_pos_ - 2] == DATA_FRAME_FOOTER[2] &&
this->buffer_data_[this->buffer_pos_ - 1] == DATA_FRAME_FOOTER[3]) {
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
this->handle_periodic_data_(); this->handle_periodic_data_();
this->buffer_pos_ = 0; // Reset position index for next message this->buffer_pos_ = 0; // Reset position index for next message
} else if (this->buffer_data_[this->buffer_pos_ - 4] == CMD_FRAME_FOOTER[0] && } else if (ld2410::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
this->buffer_data_[this->buffer_pos_ - 3] == CMD_FRAME_FOOTER[1] &&
this->buffer_data_[this->buffer_pos_ - 2] == CMD_FRAME_FOOTER[2] &&
this->buffer_data_[this->buffer_pos_ - 1] == CMD_FRAME_FOOTER[3]) {
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
if (this->handle_ack_data_()) { if (this->handle_ack_data_()) {
this->buffer_pos_ = 0; // Reset position index for next message this->buffer_pos_ = 0; // Reset position index for next message
@ -772,7 +759,6 @@ void LD2410Component::set_max_distances_timeout() {
0x00}; 0x00};
this->set_config_mode_(true); this->set_config_mode_(true);
this->send_command_(CMD_MAXDIST_DURATION, value, sizeof(value)); this->send_command_(CMD_MAXDIST_DURATION, value, sizeof(value));
delay(50); // NOLINT
this->query_parameters_(); this->query_parameters_();
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
this->set_config_mode_(false); this->set_config_mode_(false);
@ -802,7 +788,6 @@ void LD2410Component::set_gate_threshold(uint8_t gate) {
0x01, 0x00, lowbyte(motion), highbyte(motion), 0x00, 0x00, 0x01, 0x00, lowbyte(motion), highbyte(motion), 0x00, 0x00,
0x02, 0x00, lowbyte(still), highbyte(still), 0x00, 0x00}; 0x02, 0x00, lowbyte(still), highbyte(still), 0x00, 0x00};
this->send_command_(CMD_GATE_SENS, value, sizeof(value)); this->send_command_(CMD_GATE_SENS, value, sizeof(value));
delay(50); // NOLINT
this->query_parameters_(); this->query_parameters_();
this->set_config_mode_(false); this->set_config_mode_(false);
} }
@ -833,7 +818,6 @@ void LD2410Component::set_light_out_control() {
this->set_config_mode_(true); this->set_config_mode_(true);
uint8_t value[4] = {this->light_function_, this->light_threshold_, this->out_pin_level_, 0x00}; uint8_t value[4] = {this->light_function_, this->light_threshold_, this->out_pin_level_, 0x00};
this->send_command_(CMD_SET_LIGHT_CONTROL, value, sizeof(value)); this->send_command_(CMD_SET_LIGHT_CONTROL, value, sizeof(value));
delay(50); // NOLINT
this->query_light_control_(); this->query_light_control_();
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
this->set_config_mode_(false); this->set_config_mode_(false);

View File

@ -5,10 +5,10 @@
namespace esphome { namespace esphome {
namespace ld2420 { namespace ld2420 {
static const char *const TAG = "LD2420.binary_sensor"; static const char *const TAG = "ld2420.binary_sensor";
void LD2420BinarySensor::dump_config() { void LD2420BinarySensor::dump_config() {
ESP_LOGCONFIG(TAG, "LD2420 BinarySensor:"); ESP_LOGCONFIG(TAG, "Binary Sensor:");
LOG_BINARY_SENSOR(" ", "Presence", this->presence_bsensor_); LOG_BINARY_SENSOR(" ", "Presence", this->presence_bsensor_);
} }

View File

@ -2,7 +2,7 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
static const char *const TAG = "LD2420.button"; static const char *const TAG = "ld2420.button";
namespace esphome { namespace esphome {
namespace ld2420 { namespace ld2420 {

View File

@ -137,7 +137,7 @@ static const std::string OP_SIMPLE_MODE_STRING = "Simple";
// Memory-efficient lookup tables // Memory-efficient lookup tables
struct StringToUint8 { struct StringToUint8 {
const char *str; const char *str;
uint8_t value; const uint8_t value;
}; };
static constexpr StringToUint8 OP_MODE_BY_STR[] = { static constexpr StringToUint8 OP_MODE_BY_STR[] = {
@ -155,8 +155,9 @@ static constexpr const char *ERR_MESSAGE[] = {
// Helper function for lookups // Helper function for lookups
template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) { template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) {
for (const auto &entry : arr) { for (const auto &entry : arr) {
if (str == entry.str) if (str == entry.str) {
return entry.value; return entry.value;
}
} }
return 0xFF; // Not found return 0xFF; // Not found
} }
@ -326,15 +327,8 @@ void LD2420Component::revert_config_action() {
void LD2420Component::loop() { void LD2420Component::loop() {
// If there is a active send command do not process it here, the send command call will handle it. // If there is a active send command do not process it here, the send command call will handle it.
if (!this->get_cmd_active_()) { while (!this->cmd_active_ && this->available()) {
if (!this->available()) this->readline_(this->read(), this->buffer_data_, MAX_LINE_LENGTH);
return;
static uint8_t buffer[2048];
static uint8_t rx_data;
while (this->available()) {
rx_data = this->read();
this->readline_(rx_data, buffer, sizeof(buffer));
}
} }
} }
@ -365,8 +359,9 @@ void LD2420Component::auto_calibrate_sensitivity() {
// Store average and peak values // Store average and peak values
this->gate_avg[gate] = sum / CALIBRATE_SAMPLES; this->gate_avg[gate] = sum / CALIBRATE_SAMPLES;
if (this->gate_peak[gate] < peak) if (this->gate_peak[gate] < peak) {
this->gate_peak[gate] = peak; this->gate_peak[gate] = peak;
}
uint32_t calculated_value = uint32_t calculated_value =
(static_cast<uint32_t>(this->gate_peak[gate]) + (move_factor * static_cast<uint32_t>(this->gate_peak[gate]))); (static_cast<uint32_t>(this->gate_peak[gate]) + (move_factor * static_cast<uint32_t>(this->gate_peak[gate])));
@ -403,8 +398,9 @@ void LD2420Component::set_operating_mode(const std::string &state) {
} }
} else { } else {
// Set the current data back so we don't have new data that can be applied in error. // Set the current data back so we don't have new data that can be applied in error.
if (this->get_calibration_()) if (this->get_calibration_()) {
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
}
this->set_calibration_(false); this->set_calibration_(false);
} }
} else { } else {
@ -414,30 +410,32 @@ void LD2420Component::set_operating_mode(const std::string &state) {
} }
void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) { void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) {
static int pos = 0; if (rx_data < 0) {
return; // No data available
if (rx_data >= 0) { }
if (pos < len - 1) { if (this->buffer_pos_ < len - 1) {
buffer[pos++] = rx_data; buffer[this->buffer_pos_++] = rx_data;
buffer[pos] = 0; buffer[this->buffer_pos_] = 0;
} else { } else {
pos = 0; // We should never get here, but just in case...
} ESP_LOGW(TAG, "Max command length exceeded; ignoring");
if (pos >= 4) { this->buffer_pos_ = 0;
if (memcmp(&buffer[pos - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) { }
this->set_cmd_active_(false); // Set command state to inactive after responce. if (this->buffer_pos_ < 4) {
this->handle_ack_data_(buffer, pos); return; // Not enough data to process yet
pos = 0; }
} else if ((buffer[pos - 2] == 0x0D && buffer[pos - 1] == 0x0A) && if (memcmp(&buffer[this->buffer_pos_ - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) {
(this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) { this->cmd_active_ = false; // Set command state to inactive after response
this->handle_simple_mode_(buffer, pos); this->handle_ack_data_(buffer, this->buffer_pos_);
pos = 0; this->buffer_pos_ = 0;
} else if ((memcmp(&buffer[pos - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) && } else if ((buffer[this->buffer_pos_ - 2] == 0x0D && buffer[this->buffer_pos_ - 1] == 0x0A) &&
(this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) { (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) {
this->handle_energy_mode_(buffer, pos); this->handle_simple_mode_(buffer, this->buffer_pos_);
pos = 0; this->buffer_pos_ = 0;
} } else if ((memcmp(&buffer[this->buffer_pos_ - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) &&
} (this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) {
this->handle_energy_mode_(buffer, this->buffer_pos_);
this->buffer_pos_ = 0;
} }
} }
@ -462,8 +460,9 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
// Resonable refresh rate for home assistant database size health // Resonable refresh rate for home assistant database size health
const int32_t current_millis = App.get_loop_component_start_time(); const int32_t current_millis = App.get_loop_component_start_time();
if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) {
return; return;
}
this->last_periodic_millis = current_millis; this->last_periodic_millis = current_millis;
for (auto &listener : this->listeners_) { for (auto &listener : this->listeners_) {
listener->on_distance(this->get_distance_()); listener->on_distance(this->get_distance_());
@ -506,14 +505,16 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
} }
} }
outbuf[index] = '\0'; outbuf[index] = '\0';
if (index > 1) if (index > 1) {
this->set_distance_(strtol(outbuf, &endptr, 10)); this->set_distance_(strtol(outbuf, &endptr, 10));
}
if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) { if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
// Resonable refresh rate for home assistant database size health // Resonable refresh rate for home assistant database size health
const int32_t current_millis = App.get_loop_component_start_time(); const int32_t current_millis = App.get_loop_component_start_time();
if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) {
return; return;
}
this->last_normal_periodic_millis = current_millis; this->last_normal_periodic_millis = current_millis;
for (auto &listener : this->listeners_) for (auto &listener : this->listeners_)
listener->on_distance(this->get_distance_()); listener->on_distance(this->get_distance_());
@ -593,11 +594,12 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
int LD2420Component::send_cmd_from_array(CmdFrameT frame) { int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
uint32_t start_millis = millis(); uint32_t start_millis = millis();
uint8_t error = 0; uint8_t error = 0;
uint8_t ack_buffer[64]; uint8_t ack_buffer[MAX_LINE_LENGTH];
uint8_t cmd_buffer[64]; uint8_t cmd_buffer[MAX_LINE_LENGTH];
this->cmd_reply_.ack = false; this->cmd_reply_.ack = false;
if (frame.command != CMD_RESTART) if (frame.command != CMD_RESTART) {
this->set_cmd_active_(true); // Restart does not reply, thus no ack state required. this->cmd_active_ = true;
} // Restart does not reply, thus no ack state required
uint8_t retry = 3; uint8_t retry = 3;
while (retry) { while (retry) {
frame.length = 0; frame.length = 0;
@ -619,9 +621,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer)); memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer));
frame.length += sizeof(frame.footer); frame.length += sizeof(frame.footer);
for (uint16_t index = 0; index < frame.length; index++) { this->write_array(cmd_buffer, frame.length);
this->write_byte(cmd_buffer[index]);
}
error = 0; error = 0;
if (frame.command == CMD_RESTART) { if (frame.command == CMD_RESTART) {
@ -630,7 +630,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
while (!this->cmd_reply_.ack) { while (!this->cmd_reply_.ack) {
while (this->available()) { while (this->available()) {
this->readline_(read(), ack_buffer, sizeof(ack_buffer)); this->readline_(this->read(), ack_buffer, sizeof(ack_buffer));
} }
delay_microseconds_safe(1450); delay_microseconds_safe(1450);
// Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT. // Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT.
@ -641,10 +641,12 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
break; break;
} }
} }
if (this->cmd_reply_.ack) if (this->cmd_reply_.ack) {
retry = 0; retry = 0;
if (this->cmd_reply_.error > 0) }
if (this->cmd_reply_.error > 0) {
this->handle_cmd_error(error); this->handle_cmd_error(error);
}
} }
return error; return error;
} }
@ -764,8 +766,9 @@ void LD2420Component::set_system_mode(uint16_t mode) {
cmd_frame.data_length += sizeof(unknown_parm); cmd_frame.data_length += sizeof(unknown_parm);
cmd_frame.footer = CMD_FRAME_FOOTER; cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command); ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command);
if (this->send_cmd_from_array(cmd_frame) == 0) if (this->send_cmd_from_array(cmd_frame) == 0) {
this->set_mode_(mode); this->set_mode_(mode);
}
} }
void LD2420Component::get_firmware_version_() { void LD2420Component::get_firmware_version_() {
@ -840,18 +843,24 @@ void LD2420Component::set_gate_threshold(uint8_t gate) {
#ifdef USE_NUMBER #ifdef USE_NUMBER
void LD2420Component::init_gate_config_numbers() { void LD2420Component::init_gate_config_numbers() {
if (this->gate_timeout_number_ != nullptr) if (this->gate_timeout_number_ != nullptr) {
this->gate_timeout_number_->publish_state(static_cast<uint16_t>(this->current_config.timeout)); this->gate_timeout_number_->publish_state(static_cast<uint16_t>(this->current_config.timeout));
if (this->gate_select_number_ != nullptr) }
if (this->gate_select_number_ != nullptr) {
this->gate_select_number_->publish_state(0); this->gate_select_number_->publish_state(0);
if (this->min_gate_distance_number_ != nullptr) }
if (this->min_gate_distance_number_ != nullptr) {
this->min_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.min_gate)); this->min_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.min_gate));
if (this->max_gate_distance_number_ != nullptr) }
if (this->max_gate_distance_number_ != nullptr) {
this->max_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.max_gate)); this->max_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.max_gate));
if (this->gate_move_sensitivity_factor_number_ != nullptr) }
if (this->gate_move_sensitivity_factor_number_ != nullptr) {
this->gate_move_sensitivity_factor_number_->publish_state(this->gate_move_sensitivity_factor); this->gate_move_sensitivity_factor_number_->publish_state(this->gate_move_sensitivity_factor);
if (this->gate_still_sensitivity_factor_number_ != nullptr) }
if (this->gate_still_sensitivity_factor_number_ != nullptr) {
this->gate_still_sensitivity_factor_number_->publish_state(this->gate_still_sensitivity_factor); this->gate_still_sensitivity_factor_number_->publish_state(this->gate_still_sensitivity_factor);
}
for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) { for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
if (this->gate_still_threshold_numbers_[gate] != nullptr) { if (this->gate_still_threshold_numbers_[gate] != nullptr) {
this->gate_still_threshold_numbers_[gate]->publish_state( this->gate_still_threshold_numbers_[gate]->publish_state(

View File

@ -20,8 +20,9 @@
namespace esphome { namespace esphome {
namespace ld2420 { namespace ld2420 {
static const uint8_t TOTAL_GATES = 16;
static const uint8_t CALIBRATE_SAMPLES = 64; static const uint8_t CALIBRATE_SAMPLES = 64;
static const uint8_t MAX_LINE_LENGTH = 46; // Max characters for serial buffer
static const uint8_t TOTAL_GATES = 16;
enum OpMode : uint8_t { enum OpMode : uint8_t {
OP_NORMAL_MODE = 1, OP_NORMAL_MODE = 1,
@ -118,10 +119,10 @@ class LD2420Component : public Component, public uart::UARTDevice {
float gate_move_sensitivity_factor{0.5}; float gate_move_sensitivity_factor{0.5};
float gate_still_sensitivity_factor{0.5}; float gate_still_sensitivity_factor{0.5};
int32_t last_periodic_millis = millis(); int32_t last_periodic_millis{0};
int32_t report_periodic_millis = millis(); int32_t report_periodic_millis{0};
int32_t monitor_periodic_millis = millis(); int32_t monitor_periodic_millis{0};
int32_t last_normal_periodic_millis = millis(); int32_t last_normal_periodic_millis{0};
uint16_t radar_data[TOTAL_GATES][CALIBRATE_SAMPLES]; uint16_t radar_data[TOTAL_GATES][CALIBRATE_SAMPLES];
uint16_t gate_avg[TOTAL_GATES]; uint16_t gate_avg[TOTAL_GATES];
uint16_t gate_peak[TOTAL_GATES]; uint16_t gate_peak[TOTAL_GATES];
@ -161,8 +162,6 @@ class LD2420Component : public Component, public uart::UARTDevice {
void set_presence_(bool presence) { this->presence_ = presence; }; void set_presence_(bool presence) { this->presence_ = presence; };
uint16_t get_distance_() { return this->distance_; }; uint16_t get_distance_() { return this->distance_; };
void set_distance_(uint16_t distance) { this->distance_ = distance; }; void set_distance_(uint16_t distance) { this->distance_ = distance; };
bool get_cmd_active_() { return this->cmd_active_; };
void set_cmd_active_(bool active) { this->cmd_active_ = active; };
void handle_simple_mode_(const uint8_t *inbuf, int len); void handle_simple_mode_(const uint8_t *inbuf, int len);
void handle_energy_mode_(uint8_t *buffer, int len); void handle_energy_mode_(uint8_t *buffer, int len);
void handle_ack_data_(uint8_t *buffer, int len); void handle_ack_data_(uint8_t *buffer, int len);
@ -181,12 +180,11 @@ class LD2420Component : public Component, public uart::UARTDevice {
std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(16); std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(16);
#endif #endif
uint32_t max_distance_gate_; uint16_t distance_{0};
uint32_t min_distance_gate_;
uint16_t system_mode_; uint16_t system_mode_;
uint16_t gate_energy_[TOTAL_GATES]; uint16_t gate_energy_[TOTAL_GATES];
uint16_t distance_{0}; uint8_t buffer_pos_{0}; // where to resume processing/populating buffer
uint8_t config_checksum_{0}; uint8_t buffer_data_[MAX_LINE_LENGTH];
char firmware_ver_[8]{"v0.0.0"}; char firmware_ver_[8]{"v0.0.0"};
bool cmd_active_{false}; bool cmd_active_{false};
bool presence_{false}; bool presence_{false};

View File

@ -2,7 +2,7 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
static const char *const TAG = "LD2420.number"; static const char *const TAG = "ld2420.number";
namespace esphome { namespace esphome {
namespace ld2420 { namespace ld2420 {

View File

@ -5,7 +5,7 @@
namespace esphome { namespace esphome {
namespace ld2420 { namespace ld2420 {
static const char *const TAG = "LD2420.select"; static const char *const TAG = "ld2420.select";
void LD2420Select::control(const std::string &value) { void LD2420Select::control(const std::string &value) {
this->publish_state(value); this->publish_state(value);

View File

@ -5,10 +5,10 @@
namespace esphome { namespace esphome {
namespace ld2420 { namespace ld2420 {
static const char *const TAG = "LD2420.sensor"; static const char *const TAG = "ld2420.sensor";
void LD2420Sensor::dump_config() { void LD2420Sensor::dump_config() {
ESP_LOGCONFIG(TAG, "LD2420 Sensor:"); ESP_LOGCONFIG(TAG, "Sensor:");
LOG_SENSOR(" ", "Distance", this->distance_sensor_); LOG_SENSOR(" ", "Distance", this->distance_sensor_);
} }

View File

@ -5,10 +5,10 @@
namespace esphome { namespace esphome {
namespace ld2420 { namespace ld2420 {
static const char *const TAG = "LD2420.text_sensor"; static const char *const TAG = "ld2420.text_sensor";
void LD2420TextSensor::dump_config() { void LD2420TextSensor::dump_config() {
ESP_LOGCONFIG(TAG, "LD2420 TextSensor:"); ESP_LOGCONFIG(TAG, "Text Sensor:");
LOG_TEXT_SENSOR(" ", "Firmware", this->fw_version_text_sensor_); LOG_TEXT_SENSOR(" ", "Firmware", this->fw_version_text_sensor_);
} }

View File

@ -268,7 +268,7 @@ async def component_to_code(config):
# disable library compatibility checks # disable library compatibility checks
cg.add_platformio_option("lib_ldf_mode", "off") cg.add_platformio_option("lib_ldf_mode", "off")
cg.add_platformio_option("lib_compat_mode", "strict") cg.add_platformio_option("lib_compat_mode", "soft")
# include <Arduino.h> in every file # include <Arduino.h> in every file
cg.add_platformio_option("build_src_flags", "-include Arduino.h") cg.add_platformio_option("build_src_flags", "-include Arduino.h")
# dummy version code # dummy version code

View File

@ -9,6 +9,7 @@ namespace light {
// See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema // See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema
void LightJSONSchema::dump_json(LightState &state, JsonObject root) { void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (state.supports_effects()) if (state.supports_effects())
root["effect"] = state.get_effect_name(); root["effect"] = state.get_effect_name();
@ -52,7 +53,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
if (values.get_color_mode() & ColorCapability::BRIGHTNESS) if (values.get_color_mode() & ColorCapability::BRIGHTNESS)
root["brightness"] = uint8_t(values.get_brightness() * 255); root["brightness"] = uint8_t(values.get_brightness() * 255);
JsonObject color = root.createNestedObject("color"); JsonObject color = root["color"].to<JsonObject>();
if (values.get_color_mode() & ColorCapability::RGB) { if (values.get_color_mode() & ColorCapability::RGB) {
color["r"] = uint8_t(values.get_color_brightness() * values.get_red() * 255); color["r"] = uint8_t(values.get_color_brightness() * values.get_red() * 255);
color["g"] = uint8_t(values.get_color_brightness() * values.get_green() * 255); color["g"] = uint8_t(values.get_color_brightness() * values.get_green() * 255);
@ -73,7 +74,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
} }
void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject root) { void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject root) {
if (root.containsKey("state")) { if (root["state"].is<const char *>()) {
auto val = parse_on_off(root["state"]); auto val = parse_on_off(root["state"]);
switch (val) { switch (val) {
case PARSE_ON: case PARSE_ON:
@ -90,40 +91,40 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO
} }
} }
if (root.containsKey("brightness")) { if (root["brightness"].is<uint8_t>()) {
call.set_brightness(float(root["brightness"]) / 255.0f); call.set_brightness(float(root["brightness"]) / 255.0f);
} }
if (root.containsKey("color")) { if (root["color"].is<JsonObject>()) {
JsonObject color = root["color"]; JsonObject color = root["color"];
// HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness. // HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness.
float max_rgb = 0.0f; float max_rgb = 0.0f;
if (color.containsKey("r")) { if (color["r"].is<uint8_t>()) {
float r = float(color["r"]) / 255.0f; float r = float(color["r"]) / 255.0f;
max_rgb = fmaxf(max_rgb, r); max_rgb = fmaxf(max_rgb, r);
call.set_red(r); call.set_red(r);
} }
if (color.containsKey("g")) { if (color["g"].is<uint8_t>()) {
float g = float(color["g"]) / 255.0f; float g = float(color["g"]) / 255.0f;
max_rgb = fmaxf(max_rgb, g); max_rgb = fmaxf(max_rgb, g);
call.set_green(g); call.set_green(g);
} }
if (color.containsKey("b")) { if (color["b"].is<uint8_t>()) {
float b = float(color["b"]) / 255.0f; float b = float(color["b"]) / 255.0f;
max_rgb = fmaxf(max_rgb, b); max_rgb = fmaxf(max_rgb, b);
call.set_blue(b); call.set_blue(b);
} }
if (color.containsKey("r") || color.containsKey("g") || color.containsKey("b")) { if (color["r"].is<uint8_t>() || color["g"].is<uint8_t>() || color["b"].is<uint8_t>()) {
call.set_color_brightness(max_rgb); call.set_color_brightness(max_rgb);
} }
if (color.containsKey("c")) { if (color["c"].is<uint8_t>()) {
call.set_cold_white(float(color["c"]) / 255.0f); call.set_cold_white(float(color["c"]) / 255.0f);
} }
if (color.containsKey("w")) { if (color["w"].is<uint8_t>()) {
// the HA scheme is ambiguous here, the same key is used for white channel in RGBW and warm // the HA scheme is ambiguous here, the same key is used for white channel in RGBW and warm
// white channel in RGBWW. // white channel in RGBWW.
if (color.containsKey("c")) { if (color["c"].is<uint8_t>()) {
call.set_warm_white(float(color["w"]) / 255.0f); call.set_warm_white(float(color["w"]) / 255.0f);
} else { } else {
call.set_white(float(color["w"]) / 255.0f); call.set_white(float(color["w"]) / 255.0f);
@ -131,11 +132,11 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO
} }
} }
if (root.containsKey("white_value")) { // legacy API if (root["white_value"].is<uint8_t>()) { // legacy API
call.set_white(float(root["white_value"]) / 255.0f); call.set_white(float(root["white_value"]) / 255.0f);
} }
if (root.containsKey("color_temp")) { if (root["color_temp"].is<uint16_t>()) {
call.set_color_temperature(float(root["color_temp"])); call.set_color_temperature(float(root["color_temp"]));
} }
} }
@ -143,17 +144,17 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO
void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject root) { void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject root) {
LightJSONSchema::parse_color_json(state, call, root); LightJSONSchema::parse_color_json(state, call, root);
if (root.containsKey("flash")) { if (root["flash"].is<uint32_t>()) {
auto length = uint32_t(float(root["flash"]) * 1000); auto length = uint32_t(float(root["flash"]) * 1000);
call.set_flash_length(length); call.set_flash_length(length);
} }
if (root.containsKey("transition")) { if (root["transition"].is<uint16_t>()) {
auto length = uint32_t(float(root["transition"]) * 1000); auto length = uint32_t(float(root["transition"]) * 1000);
call.set_transition_length(length); call.set_transition_length(length);
} }
if (root.containsKey("effect")) { if (root["effect"].is<const char *>()) {
const char *effect = root["effect"]; const char *effect = root["effect"];
call.set_effect(effect); call.set_effect(effect);
} }

View File

@ -1,16 +1,16 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_ID,
CONF_TEMPERATURE,
CONF_PRESSURE, CONF_PRESSURE,
CONF_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
ICON_THERMOMETER,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS, UNIT_CELSIUS,
UNIT_HECTOPASCAL, UNIT_HECTOPASCAL,
ICON_THERMOMETER,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
) )
CODEOWNERS = ["@nagisa"] CODEOWNERS = ["@nagisa"]

View File

@ -29,9 +29,9 @@ from ..defines import (
) )
from ..helpers import add_lv_use, lvgl_components_required from ..helpers import add_lv_use, lvgl_components_required
from ..lv_validation import ( from ..lv_validation import (
angle,
get_end_value, get_end_value,
get_start_value, get_start_value,
lv_angle,
lv_bool, lv_bool,
lv_color, lv_color,
lv_float, lv_float,
@ -162,7 +162,7 @@ SCALE_SCHEMA = cv.Schema(
cv.Optional(CONF_RANGE_FROM, default=0.0): cv.float_, cv.Optional(CONF_RANGE_FROM, default=0.0): cv.float_,
cv.Optional(CONF_RANGE_TO, default=100.0): cv.float_, cv.Optional(CONF_RANGE_TO, default=100.0): cv.float_,
cv.Optional(CONF_ANGLE_RANGE, default=270): cv.int_range(0, 360), cv.Optional(CONF_ANGLE_RANGE, default=270): cv.int_range(0, 360),
cv.Optional(CONF_ROTATION): angle, cv.Optional(CONF_ROTATION): lv_angle,
cv.Optional(CONF_INDICATORS): cv.ensure_list(INDICATOR_SCHEMA), cv.Optional(CONF_INDICATORS): cv.ensure_list(INDICATOR_SCHEMA),
} }
) )
@ -187,7 +187,7 @@ class MeterType(WidgetType):
for scale_conf in config.get(CONF_SCALES, ()): for scale_conf in config.get(CONF_SCALES, ()):
rotation = 90 + (360 - scale_conf[CONF_ANGLE_RANGE]) / 2 rotation = 90 + (360 - scale_conf[CONF_ANGLE_RANGE]) / 2
if CONF_ROTATION in scale_conf: if CONF_ROTATION in scale_conf:
rotation = scale_conf[CONF_ROTATION] // 10 rotation = await lv_angle.process(scale_conf[CONF_ROTATION])
with LocalVariable( with LocalVariable(
"meter_var", "lv_meter_scale_t", lv_expr.meter_add_scale(var) "meter_var", "lv_meter_scale_t", lv_expr.meter_add_scale(var)
) as meter_var: ) as meter_var:
@ -205,21 +205,20 @@ class MeterType(WidgetType):
var, var,
meter_var, meter_var,
ticks[CONF_COUNT], ticks[CONF_COUNT],
ticks[CONF_WIDTH], await size.process(ticks[CONF_WIDTH]),
ticks[CONF_LENGTH], await size.process(ticks[CONF_LENGTH]),
color, color,
) )
if CONF_MAJOR in ticks: if CONF_MAJOR in ticks:
major = ticks[CONF_MAJOR] major = ticks[CONF_MAJOR]
color = await lv_color.process(major[CONF_COLOR])
lv.meter_set_scale_major_ticks( lv.meter_set_scale_major_ticks(
var, var,
meter_var, meter_var,
major[CONF_STRIDE], major[CONF_STRIDE],
major[CONF_WIDTH], await size.process(major[CONF_WIDTH]),
major[CONF_LENGTH], await size.process(major[CONF_LENGTH]),
color, await lv_color.process(major[CONF_COLOR]),
major[CONF_LABEL_GAP], await size.process(major[CONF_LABEL_GAP]),
) )
for indicator in scale_conf.get(CONF_INDICATORS, ()): for indicator in scale_conf.get(CONF_INDICATORS, ()):
(t, v) = next(iter(indicator.items())) (t, v) = next(iter(indicator.items()))
@ -233,7 +232,11 @@ class MeterType(WidgetType):
lv_assign( lv_assign(
ivar, ivar,
lv_expr.meter_add_needle_line( lv_expr.meter_add_needle_line(
var, meter_var, v[CONF_WIDTH], color, v[CONF_R_MOD] var,
meter_var,
await size.process(v[CONF_WIDTH]),
color,
await size.process(v[CONF_R_MOD]),
), ),
) )
if t == CONF_ARC: if t == CONF_ARC:
@ -241,7 +244,11 @@ class MeterType(WidgetType):
lv_assign( lv_assign(
ivar, ivar,
lv_expr.meter_add_arc( lv_expr.meter_add_arc(
var, meter_var, v[CONF_WIDTH], color, v[CONF_R_MOD] var,
meter_var,
await size.process(v[CONF_WIDTH]),
color,
await size.process(v[CONF_R_MOD]),
), ),
) )
if t == CONF_TICK_STYLE: if t == CONF_TICK_STYLE:
@ -257,7 +264,7 @@ class MeterType(WidgetType):
color_start, color_start,
color_end, color_end,
v[CONF_LOCAL], v[CONF_LOCAL],
v[CONF_WIDTH], size.process(v[CONF_WIDTH]),
), ),
) )
if t == CONF_IMAGE: if t == CONF_IMAGE:

View File

@ -55,7 +55,8 @@ void MQTTAlarmControlPanelComponent::dump_config() {
} }
void MQTTAlarmControlPanelComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTAlarmControlPanelComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
JsonArray supported_features = root.createNestedArray(MQTT_SUPPORTED_FEATURES); // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
JsonArray supported_features = root[MQTT_SUPPORTED_FEATURES].to<JsonArray>();
const uint32_t acp_supported_features = this->alarm_control_panel_->get_supported_features(); const uint32_t acp_supported_features = this->alarm_control_panel_->get_supported_features();
if (acp_supported_features & ACP_FEAT_ARM_AWAY) { if (acp_supported_features & ACP_FEAT_ARM_AWAY) {
supported_features.add("arm_away"); supported_features.add("arm_away");

View File

@ -30,6 +30,7 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor
} }
void MQTTBinarySensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTBinarySensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (!this->binary_sensor_->get_device_class().empty()) if (!this->binary_sensor_->get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->binary_sensor_->get_device_class(); root[MQTT_DEVICE_CLASS] = this->binary_sensor_->get_device_class();
if (this->binary_sensor_->is_status_binary_sensor()) if (this->binary_sensor_->is_status_binary_sensor())

View File

@ -31,9 +31,12 @@ void MQTTButtonComponent::dump_config() {
} }
void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
config.state_topic = false; config.state_topic = false;
if (!this->button_->get_device_class().empty()) if (!this->button_->get_device_class().empty()) {
root[MQTT_DEVICE_CLASS] = this->button_->get_device_class(); root[MQTT_DEVICE_CLASS] = this->button_->get_device_class();
}
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
} }
std::string MQTTButtonComponent::component_type() const { return "button"; } std::string MQTTButtonComponent::component_type() const { return "button"; }

View File

@ -92,6 +92,7 @@ void MQTTClientComponent::send_device_info_() {
std::string topic = "esphome/discover/"; std::string topic = "esphome/discover/";
topic.append(App.get_name()); topic.append(App.get_name());
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
this->publish_json( this->publish_json(
topic, topic,
[](JsonObject root) { [](JsonObject root) {
@ -147,6 +148,7 @@ void MQTTClientComponent::send_device_info_() {
#endif #endif
}, },
2, this->discovery_info_.retain); 2, this->discovery_info_.retain);
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
} }
void MQTTClientComponent::dump_config() { void MQTTClientComponent::dump_config() {

View File

@ -14,6 +14,7 @@ static const char *const TAG = "mqtt.climate";
using namespace esphome::climate; using namespace esphome::climate;
void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
auto traits = this->device_->get_traits(); auto traits = this->device_->get_traits();
// current_temperature_topic // current_temperature_topic
if (traits.get_supports_current_temperature()) { if (traits.get_supports_current_temperature()) {
@ -28,7 +29,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
// mode_state_topic // mode_state_topic
root[MQTT_MODE_STATE_TOPIC] = this->get_mode_state_topic(); root[MQTT_MODE_STATE_TOPIC] = this->get_mode_state_topic();
// modes // modes
JsonArray modes = root.createNestedArray(MQTT_MODES); JsonArray modes = root[MQTT_MODES].to<JsonArray>();
// sort array for nice UI in HA // sort array for nice UI in HA
if (traits.supports_mode(CLIMATE_MODE_AUTO)) if (traits.supports_mode(CLIMATE_MODE_AUTO))
modes.add("auto"); modes.add("auto");
@ -89,7 +90,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
// preset_mode_state_topic // preset_mode_state_topic
root[MQTT_PRESET_MODE_STATE_TOPIC] = this->get_preset_state_topic(); root[MQTT_PRESET_MODE_STATE_TOPIC] = this->get_preset_state_topic();
// presets // presets
JsonArray presets = root.createNestedArray("preset_modes"); JsonArray presets = root["preset_modes"].to<JsonArray>();
if (traits.supports_preset(CLIMATE_PRESET_HOME)) if (traits.supports_preset(CLIMATE_PRESET_HOME))
presets.add("home"); presets.add("home");
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) if (traits.supports_preset(CLIMATE_PRESET_AWAY))
@ -119,7 +120,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
// fan_mode_state_topic // fan_mode_state_topic
root[MQTT_FAN_MODE_STATE_TOPIC] = this->get_fan_mode_state_topic(); root[MQTT_FAN_MODE_STATE_TOPIC] = this->get_fan_mode_state_topic();
// fan_modes // fan_modes
JsonArray fan_modes = root.createNestedArray("fan_modes"); JsonArray fan_modes = root["fan_modes"].to<JsonArray>();
if (traits.supports_fan_mode(CLIMATE_FAN_ON)) if (traits.supports_fan_mode(CLIMATE_FAN_ON))
fan_modes.add("on"); fan_modes.add("on");
if (traits.supports_fan_mode(CLIMATE_FAN_OFF)) if (traits.supports_fan_mode(CLIMATE_FAN_OFF))
@ -150,7 +151,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
// swing_mode_state_topic // swing_mode_state_topic
root[MQTT_SWING_MODE_STATE_TOPIC] = this->get_swing_mode_state_topic(); root[MQTT_SWING_MODE_STATE_TOPIC] = this->get_swing_mode_state_topic();
// swing_modes // swing_modes
JsonArray swing_modes = root.createNestedArray("swing_modes"); JsonArray swing_modes = root["swing_modes"].to<JsonArray>();
if (traits.supports_swing_mode(CLIMATE_SWING_OFF)) if (traits.supports_swing_mode(CLIMATE_SWING_OFF))
swing_modes.add("off"); swing_modes.add("off");
if (traits.supports_swing_mode(CLIMATE_SWING_BOTH)) if (traits.supports_swing_mode(CLIMATE_SWING_BOTH))
@ -163,6 +164,7 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
config.state_topic = false; config.state_topic = false;
config.command_topic = false; config.command_topic = false;
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
} }
void MQTTClimateComponent::setup() { void MQTTClimateComponent::setup() {
auto traits = this->device_->get_traits(); auto traits = this->device_->get_traits();

View File

@ -70,6 +70,7 @@ bool MQTTComponent::send_discovery_() {
ESP_LOGV(TAG, "'%s': Sending discovery", this->friendly_name().c_str()); ESP_LOGV(TAG, "'%s': Sending discovery", this->friendly_name().c_str());
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
return global_mqtt_client->publish_json( return global_mqtt_client->publish_json(
this->get_discovery_topic_(discovery_info), this->get_discovery_topic_(discovery_info),
[this](JsonObject root) { [this](JsonObject root) {
@ -155,7 +156,7 @@ bool MQTTComponent::send_discovery_() {
} }
std::string node_area = App.get_area(); std::string node_area = App.get_area();
JsonObject device_info = root.createNestedObject(MQTT_DEVICE); JsonObject device_info = root[MQTT_DEVICE].to<JsonObject>();
const auto mac = get_mac_address(); const auto mac = get_mac_address();
device_info[MQTT_DEVICE_IDENTIFIERS] = mac; device_info[MQTT_DEVICE_IDENTIFIERS] = mac;
device_info[MQTT_DEVICE_NAME] = node_friendly_name; device_info[MQTT_DEVICE_NAME] = node_friendly_name;
@ -192,6 +193,7 @@ bool MQTTComponent::send_discovery_() {
device_info[MQTT_DEVICE_CONNECTIONS][0][1] = mac; device_info[MQTT_DEVICE_CONNECTIONS][0][1] = mac;
}, },
this->qos_, discovery_info.retain); this->qos_, discovery_info.retain);
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
} }
uint8_t MQTTComponent::get_qos() const { return this->qos_; } uint8_t MQTTComponent::get_qos() const { return this->qos_; }

View File

@ -67,6 +67,7 @@ void MQTTCoverComponent::dump_config() {
} }
} }
void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (!this->cover_->get_device_class().empty()) if (!this->cover_->get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->cover_->get_device_class(); root[MQTT_DEVICE_CLASS] = this->cover_->get_device_class();

View File

@ -20,13 +20,13 @@ MQTTDateComponent::MQTTDateComponent(DateEntity *date) : date_(date) {}
void MQTTDateComponent::setup() { void MQTTDateComponent::setup() {
this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) {
auto call = this->date_->make_call(); auto call = this->date_->make_call();
if (root.containsKey("year")) { if (root["year"].is<uint16_t>()) {
call.set_year(root["year"]); call.set_year(root["year"]);
} }
if (root.containsKey("month")) { if (root["month"].is<uint8_t>()) {
call.set_month(root["month"]); call.set_month(root["month"]);
} }
if (root.containsKey("day")) { if (root["day"].is<uint8_t>()) {
call.set_day(root["day"]); call.set_day(root["day"]);
} }
call.perform(); call.perform();
@ -55,6 +55,7 @@ bool MQTTDateComponent::send_initial_state() {
} }
bool MQTTDateComponent::publish_state(uint16_t year, uint8_t month, uint8_t day) { bool MQTTDateComponent::publish_state(uint16_t year, uint8_t month, uint8_t day) {
return this->publish_json(this->get_state_topic_(), [year, month, day](JsonObject root) { return this->publish_json(this->get_state_topic_(), [year, month, day](JsonObject root) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
root["year"] = year; root["year"] = year;
root["month"] = month; root["month"] = month;
root["day"] = day; root["day"] = day;

View File

@ -20,22 +20,22 @@ MQTTDateTimeComponent::MQTTDateTimeComponent(DateTimeEntity *datetime) : datetim
void MQTTDateTimeComponent::setup() { void MQTTDateTimeComponent::setup() {
this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) {
auto call = this->datetime_->make_call(); auto call = this->datetime_->make_call();
if (root.containsKey("year")) { if (root["year"].is<uint16_t>()) {
call.set_year(root["year"]); call.set_year(root["year"]);
} }
if (root.containsKey("month")) { if (root["month"].is<uint8_t>()) {
call.set_month(root["month"]); call.set_month(root["month"]);
} }
if (root.containsKey("day")) { if (root["day"].is<uint8_t>()) {
call.set_day(root["day"]); call.set_day(root["day"]);
} }
if (root.containsKey("hour")) { if (root["hour"].is<uint8_t>()) {
call.set_hour(root["hour"]); call.set_hour(root["hour"]);
} }
if (root.containsKey("minute")) { if (root["minute"].is<uint8_t>()) {
call.set_minute(root["minute"]); call.set_minute(root["minute"]);
} }
if (root.containsKey("second")) { if (root["second"].is<uint8_t>()) {
call.set_second(root["second"]); call.set_second(root["second"]);
} }
call.perform(); call.perform();
@ -68,6 +68,7 @@ bool MQTTDateTimeComponent::send_initial_state() {
bool MQTTDateTimeComponent::publish_state(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, bool MQTTDateTimeComponent::publish_state(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute,
uint8_t second) { uint8_t second) {
return this->publish_json(this->get_state_topic_(), [year, month, day, hour, minute, second](JsonObject root) { return this->publish_json(this->get_state_topic_(), [year, month, day, hour, minute, second](JsonObject root) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
root["year"] = year; root["year"] = year;
root["month"] = month; root["month"] = month;
root["day"] = day; root["day"] = day;

View File

@ -16,7 +16,8 @@ using namespace esphome::event;
MQTTEventComponent::MQTTEventComponent(event::Event *event) : event_(event) {} MQTTEventComponent::MQTTEventComponent(event::Event *event) : event_(event) {}
void MQTTEventComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTEventComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
JsonArray event_types = root.createNestedArray(MQTT_EVENT_TYPES); // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
JsonArray event_types = root[MQTT_EVENT_TYPES].to<JsonArray>();
for (const auto &event_type : this->event_->get_event_types()) for (const auto &event_type : this->event_->get_event_types())
event_types.add(event_type); event_types.add(event_type);
@ -40,8 +41,10 @@ void MQTTEventComponent::dump_config() {
} }
bool MQTTEventComponent::publish_event_(const std::string &event_type) { bool MQTTEventComponent::publish_event_(const std::string &event_type) {
return this->publish_json(this->get_state_topic_(), return this->publish_json(this->get_state_topic_(), [event_type](JsonObject root) {
[event_type](JsonObject root) { root[MQTT_EVENT_TYPE] = event_type; }); // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
root[MQTT_EVENT_TYPE] = event_type;
});
} }
std::string MQTTEventComponent::component_type() const { return "event"; } std::string MQTTEventComponent::component_type() const { return "event"; }

View File

@ -143,6 +143,7 @@ void MQTTFanComponent::dump_config() {
bool MQTTFanComponent::send_initial_state() { return this->publish_state(); } bool MQTTFanComponent::send_initial_state() { return this->publish_state(); }
void MQTTFanComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTFanComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (this->state_->get_traits().supports_direction()) { if (this->state_->get_traits().supports_direction()) {
root[MQTT_DIRECTION_COMMAND_TOPIC] = this->get_direction_command_topic(); root[MQTT_DIRECTION_COMMAND_TOPIC] = this->get_direction_command_topic();
root[MQTT_DIRECTION_STATE_TOPIC] = this->get_direction_state_topic(); root[MQTT_DIRECTION_STATE_TOPIC] = this->get_direction_state_topic();

View File

@ -32,17 +32,21 @@ void MQTTJSONLightComponent::setup() {
MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : state_(state) {} MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : state_(state) {}
bool MQTTJSONLightComponent::publish_state_() { bool MQTTJSONLightComponent::publish_state_() {
return this->publish_json(this->get_state_topic_(), return this->publish_json(this->get_state_topic_(), [this](JsonObject root) {
[this](JsonObject root) { LightJSONSchema::dump_json(*this->state_, root); }); // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
LightJSONSchema::dump_json(*this->state_, root);
});
} }
LightState *MQTTJSONLightComponent::get_state() const { return this->state_; } LightState *MQTTJSONLightComponent::get_state() const { return this->state_; }
void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
root["schema"] = "json"; root["schema"] = "json";
auto traits = this->state_->get_traits(); auto traits = this->state_->get_traits();
root[MQTT_COLOR_MODE] = true; root[MQTT_COLOR_MODE] = true;
JsonArray color_modes = root.createNestedArray("supported_color_modes"); // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
JsonArray color_modes = root["supported_color_modes"].to<JsonArray>();
if (traits.supports_color_mode(ColorMode::ON_OFF)) if (traits.supports_color_mode(ColorMode::ON_OFF))
color_modes.add("onoff"); color_modes.add("onoff");
if (traits.supports_color_mode(ColorMode::BRIGHTNESS)) if (traits.supports_color_mode(ColorMode::BRIGHTNESS))
@ -67,7 +71,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscovery
if (this->state_->supports_effects()) { if (this->state_->supports_effects()) {
root["effect"] = true; root["effect"] = true;
JsonArray effect_list = root.createNestedArray(MQTT_EFFECT_LIST); JsonArray effect_list = root[MQTT_EFFECT_LIST].to<JsonArray>();
for (auto *effect : this->state_->get_effects()) for (auto *effect : this->state_->get_effects())
effect_list.add(effect->get_name()); effect_list.add(effect->get_name());
effect_list.add("None"); effect_list.add("None");

View File

@ -38,8 +38,10 @@ void MQTTLockComponent::dump_config() {
std::string MQTTLockComponent::component_type() const { return "lock"; } std::string MQTTLockComponent::component_type() const { return "lock"; }
const EntityBase *MQTTLockComponent::get_entity() const { return this->lock_; } const EntityBase *MQTTLockComponent::get_entity() const { return this->lock_; }
void MQTTLockComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTLockComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
if (this->lock_->traits.get_assumed_state()) // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (this->lock_->traits.get_assumed_state()) {
root[MQTT_OPTIMISTIC] = true; root[MQTT_OPTIMISTIC] = true;
}
if (this->lock_->traits.get_supports_open()) if (this->lock_->traits.get_supports_open())
root[MQTT_PAYLOAD_OPEN] = "OPEN"; root[MQTT_PAYLOAD_OPEN] = "OPEN";
} }

View File

@ -40,6 +40,7 @@ const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_
void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
const auto &traits = number_->traits; const auto &traits = number_->traits;
// https://www.home-assistant.io/integrations/number.mqtt/ // https://www.home-assistant.io/integrations/number.mqtt/
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
root[MQTT_MIN] = traits.get_min_value(); root[MQTT_MIN] = traits.get_min_value();
root[MQTT_MAX] = traits.get_max_value(); root[MQTT_MAX] = traits.get_max_value();
root[MQTT_STEP] = traits.get_step(); root[MQTT_STEP] = traits.get_step();

View File

@ -35,7 +35,8 @@ const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_
void MQTTSelectComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTSelectComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
const auto &traits = select_->traits; const auto &traits = select_->traits;
// https://www.home-assistant.io/integrations/select.mqtt/ // https://www.home-assistant.io/integrations/select.mqtt/
JsonArray options = root.createNestedArray(MQTT_OPTIONS); // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
JsonArray options = root[MQTT_OPTIONS].to<JsonArray>();
for (const auto &option : traits.get_options()) for (const auto &option : traits.get_options())
options.add(option); options.add(option);

View File

@ -44,8 +44,10 @@ void MQTTSensorComponent::set_expire_after(uint32_t expire_after) { this->expire
void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; }
void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
if (!this->sensor_->get_device_class().empty()) // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (!this->sensor_->get_device_class().empty()) {
root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class();
}
if (!this->sensor_->get_unit_of_measurement().empty()) if (!this->sensor_->get_unit_of_measurement().empty())
root[MQTT_UNIT_OF_MEASUREMENT] = this->sensor_->get_unit_of_measurement(); root[MQTT_UNIT_OF_MEASUREMENT] = this->sensor_->get_unit_of_measurement();

View File

@ -45,8 +45,10 @@ void MQTTSwitchComponent::dump_config() {
std::string MQTTSwitchComponent::component_type() const { return "switch"; } std::string MQTTSwitchComponent::component_type() const { return "switch"; }
const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; } const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; }
void MQTTSwitchComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTSwitchComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
if (this->switch_->assumed_state()) // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (this->switch_->assumed_state()) {
root[MQTT_OPTIMISTIC] = true; root[MQTT_OPTIMISTIC] = true;
}
} }
bool MQTTSwitchComponent::send_initial_state() { return this->publish_state(this->switch_->state); } bool MQTTSwitchComponent::send_initial_state() { return this->publish_state(this->switch_->state); }

View File

@ -34,6 +34,7 @@ std::string MQTTTextComponent::component_type() const { return "text"; }
const EntityBase *MQTTTextComponent::get_entity() const { return this->text_; } const EntityBase *MQTTTextComponent::get_entity() const { return this->text_; }
void MQTTTextComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTTextComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
switch (this->text_->traits.get_mode()) { switch (this->text_->traits.get_mode()) {
case TEXT_MODE_TEXT: case TEXT_MODE_TEXT:
root[MQTT_MODE] = "text"; root[MQTT_MODE] = "text";

View File

@ -15,8 +15,10 @@ using namespace esphome::text_sensor;
MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {} MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {}
void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
if (!this->sensor_->get_device_class().empty()) // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (!this->sensor_->get_device_class().empty()) {
root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class();
}
config.command_topic = false; config.command_topic = false;
} }
void MQTTTextSensor::setup() { void MQTTTextSensor::setup() {

View File

@ -20,13 +20,13 @@ MQTTTimeComponent::MQTTTimeComponent(TimeEntity *time) : time_(time) {}
void MQTTTimeComponent::setup() { void MQTTTimeComponent::setup() {
this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) { this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) {
auto call = this->time_->make_call(); auto call = this->time_->make_call();
if (root.containsKey("hour")) { if (root["hour"].is<uint8_t>()) {
call.set_hour(root["hour"]); call.set_hour(root["hour"]);
} }
if (root.containsKey("minute")) { if (root["minute"].is<uint8_t>()) {
call.set_minute(root["minute"]); call.set_minute(root["minute"]);
} }
if (root.containsKey("second")) { if (root["second"].is<uint8_t>()) {
call.set_second(root["second"]); call.set_second(root["second"]);
} }
call.perform(); call.perform();
@ -55,6 +55,7 @@ bool MQTTTimeComponent::send_initial_state() {
} }
bool MQTTTimeComponent::publish_state(uint8_t hour, uint8_t minute, uint8_t second) { bool MQTTTimeComponent::publish_state(uint8_t hour, uint8_t minute, uint8_t second) {
return this->publish_json(this->get_state_topic_(), [hour, minute, second](JsonObject root) { return this->publish_json(this->get_state_topic_(), [hour, minute, second](JsonObject root) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
root["hour"] = hour; root["hour"] = hour;
root["minute"] = minute; root["minute"] = minute;
root["second"] = second; root["second"] = second;

View File

@ -41,6 +41,7 @@ bool MQTTUpdateComponent::publish_state() {
} }
void MQTTUpdateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTUpdateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
root["schema"] = "json"; root["schema"] = "json";
root[MQTT_PAYLOAD_INSTALL] = "INSTALL"; root[MQTT_PAYLOAD_INSTALL] = "INSTALL";
} }

View File

@ -49,8 +49,10 @@ void MQTTValveComponent::dump_config() {
} }
} }
void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
if (!this->valve_->get_device_class().empty()) // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (!this->valve_->get_device_class().empty()) {
root[MQTT_DEVICE_CLASS] = this->valve_->get_device_class(); root[MQTT_DEVICE_CLASS] = this->valve_->get_device_class();
}
auto traits = this->valve_->get_traits(); auto traits = this->valve_->get_traits();
if (traits.get_is_assumed_state()) { if (traits.get_is_assumed_state()) {

View File

@ -356,7 +356,7 @@ void MS8607Component::read_humidity_(float temperature_float) {
// map 16 bit humidity value into range [-6%, 118%] // map 16 bit humidity value into range [-6%, 118%]
float const humidity_partial = double(humidity) / (1 << 16); float const humidity_partial = double(humidity) / (1 << 16);
float const humidity_percentage = lerp(humidity_partial, -6.0, 118.0); float const humidity_percentage = std::lerp(-6.0, 118.0, humidity_partial);
float const compensated_humidity_percentage = float const compensated_humidity_percentage =
humidity_percentage + (20 - temperature_float) * MS8607_H_TEMP_COEFFICIENT; humidity_percentage + (20 - temperature_float) * MS8607_H_TEMP_COEFFICIENT;
ESP_LOGD(TAG, "Compensated for temperature, humidity=%.2f%%", compensated_humidity_percentage); ESP_LOGD(TAG, "Compensated for temperature, humidity=%.2f%%", compensated_humidity_percentage);

View File

@ -2,7 +2,7 @@ import logging
from esphome import automation from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.const import CONF_REQUEST_HEADERS from esphome.components.const import CONF_BYTE_ORDER, CONF_REQUEST_HEADERS
from esphome.components.http_request import CONF_HTTP_REQUEST_ID, HttpRequestComponent from esphome.components.http_request import CONF_HTTP_REQUEST_ID, HttpRequestComponent
from esphome.components.image import ( from esphome.components.image import (
CONF_INVERT_ALPHA, CONF_INVERT_ALPHA,
@ -11,6 +11,7 @@ from esphome.components.image import (
Image_, Image_,
get_image_type_enum, get_image_type_enum,
get_transparency_enum, get_transparency_enum,
validate_settings,
) )
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
@ -161,6 +162,7 @@ CONFIG_SCHEMA = cv.Schema(
rp2040_arduino=cv.Version(0, 0, 0), rp2040_arduino=cv.Version(0, 0, 0),
host=cv.Version(0, 0, 0), host=cv.Version(0, 0, 0),
), ),
validate_settings,
) )
) )
@ -213,6 +215,7 @@ async def to_code(config):
get_image_type_enum(config[CONF_TYPE]), get_image_type_enum(config[CONF_TYPE]),
transparent, transparent,
config[CONF_BUFFER_SIZE], config[CONF_BUFFER_SIZE],
config.get(CONF_BYTE_ORDER) != "LITTLE_ENDIAN",
) )
await cg.register_component(var, config) await cg.register_component(var, config)
await cg.register_parented(var, config[CONF_HTTP_REQUEST_ID]) await cg.register_parented(var, config[CONF_HTTP_REQUEST_ID])

View File

@ -35,14 +35,15 @@ inline bool is_color_on(const Color &color) {
} }
OnlineImage::OnlineImage(const std::string &url, int width, int height, ImageFormat format, ImageType type, OnlineImage::OnlineImage(const std::string &url, int width, int height, ImageFormat format, ImageType type,
image::Transparency transparency, uint32_t download_buffer_size) image::Transparency transparency, uint32_t download_buffer_size, bool is_big_endian)
: Image(nullptr, 0, 0, type, transparency), : Image(nullptr, 0, 0, type, transparency),
buffer_(nullptr), buffer_(nullptr),
download_buffer_(download_buffer_size), download_buffer_(download_buffer_size),
download_buffer_initial_size_(download_buffer_size), download_buffer_initial_size_(download_buffer_size),
format_(format), format_(format),
fixed_width_(width), fixed_width_(width),
fixed_height_(height) { fixed_height_(height),
is_big_endian_(is_big_endian) {
this->set_url(url); this->set_url(url);
} }
@ -296,7 +297,7 @@ void OnlineImage::draw_pixel_(int x, int y, Color color) {
break; break;
} }
case ImageType::IMAGE_TYPE_GRAYSCALE: { case ImageType::IMAGE_TYPE_GRAYSCALE: {
uint8_t gray = static_cast<uint8_t>(0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b); auto gray = static_cast<uint8_t>(0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b);
if (this->transparency_ == image::TRANSPARENCY_CHROMA_KEY) { if (this->transparency_ == image::TRANSPARENCY_CHROMA_KEY) {
if (gray == 1) { if (gray == 1) {
gray = 0; gray = 0;
@ -314,8 +315,13 @@ void OnlineImage::draw_pixel_(int x, int y, Color color) {
case ImageType::IMAGE_TYPE_RGB565: { case ImageType::IMAGE_TYPE_RGB565: {
this->map_chroma_key(color); this->map_chroma_key(color);
uint16_t col565 = display::ColorUtil::color_to_565(color); uint16_t col565 = display::ColorUtil::color_to_565(color);
this->buffer_[pos + 0] = static_cast<uint8_t>((col565 >> 8) & 0xFF); if (this->is_big_endian_) {
this->buffer_[pos + 1] = static_cast<uint8_t>(col565 & 0xFF); this->buffer_[pos + 0] = static_cast<uint8_t>((col565 >> 8) & 0xFF);
this->buffer_[pos + 1] = static_cast<uint8_t>(col565 & 0xFF);
} else {
this->buffer_[pos + 0] = static_cast<uint8_t>(col565 & 0xFF);
this->buffer_[pos + 1] = static_cast<uint8_t>((col565 >> 8) & 0xFF);
}
if (this->transparency_ == image::TRANSPARENCY_ALPHA_CHANNEL) { if (this->transparency_ == image::TRANSPARENCY_ALPHA_CHANNEL) {
this->buffer_[pos + 2] = color.w; this->buffer_[pos + 2] = color.w;
} }

View File

@ -50,7 +50,7 @@ class OnlineImage : public PollingComponent,
* @param buffer_size Size of the buffer used to download the image. * @param buffer_size Size of the buffer used to download the image.
*/ */
OnlineImage(const std::string &url, int width, int height, ImageFormat format, image::ImageType type, OnlineImage(const std::string &url, int width, int height, ImageFormat format, image::ImageType type,
image::Transparency transparency, uint32_t buffer_size); image::Transparency transparency, uint32_t buffer_size, bool is_big_endian);
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override; void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override;
@ -164,6 +164,11 @@ class OnlineImage : public PollingComponent,
const int fixed_width_; const int fixed_width_;
/** height requested on configuration, or 0 if non specified. */ /** height requested on configuration, or 0 if non specified. */
const int fixed_height_; const int fixed_height_;
/**
* Whether the image is stored in big-endian format.
* This is used to determine how to store 16 bit colors in the buffer.
*/
bool is_big_endian_;
/** /**
* Actual width of the current image. If fixed_width_ is specified, * Actual width of the current image. If fixed_width_ is specified,
* this will be equal to it; otherwise it will be set once the decoding * this will be equal to it; otherwise it will be set once the decoding

View File

@ -10,7 +10,7 @@ void opentherm::OpenthermOutput::write_state(float state) {
ESP_LOGD(TAG, "Received state: %.2f. Min value: %.2f, max value: %.2f", state, min_value_, max_value_); ESP_LOGD(TAG, "Received state: %.2f. Min value: %.2f, max value: %.2f", state, min_value_, max_value_);
this->state = state < 0.003 && this->zero_means_zero_ this->state = state < 0.003 && this->zero_means_zero_
? 0.0 ? 0.0
: clamp(lerp(state, min_value_, max_value_), min_value_, max_value_); : clamp(std::lerp(min_value_, max_value_, state), min_value_, max_value_);
this->has_state_ = true; this->has_state_ = true;
ESP_LOGD(TAG, "Output %s set to %.2f", this->id_, this->state); ESP_LOGD(TAG, "Output %s set to %.2f", this->id_, this->state);
} }

View File

@ -1,11 +1,7 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor from esphome.components import i2c, sensor
from esphome.const import ( import esphome.config_validation as cv
DEVICE_CLASS_ILLUMINANCE, from esphome.const import DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT, UNIT_LUX
STATE_CLASS_MEASUREMENT,
UNIT_LUX,
)
DEPENDENCIES = ["i2c"] DEPENDENCIES = ["i2c"]
CODEOWNERS = ["@ccutrer"] CODEOWNERS = ["@ccutrer"]

View File

@ -314,6 +314,9 @@ void PacketTransport::send_data_(bool all) {
} }
void PacketTransport::update() { void PacketTransport::update() {
if (!this->ping_pong_enable_) {
return;
}
auto now = millis() / 1000; auto now = millis() / 1000;
if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) { if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) {
this->resend_ping_key_ = this->ping_pong_enable_; this->resend_ping_key_ = this->ping_pong_enable_;

View File

@ -88,9 +88,9 @@ void Servo::internal_write(float value) {
value = clamp(value, -1.0f, 1.0f); value = clamp(value, -1.0f, 1.0f);
float level; float level;
if (value < 0.0) { if (value < 0.0) {
level = lerp(-value, this->idle_level_, this->min_level_); level = std::lerp(this->idle_level_, this->min_level_, -value);
} else { } else {
level = lerp(value, this->idle_level_, this->max_level_); level = std::lerp(this->idle_level_, this->max_level_, value);
} }
this->output_->set_level(level); this->output_->set_level(level);
this->current_value_ = value; this->current_value_ = value;

View File

@ -5,13 +5,8 @@ from esphome.config_helpers import Extend, Remove, merge_config
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_SUBSTITUTIONS, VALID_SUBSTITUTIONS_CHARACTERS from esphome.const import CONF_SUBSTITUTIONS, VALID_SUBSTITUTIONS_CHARACTERS
from esphome.yaml_util import ESPHomeDataBase, make_data_base from esphome.yaml_util import ESPHomeDataBase, make_data_base
from .jinja import (
Jinja, from .jinja import Jinja, JinjaStr, TemplateError, TemplateRuntimeError, has_jinja
JinjaStr,
has_jinja,
TemplateError,
TemplateRuntimeError,
)
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -151,8 +146,11 @@ def _substitute_item(substitutions, item, path, jinja, ignore_missing):
if sub is not None: if sub is not None:
item[k] = sub item[k] = sub
for old, new in replace_keys: for old, new in replace_keys:
item[new] = merge_config(item.get(old), item.get(new)) if str(new) == str(old):
del item[old] item[new] = item[old]
else:
item[new] = merge_config(item.get(old), item.get(new))
del item[old]
elif isinstance(item, str): elif isinstance(item, str):
sub = _expand_substitutions(substitutions, item, path, jinja, ignore_missing) sub = _expand_substitutions(substitutions, item, path, jinja, ignore_missing)
if isinstance(sub, JinjaStr) or sub != item: if isinstance(sub, JinjaStr) or sub != item:

View File

@ -1,6 +1,7 @@
import logging import logging
import math import math
import re import re
import jinja2 as jinja import jinja2 as jinja
from jinja2.nativetypes import NativeEnvironment from jinja2.nativetypes import NativeEnvironment

View File

@ -167,8 +167,8 @@ def validate_config(config):
if config[CONF_MODULATION] == "LORA": if config[CONF_MODULATION] == "LORA":
if config[CONF_BANDWIDTH] not in lora_bws: if config[CONF_BANDWIDTH] not in lora_bws:
raise cv.Invalid(f"{config[CONF_BANDWIDTH]} is not available with LORA") raise cv.Invalid(f"{config[CONF_BANDWIDTH]} is not available with LORA")
if config[CONF_PREAMBLE_SIZE] > 0 and config[CONF_PREAMBLE_SIZE] < 6: if config[CONF_PREAMBLE_SIZE] < 6:
raise cv.Invalid("Minimum preamble size is 6 with LORA") raise cv.Invalid("Minimum 'preamble_size' is 6 with LORA")
if config[CONF_SPREADING_FACTOR] == 6 and config[CONF_PAYLOAD_LENGTH] == 0: if config[CONF_SPREADING_FACTOR] == 6 and config[CONF_PAYLOAD_LENGTH] == 0:
raise cv.Invalid("Payload length must be set when spreading factor is 6") raise cv.Invalid("Payload length must be set when spreading factor is 6")
else: else:
@ -200,7 +200,7 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_PA_RAMP, default="40us"): cv.enum(RAMP), cv.Optional(CONF_PA_RAMP, default="40us"): cv.enum(RAMP),
cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range(min=0, max=256), cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range(min=0, max=256),
cv.Optional(CONF_PREAMBLE_DETECT, default=2): cv.int_range(min=0, max=4), cv.Optional(CONF_PREAMBLE_DETECT, default=2): cv.int_range(min=0, max=4),
cv.Required(CONF_PREAMBLE_SIZE): cv.int_range(min=1, max=65535), cv.Optional(CONF_PREAMBLE_SIZE, default=8): cv.int_range(min=1, max=65535),
cv.Required(CONF_RST_PIN): pins.internal_gpio_output_pin_schema, cv.Required(CONF_RST_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_RX_START, default=True): cv.boolean, cv.Optional(CONF_RX_START, default=True): cv.boolean,
cv.Required(CONF_RF_SWITCH): cv.boolean, cv.Required(CONF_RF_SWITCH): cv.boolean,

View File

@ -164,8 +164,8 @@ def validate_config(config):
raise cv.Invalid(f"{config[CONF_BANDWIDTH]} is not available with LORA") raise cv.Invalid(f"{config[CONF_BANDWIDTH]} is not available with LORA")
if CONF_DIO0_PIN not in config: if CONF_DIO0_PIN not in config:
raise cv.Invalid("Cannot use LoRa without dio0_pin") raise cv.Invalid("Cannot use LoRa without dio0_pin")
if 0 < config[CONF_PREAMBLE_SIZE] < 6: if config[CONF_PREAMBLE_SIZE] < 6:
raise cv.Invalid("Minimum preamble size is 6 with LORA") raise cv.Invalid("Minimum 'preamble_size' is 6 with LORA")
if config[CONF_SPREADING_FACTOR] == 6 and config[CONF_PAYLOAD_LENGTH] == 0: if config[CONF_SPREADING_FACTOR] == 6 and config[CONF_PAYLOAD_LENGTH] == 0:
raise cv.Invalid("Payload length must be set when spreading factor is 6") raise cv.Invalid("Payload length must be set when spreading factor is 6")
else: else:

View File

@ -70,7 +70,7 @@ static void usbh_print_cfg_desc(const usb_config_desc_t *cfg_desc) {
ESP_LOGV(TAG, "bMaxPower %dmA", cfg_desc->bMaxPower * 2); ESP_LOGV(TAG, "bMaxPower %dmA", cfg_desc->bMaxPower * 2);
} }
void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) { static void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) {
if (devc_desc == NULL) { if (devc_desc == NULL) {
return; return;
} }
@ -92,8 +92,8 @@ void usb_client_print_device_descriptor(const usb_device_desc_t *devc_desc) {
ESP_LOGV(TAG, "bNumConfigurations %d", devc_desc->bNumConfigurations); ESP_LOGV(TAG, "bNumConfigurations %d", devc_desc->bNumConfigurations);
} }
void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc, static void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc,
print_class_descriptor_cb class_specific_cb) { print_class_descriptor_cb class_specific_cb) {
if (cfg_desc == nullptr) { if (cfg_desc == nullptr) {
return; return;
} }
@ -128,9 +128,9 @@ void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc,
static std::string get_descriptor_string(const usb_str_desc_t *desc) { static std::string get_descriptor_string(const usb_str_desc_t *desc) {
char buffer[256]; char buffer[256];
if (desc == nullptr) if (desc == nullptr)
return "(unknown)"; return "(unspecified)";
char *p = buffer; char *p = buffer;
for (size_t i = 0; i != desc->bLength / 2; i++) { for (int i = 0; i != desc->bLength / 2; i++) {
auto c = desc->wData[i]; auto c = desc->wData[i];
if (c < 0x100) if (c < 0x100)
*p++ = static_cast<char>(c); *p++ = static_cast<char>(c);
@ -169,7 +169,7 @@ void USBClient::setup() {
this->mark_failed(); this->mark_failed();
return; return;
} }
for (auto trq : this->trq_pool_) { for (auto *trq : this->trq_pool_) {
usb_host_transfer_alloc(64, 0, &trq->transfer); usb_host_transfer_alloc(64, 0, &trq->transfer);
trq->client = this; trq->client = this;
} }
@ -197,7 +197,8 @@ void USBClient::loop() {
ESP_LOGD(TAG, "Device descriptor: vid %X pid %X", desc->idVendor, desc->idProduct); ESP_LOGD(TAG, "Device descriptor: vid %X pid %X", desc->idVendor, desc->idProduct);
if (desc->idVendor == this->vid_ && desc->idProduct == this->pid_ || this->vid_ == 0 && this->pid_ == 0) { if (desc->idVendor == this->vid_ && desc->idProduct == this->pid_ || this->vid_ == 0 && this->pid_ == 0) {
usb_device_info_t dev_info; usb_device_info_t dev_info;
if ((err = usb_host_device_info(this->device_handle_, &dev_info)) != ESP_OK) { err = usb_host_device_info(this->device_handle_, &dev_info);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Device info failed: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "Device info failed: %s", esp_err_to_name(err));
this->disconnect(); this->disconnect();
break; break;
@ -336,7 +337,7 @@ static void transfer_callback(usb_transfer_t *xfer) {
* @throws None. * @throws None.
*/ */
void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) { void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length) {
auto trq = this->get_trq_(); auto *trq = this->get_trq_();
if (trq == nullptr) { if (trq == nullptr) {
ESP_LOGE(TAG, "Too many requests queued"); ESP_LOGE(TAG, "Too many requests queued");
return; return;
@ -349,7 +350,6 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err); ESP_LOGE(TAG, "Failed to submit transfer, address=%x, length=%d, err=%x", ep_address, length, err);
this->release_trq(trq); this->release_trq(trq);
this->disconnect();
} }
} }
@ -364,7 +364,7 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u
* @throws None. * @throws None.
*/ */
void USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) { void USBClient::transfer_out(uint8_t ep_address, const transfer_cb_t &callback, const uint8_t *data, uint16_t length) {
auto trq = this->get_trq_(); auto *trq = this->get_trq_();
if (trq == nullptr) { if (trq == nullptr) {
ESP_LOGE(TAG, "Too many requests queued"); ESP_LOGE(TAG, "Too many requests queued");
return; return;

View File

@ -43,7 +43,7 @@ static constexpr uint8_t SET_BAUDRATE = 0x1E; // Set the baud rate.
static constexpr uint8_t SET_CHARS = 0x19; // Set special characters. static constexpr uint8_t SET_CHARS = 0x19; // Set special characters.
static constexpr uint8_t VENDOR_SPECIFIC = 0xFF; // Vendor specific command. static constexpr uint8_t VENDOR_SPECIFIC = 0xFF; // Vendor specific command.
std::vector<CdcEps> USBUartTypeCP210X::parse_descriptors_(usb_device_handle_t dev_hdl) { std::vector<CdcEps> USBUartTypeCP210X::parse_descriptors(usb_device_handle_t dev_hdl) {
const usb_config_desc_t *config_desc; const usb_config_desc_t *config_desc;
const usb_device_desc_t *device_desc; const usb_device_desc_t *device_desc;
int conf_offset = 0, ep_offset; int conf_offset = 0, ep_offset;

View File

@ -18,52 +18,48 @@ namespace usb_uart {
*/ */
static optional<CdcEps> get_cdc(const usb_config_desc_t *config_desc, uint8_t intf_idx) { static optional<CdcEps> get_cdc(const usb_config_desc_t *config_desc, uint8_t intf_idx) {
int conf_offset, ep_offset; int conf_offset, ep_offset;
const usb_ep_desc_t *notify_ep{}, *in_ep{}, *out_ep{}; // look for an interface with an interrupt endpoint (notify), and one with two bulk endpoints (data in/out)
uint8_t interface_number = 0; CdcEps eps{};
// look for an interface with one interrupt endpoint (notify), and an interface with two bulk endpoints (data in/out) eps.bulk_interface_number = 0xFF;
for (;;) { for (;;) {
auto intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx++, 0, &conf_offset); const auto *intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx++, 0, &conf_offset);
if (!intf_desc) { if (!intf_desc) {
ESP_LOGE(TAG, "usb_parse_interface_descriptor failed"); ESP_LOGE(TAG, "usb_parse_interface_descriptor failed");
return nullopt; return nullopt;
} }
if (intf_desc->bNumEndpoints == 1) { ESP_LOGD(TAG, "intf_desc: bInterfaceClass=%02X, bInterfaceSubClass=%02X, bInterfaceProtocol=%02X, bNumEndpoints=%d",
intf_desc->bInterfaceClass, intf_desc->bInterfaceSubClass, intf_desc->bInterfaceProtocol,
intf_desc->bNumEndpoints);
for (uint8_t i = 0; i != intf_desc->bNumEndpoints; i++) {
ep_offset = conf_offset; ep_offset = conf_offset;
notify_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 0, config_desc->wTotalLength, &ep_offset); const auto *ep = usb_parse_endpoint_descriptor_by_index(intf_desc, i, config_desc->wTotalLength, &ep_offset);
if (!notify_ep) { if (!ep) {
ESP_LOGE(TAG, "notify_ep: usb_parse_endpoint_descriptor_by_index failed"); ESP_LOGE(TAG, "Ran out of interfaces at %d before finding all endpoints", i);
return nullopt; return nullopt;
} }
if (notify_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_INT) ESP_LOGD(TAG, "ep: bEndpointAddress=%02X, bmAttributes=%02X", ep->bEndpointAddress, ep->bmAttributes);
notify_ep = nullptr; if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_INT) {
} else if (USB_CLASS_CDC_DATA && intf_desc->bNumEndpoints == 2) { eps.notify_ep = ep;
interface_number = intf_desc->bInterfaceNumber; eps.interrupt_interface_number = intf_desc->bInterfaceNumber;
ep_offset = conf_offset; } else if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_BULK && ep->bEndpointAddress & usb_host::USB_DIR_IN &&
out_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 0, config_desc->wTotalLength, &ep_offset); (eps.bulk_interface_number == 0xFF || eps.bulk_interface_number == intf_desc->bInterfaceNumber)) {
if (!out_ep) { eps.in_ep = ep;
ESP_LOGE(TAG, "out_ep: usb_parse_endpoint_descriptor_by_index failed"); eps.bulk_interface_number = intf_desc->bInterfaceNumber;
return nullopt; } else if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_BULK && !(ep->bEndpointAddress & usb_host::USB_DIR_IN) &&
(eps.bulk_interface_number == 0xFF || eps.bulk_interface_number == intf_desc->bInterfaceNumber)) {
eps.out_ep = ep;
eps.bulk_interface_number = intf_desc->bInterfaceNumber;
} else {
ESP_LOGE(TAG, "Unexpected endpoint attributes: %02X", ep->bmAttributes);
continue;
} }
if (out_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_BULK)
out_ep = nullptr;
ep_offset = conf_offset;
in_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 1, config_desc->wTotalLength, &ep_offset);
if (!in_ep) {
ESP_LOGE(TAG, "in_ep: usb_parse_endpoint_descriptor_by_index failed");
return nullopt;
}
if (in_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_BULK)
in_ep = nullptr;
} }
if (in_ep != nullptr && out_ep != nullptr && notify_ep != nullptr) if (eps.in_ep != nullptr && eps.out_ep != nullptr && eps.notify_ep != nullptr)
break; return eps;
} }
if (in_ep->bEndpointAddress & usb_host::USB_DIR_IN)
return CdcEps{notify_ep, in_ep, out_ep, interface_number};
return CdcEps{notify_ep, out_ep, in_ep, interface_number};
} }
std::vector<CdcEps> USBUartTypeCdcAcm::parse_descriptors_(usb_device_handle_t dev_hdl) { std::vector<CdcEps> USBUartTypeCdcAcm::parse_descriptors(usb_device_handle_t dev_hdl) {
const usb_config_desc_t *config_desc; const usb_config_desc_t *config_desc;
const usb_device_desc_t *device_desc; const usb_device_desc_t *device_desc;
int desc_offset = 0; int desc_offset = 0;
@ -78,7 +74,7 @@ std::vector<CdcEps> USBUartTypeCdcAcm::parse_descriptors_(usb_device_handle_t de
ESP_LOGE(TAG, "get_active_config_descriptor failed"); ESP_LOGE(TAG, "get_active_config_descriptor failed");
return {}; return {};
} }
if (device_desc->bDeviceClass == USB_CLASS_COMM) { if (device_desc->bDeviceClass == USB_CLASS_COMM || device_desc->bDeviceClass == USB_CLASS_VENDOR_SPEC) {
// single CDC-ACM device // single CDC-ACM device
if (auto eps = get_cdc(config_desc, 0)) { if (auto eps = get_cdc(config_desc, 0)) {
ESP_LOGV(TAG, "Found CDC-ACM device"); ESP_LOGV(TAG, "Found CDC-ACM device");
@ -194,7 +190,7 @@ void USBUartComponent::start_input(USBUartChannel *channel) {
if (!channel->initialised_ || channel->input_started_ || if (!channel->initialised_ || channel->input_started_ ||
channel->input_buffer_.get_free_space() < channel->cdc_dev_.in_ep->wMaxPacketSize) channel->input_buffer_.get_free_space() < channel->cdc_dev_.in_ep->wMaxPacketSize)
return; return;
auto ep = channel->cdc_dev_.in_ep; const auto *ep = channel->cdc_dev_.in_ep;
auto callback = [this, channel](const usb_host::TransferStatus &status) { auto callback = [this, channel](const usb_host::TransferStatus &status) {
ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code); ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code);
if (!status.success) { if (!status.success) {
@ -227,7 +223,7 @@ void USBUartComponent::start_output(USBUartChannel *channel) {
if (channel->output_buffer_.is_empty()) { if (channel->output_buffer_.is_empty()) {
return; return;
} }
auto ep = channel->cdc_dev_.out_ep; const auto *ep = channel->cdc_dev_.out_ep;
auto callback = [this, channel](const usb_host::TransferStatus &status) { auto callback = [this, channel](const usb_host::TransferStatus &status) {
ESP_LOGV(TAG, "Output Transfer result: length: %u; status %X", status.data_len, status.error_code); ESP_LOGV(TAG, "Output Transfer result: length: %u; status %X", status.data_len, status.error_code);
channel->output_started_ = false; channel->output_started_ = false;
@ -259,15 +255,15 @@ static void fix_mps(const usb_ep_desc_t *ep) {
} }
} }
void USBUartTypeCdcAcm::on_connected() { void USBUartTypeCdcAcm::on_connected() {
auto cdc_devs = this->parse_descriptors_(this->device_handle_); auto cdc_devs = this->parse_descriptors(this->device_handle_);
if (cdc_devs.empty()) { if (cdc_devs.empty()) {
this->status_set_error("No CDC-ACM device found"); this->status_set_error("No CDC-ACM device found");
this->disconnect(); this->disconnect();
return; return;
} }
ESP_LOGD(TAG, "Found %zu CDC-ACM devices", cdc_devs.size()); ESP_LOGD(TAG, "Found %zu CDC-ACM devices", cdc_devs.size());
auto i = 0; size_t i = 0;
for (auto channel : this->channels_) { for (auto *channel : this->channels_) {
if (i == cdc_devs.size()) { if (i == cdc_devs.size()) {
ESP_LOGE(TAG, "No configuration found for channel %d", channel->index_); ESP_LOGE(TAG, "No configuration found for channel %d", channel->index_);
this->status_set_warning("No configuration found for channel"); this->status_set_warning("No configuration found for channel");
@ -277,10 +273,11 @@ void USBUartTypeCdcAcm::on_connected() {
fix_mps(channel->cdc_dev_.in_ep); fix_mps(channel->cdc_dev_.in_ep);
fix_mps(channel->cdc_dev_.out_ep); fix_mps(channel->cdc_dev_.out_ep);
channel->initialised_ = true; channel->initialised_ = true;
auto err = usb_host_interface_claim(this->handle_, this->device_handle_, channel->cdc_dev_.interface_number, 0); auto err =
usb_host_interface_claim(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number, 0);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_, ESP_LOGE(TAG, "usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_,
channel->cdc_dev_.interface_number); channel->cdc_dev_.bulk_interface_number);
this->status_set_error("usb_host_interface_claim failed"); this->status_set_error("usb_host_interface_claim failed");
this->disconnect(); this->disconnect();
return; return;
@ -290,7 +287,7 @@ void USBUartTypeCdcAcm::on_connected() {
} }
void USBUartTypeCdcAcm::on_disconnected() { void USBUartTypeCdcAcm::on_disconnected() {
for (auto channel : this->channels_) { for (auto *channel : this->channels_) {
if (channel->cdc_dev_.in_ep != nullptr) { if (channel->cdc_dev_.in_ep != nullptr) {
usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress); usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress); usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
@ -303,7 +300,7 @@ void USBUartTypeCdcAcm::on_disconnected() {
usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress); usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress); usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
} }
usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.interface_number); usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.bulk_interface_number);
channel->initialised_ = false; channel->initialised_ = false;
channel->input_started_ = false; channel->input_started_ = false;
channel->output_started_ = false; channel->output_started_ = false;
@ -314,7 +311,7 @@ void USBUartTypeCdcAcm::on_disconnected() {
} }
void USBUartTypeCdcAcm::enable_channels() { void USBUartTypeCdcAcm::enable_channels() {
for (auto channel : this->channels_) { for (auto *channel : this->channels_) {
if (!channel->initialised_) if (!channel->initialised_)
continue; continue;
channel->input_started_ = false; channel->input_started_ = false;

View File

@ -25,7 +25,8 @@ struct CdcEps {
const usb_ep_desc_t *notify_ep; const usb_ep_desc_t *notify_ep;
const usb_ep_desc_t *in_ep; const usb_ep_desc_t *in_ep;
const usb_ep_desc_t *out_ep; const usb_ep_desc_t *out_ep;
uint8_t interface_number; uint8_t bulk_interface_number;
uint8_t interrupt_interface_number;
}; };
enum UARTParityOptions { enum UARTParityOptions {
@ -123,7 +124,7 @@ class USBUartTypeCdcAcm : public USBUartComponent {
USBUartTypeCdcAcm(uint16_t vid, uint16_t pid) : USBUartComponent(vid, pid) {} USBUartTypeCdcAcm(uint16_t vid, uint16_t pid) : USBUartComponent(vid, pid) {}
protected: protected:
virtual std::vector<CdcEps> parse_descriptors_(usb_device_handle_t dev_hdl); virtual std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl);
void on_connected() override; void on_connected() override;
virtual void enable_channels(); virtual void enable_channels();
void on_disconnected() override; void on_disconnected() override;
@ -134,7 +135,7 @@ class USBUartTypeCP210X : public USBUartTypeCdcAcm {
USBUartTypeCP210X(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {} USBUartTypeCP210X(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
protected: protected:
std::vector<CdcEps> parse_descriptors_(usb_device_handle_t dev_hdl) override; std::vector<CdcEps> parse_descriptors(usb_device_handle_t dev_hdl) override;
void enable_channels() override; void enable_channels() override;
}; };
class USBUartTypeCH34X : public USBUartTypeCdcAcm { class USBUartTypeCH34X : public USBUartTypeCdcAcm {

View File

@ -792,7 +792,7 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi
light::LightJSONSchema::dump_json(*obj, root); light::LightJSONSchema::dump_json(*obj, root);
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
JsonArray opt = root.createNestedArray("effects"); JsonArray opt = root["effects"].to<JsonArray>();
opt.add("None"); opt.add("None");
for (auto const &option : obj->get_effects()) { for (auto const &option : obj->get_effects()) {
opt.add(option->get_name()); opt.add(option->get_name());
@ -1238,7 +1238,7 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value
return json::build_json([this, obj, value, start_config](JsonObject root) { return json::build_json([this, obj, value, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config); set_json_icon_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config);
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
JsonArray opt = root.createNestedArray("option"); JsonArray opt = root["option"].to<JsonArray>();
for (auto &option : obj->traits.get_options()) { for (auto &option : obj->traits.get_options()) {
opt.add(option); opt.add(option);
} }
@ -1322,6 +1322,7 @@ std::string WebServer::climate_all_json_generator(WebServer *web_server, void *s
return web_server->climate_json((climate::Climate *) (source), DETAIL_ALL); return web_server->climate_json((climate::Climate *) (source), DETAIL_ALL);
} }
std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) { std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) {
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
return json::build_json([this, obj, start_config](JsonObject root) { return json::build_json([this, obj, start_config](JsonObject root) {
set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config); set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config);
const auto traits = obj->get_traits(); const auto traits = obj->get_traits();
@ -1330,32 +1331,32 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf
char buf[16]; char buf[16];
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
JsonArray opt = root.createNestedArray("modes"); JsonArray opt = root["modes"].to<JsonArray>();
for (climate::ClimateMode m : traits.get_supported_modes()) for (climate::ClimateMode m : traits.get_supported_modes())
opt.add(PSTR_LOCAL(climate::climate_mode_to_string(m))); opt.add(PSTR_LOCAL(climate::climate_mode_to_string(m)));
if (!traits.get_supported_custom_fan_modes().empty()) { if (!traits.get_supported_custom_fan_modes().empty()) {
JsonArray opt = root.createNestedArray("fan_modes"); JsonArray opt = root["fan_modes"].to<JsonArray>();
for (climate::ClimateFanMode m : traits.get_supported_fan_modes()) for (climate::ClimateFanMode m : traits.get_supported_fan_modes())
opt.add(PSTR_LOCAL(climate::climate_fan_mode_to_string(m))); opt.add(PSTR_LOCAL(climate::climate_fan_mode_to_string(m)));
} }
if (!traits.get_supported_custom_fan_modes().empty()) { if (!traits.get_supported_custom_fan_modes().empty()) {
JsonArray opt = root.createNestedArray("custom_fan_modes"); JsonArray opt = root["custom_fan_modes"].to<JsonArray>();
for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes())
opt.add(custom_fan_mode); opt.add(custom_fan_mode);
} }
if (traits.get_supports_swing_modes()) { if (traits.get_supports_swing_modes()) {
JsonArray opt = root.createNestedArray("swing_modes"); JsonArray opt = root["swing_modes"].to<JsonArray>();
for (auto swing_mode : traits.get_supported_swing_modes()) for (auto swing_mode : traits.get_supported_swing_modes())
opt.add(PSTR_LOCAL(climate::climate_swing_mode_to_string(swing_mode))); opt.add(PSTR_LOCAL(climate::climate_swing_mode_to_string(swing_mode)));
} }
if (traits.get_supports_presets() && obj->preset.has_value()) { if (traits.get_supports_presets() && obj->preset.has_value()) {
JsonArray opt = root.createNestedArray("presets"); JsonArray opt = root["presets"].to<JsonArray>();
for (climate::ClimatePreset m : traits.get_supported_presets()) for (climate::ClimatePreset m : traits.get_supported_presets())
opt.add(PSTR_LOCAL(climate::climate_preset_to_string(m))); opt.add(PSTR_LOCAL(climate::climate_preset_to_string(m)));
} }
if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) { if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) {
JsonArray opt = root.createNestedArray("custom_presets"); JsonArray opt = root["custom_presets"].to<JsonArray>();
for (auto const &custom_preset : traits.get_supported_custom_presets()) for (auto const &custom_preset : traits.get_supported_custom_presets())
opt.add(custom_preset); opt.add(custom_preset);
} }
@ -1407,6 +1408,7 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf
root["state"] = root["target_temperature"]; root["state"] = root["target_temperature"];
} }
}); });
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
} }
#endif #endif
@ -1635,7 +1637,7 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty
root["event_type"] = event_type; root["event_type"] = event_type;
} }
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
JsonArray event_types = root.createNestedArray("event_types"); JsonArray event_types = root["event_types"].to<JsonArray>();
for (auto const &event_type : obj->get_event_types()) { for (auto const &event_type : obj->get_event_types()) {
event_types.add(event_type); event_types.add(event_type);
} }
@ -1682,6 +1684,7 @@ std::string WebServer::update_all_json_generator(WebServer *web_server, void *so
return web_server->update_json((update::UpdateEntity *) (source), DETAIL_STATE); return web_server->update_json((update::UpdateEntity *) (source), DETAIL_STATE);
} }
std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_config) { std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_config) {
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
return json::build_json([this, obj, start_config](JsonObject root) { return json::build_json([this, obj, start_config](JsonObject root) {
set_json_id(root, obj, "update-" + obj->get_object_id(), start_config); set_json_id(root, obj, "update-" + obj->get_object_id(), start_config);
root["value"] = obj->update_info.latest_version; root["value"] = obj->update_info.latest_version;
@ -1707,166 +1710,166 @@ std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_c
this->add_sorting_info_(root, obj); this->add_sorting_info_(root, obj);
} }
}); });
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
} }
#endif #endif
bool WebServer::canHandle(AsyncWebServerRequest *request) const { bool WebServer::canHandle(AsyncWebServerRequest *request) const {
if (request->url() == "/") const auto &url = request->url();
const auto method = request->method();
// Simple URL checks
if (url == "/")
return true; return true;
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
if (request->url() == "/events") { if (url == "/events")
return true; return true;
}
#endif #endif
#ifdef USE_WEBSERVER_CSS_INCLUDE #ifdef USE_WEBSERVER_CSS_INCLUDE
if (request->url() == "/0.css") if (url == "/0.css")
return true; return true;
#endif #endif
#ifdef USE_WEBSERVER_JS_INCLUDE #ifdef USE_WEBSERVER_JS_INCLUDE
if (request->url() == "/0.js") if (url == "/0.js")
return true; return true;
#endif #endif
#ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
if (request->method() == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) { if (method == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA))
return true; return true;
}
#endif #endif
// Store the URL to prevent temporary string destruction // Parse URL for component checks
// request->url() returns a reference to a String (on Arduino) or std::string (on ESP-IDF)
// UrlMatch stores pointers to the string's data, so we must ensure the string outlives match_url()
const auto &url = request->url();
UrlMatch match = match_url(url.c_str(), url.length(), true); UrlMatch match = match_url(url.c_str(), url.length(), true);
if (!match.valid) if (!match.valid)
return false; return false;
// Common pattern check
bool is_get = method == HTTP_GET;
bool is_post = method == HTTP_POST;
bool is_get_or_post = is_get || is_post;
if (!is_get_or_post)
return false;
// GET-only components
if (is_get) {
#ifdef USE_SENSOR #ifdef USE_SENSOR
if (request->method() == HTTP_GET && match.domain_equals("sensor")) if (match.domain_equals("sensor"))
return true; return true;
#endif #endif
#ifdef USE_SWITCH
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("switch"))
return true;
#endif
#ifdef USE_BUTTON
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("button"))
return true;
#endif
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
if (request->method() == HTTP_GET && match.domain_equals("binary_sensor")) if (match.domain_equals("binary_sensor"))
return true; return true;
#endif #endif
#ifdef USE_FAN
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("fan"))
return true;
#endif
#ifdef USE_LIGHT
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("light"))
return true;
#endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
if (request->method() == HTTP_GET && match.domain_equals("text_sensor")) if (match.domain_equals("text_sensor"))
return true; return true;
#endif #endif
#ifdef USE_COVER
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("cover"))
return true;
#endif
#ifdef USE_NUMBER
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("number"))
return true;
#endif
#ifdef USE_DATETIME_DATE
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("date"))
return true;
#endif
#ifdef USE_DATETIME_TIME
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("time"))
return true;
#endif
#ifdef USE_DATETIME_DATETIME
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("datetime"))
return true;
#endif
#ifdef USE_TEXT
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("text"))
return true;
#endif
#ifdef USE_SELECT
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("select"))
return true;
#endif
#ifdef USE_CLIMATE
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("climate"))
return true;
#endif
#ifdef USE_LOCK
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("lock"))
return true;
#endif
#ifdef USE_VALVE
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("valve"))
return true;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
if ((request->method() == HTTP_GET || request->method() == HTTP_POST) && match.domain_equals("alarm_control_panel"))
return true;
#endif
#ifdef USE_EVENT #ifdef USE_EVENT
if (request->method() == HTTP_GET && match.domain_equals("event")) if (match.domain_equals("event"))
return true; return true;
#endif #endif
}
#ifdef USE_UPDATE // GET+POST components
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("update")) if (is_get_or_post) {
return true; #ifdef USE_SWITCH
if (match.domain_equals("switch"))
return true;
#endif #endif
#ifdef USE_BUTTON
if (match.domain_equals("button"))
return true;
#endif
#ifdef USE_FAN
if (match.domain_equals("fan"))
return true;
#endif
#ifdef USE_LIGHT
if (match.domain_equals("light"))
return true;
#endif
#ifdef USE_COVER
if (match.domain_equals("cover"))
return true;
#endif
#ifdef USE_NUMBER
if (match.domain_equals("number"))
return true;
#endif
#ifdef USE_DATETIME_DATE
if (match.domain_equals("date"))
return true;
#endif
#ifdef USE_DATETIME_TIME
if (match.domain_equals("time"))
return true;
#endif
#ifdef USE_DATETIME_DATETIME
if (match.domain_equals("datetime"))
return true;
#endif
#ifdef USE_TEXT
if (match.domain_equals("text"))
return true;
#endif
#ifdef USE_SELECT
if (match.domain_equals("select"))
return true;
#endif
#ifdef USE_CLIMATE
if (match.domain_equals("climate"))
return true;
#endif
#ifdef USE_LOCK
if (match.domain_equals("lock"))
return true;
#endif
#ifdef USE_VALVE
if (match.domain_equals("valve"))
return true;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
if (match.domain_equals("alarm_control_panel"))
return true;
#endif
#ifdef USE_UPDATE
if (match.domain_equals("update"))
return true;
#endif
}
return false; return false;
} }
void WebServer::handleRequest(AsyncWebServerRequest *request) { void WebServer::handleRequest(AsyncWebServerRequest *request) {
if (request->url() == "/") { const auto &url = request->url();
// Handle static routes first
if (url == "/") {
this->handle_index_request(request); this->handle_index_request(request);
return; return;
} }
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
if (request->url() == "/events") { if (url == "/events") {
this->events_.add_new_client(this, request); this->events_.add_new_client(this, request);
return; return;
} }
#endif #endif
#ifdef USE_WEBSERVER_CSS_INCLUDE #ifdef USE_WEBSERVER_CSS_INCLUDE
if (request->url() == "/0.css") { if (url == "/0.css") {
this->handle_css_request(request); this->handle_css_request(request);
return; return;
} }
#endif #endif
#ifdef USE_WEBSERVER_JS_INCLUDE #ifdef USE_WEBSERVER_JS_INCLUDE
if (request->url() == "/0.js") { if (url == "/0.js") {
this->handle_js_request(request); this->handle_js_request(request);
return; return;
} }
@ -1879,147 +1882,85 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
} }
#endif #endif
// See comment in canHandle() for why we store the URL reference // Parse URL for component routing
const auto &url = request->url();
UrlMatch match = match_url(url.c_str(), url.length(), false); UrlMatch match = match_url(url.c_str(), url.length(), false);
// Component routing using minimal code repetition
struct ComponentRoute {
const char *domain;
void (WebServer::*handler)(AsyncWebServerRequest *, const UrlMatch &);
};
static const ComponentRoute ROUTES[] = {
#ifdef USE_SENSOR #ifdef USE_SENSOR
if (match.domain_equals("sensor")) { {"sensor", &WebServer::handle_sensor_request},
this->handle_sensor_request(request, match);
return;
}
#endif #endif
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (match.domain_equals("switch")) { {"switch", &WebServer::handle_switch_request},
this->handle_switch_request(request, match);
return;
}
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
if (match.domain_equals("button")) { {"button", &WebServer::handle_button_request},
this->handle_button_request(request, match);
return;
}
#endif #endif
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
if (match.domain_equals("binary_sensor")) { {"binary_sensor", &WebServer::handle_binary_sensor_request},
this->handle_binary_sensor_request(request, match);
return;
}
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
if (match.domain_equals("fan")) { {"fan", &WebServer::handle_fan_request},
this->handle_fan_request(request, match);
return;
}
#endif #endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
if (match.domain_equals("light")) { {"light", &WebServer::handle_light_request},
this->handle_light_request(request, match);
return;
}
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
if (match.domain_equals("text_sensor")) { {"text_sensor", &WebServer::handle_text_sensor_request},
this->handle_text_sensor_request(request, match);
return;
}
#endif #endif
#ifdef USE_COVER #ifdef USE_COVER
if (match.domain_equals("cover")) { {"cover", &WebServer::handle_cover_request},
this->handle_cover_request(request, match);
return;
}
#endif #endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
if (match.domain_equals("number")) { {"number", &WebServer::handle_number_request},
this->handle_number_request(request, match);
return;
}
#endif #endif
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
if (match.domain_equals("date")) { {"date", &WebServer::handle_date_request},
this->handle_date_request(request, match);
return;
}
#endif #endif
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
if (match.domain_equals("time")) { {"time", &WebServer::handle_time_request},
this->handle_time_request(request, match);
return;
}
#endif #endif
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
if (match.domain_equals("datetime")) { {"datetime", &WebServer::handle_datetime_request},
this->handle_datetime_request(request, match);
return;
}
#endif #endif
#ifdef USE_TEXT #ifdef USE_TEXT
if (match.domain_equals("text")) { {"text", &WebServer::handle_text_request},
this->handle_text_request(request, match);
return;
}
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
if (match.domain_equals("select")) { {"select", &WebServer::handle_select_request},
this->handle_select_request(request, match);
return;
}
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
if (match.domain_equals("climate")) { {"climate", &WebServer::handle_climate_request},
this->handle_climate_request(request, match);
return;
}
#endif #endif
#ifdef USE_LOCK #ifdef USE_LOCK
if (match.domain_equals("lock")) { {"lock", &WebServer::handle_lock_request},
this->handle_lock_request(request, match);
return;
}
#endif #endif
#ifdef USE_VALVE #ifdef USE_VALVE
if (match.domain_equals("valve")) { {"valve", &WebServer::handle_valve_request},
this->handle_valve_request(request, match);
return;
}
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
if (match.domain_equals("alarm_control_panel")) { {"alarm_control_panel", &WebServer::handle_alarm_control_panel_request},
this->handle_alarm_control_panel_request(request, match);
return;
}
#endif #endif
#ifdef USE_UPDATE #ifdef USE_UPDATE
if (match.domain_equals("update")) { {"update", &WebServer::handle_update_request},
this->handle_update_request(request, match);
return;
}
#endif #endif
};
// Check each route
for (const auto &route : ROUTES) {
if (match.domain_equals(route.domain)) {
(this->*route.handler)(request, match);
return;
}
}
// No matching handler found - send 404 // No matching handler found - send 404
ESP_LOGV(TAG, "Request for unknown URL: %s", request->url().c_str()); ESP_LOGV(TAG, "Request for unknown URL: %s", url.c_str());
request->send(404, "text/plain", "Not Found"); request->send(404, "text/plain", "Not Found");
} }

View File

@ -40,4 +40,4 @@ async def to_code(config):
if CORE.is_esp8266: if CORE.is_esp8266:
cg.add_library("ESP8266WiFi", None) cg.add_library("ESP8266WiFi", None)
# https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json # https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json
cg.add_library("ESP32Async/ESPAsyncWebServer", "3.7.8") cg.add_library("ESP32Async/ESPAsyncWebServer", "3.7.10")

View File

@ -389,10 +389,12 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest *
#ifdef USE_WEBSERVER_SORTING #ifdef USE_WEBSERVER_SORTING
for (auto &group : ws->sorting_groups_) { for (auto &group : ws->sorting_groups_) {
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
message = json::build_json([group](JsonObject root) { message = json::build_json([group](JsonObject root) {
root["name"] = group.second.name; root["name"] = group.second.name;
root["sorting_weight"] = group.second.weight; root["sorting_weight"] = group.second.weight;
}); });
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
// a (very) large number of these should be able to be queued initially without defer // a (very) large number of these should be able to be queued initially without defer
// since the only thing in the send buffer at this point is the initial ping/config // since the only thing in the send buffer at this point is the initial ping/config

View File

@ -309,6 +309,12 @@ void Application::disable_component_loop_(Component *component) {
if (this->in_loop_ && i == this->current_loop_index_) { if (this->in_loop_ && i == this->current_loop_index_) {
// Decrement so we'll process the swapped component next // Decrement so we'll process the swapped component next
this->current_loop_index_--; this->current_loop_index_--;
// Update the loop start time to current time so the swapped component
// gets correct timing instead of inheriting stale timing.
// This prevents integer underflow in timing calculations by ensuring
// the swapped component starts with a fresh timing reference, avoiding
// errors caused by stale or wrapped timing values.
this->loop_component_start_time_ = millis();
} }
} }
return; return;

View File

@ -138,7 +138,7 @@ void Component::call_dump_config() {
} }
} }
} }
ESP_LOGE(TAG, " Component %s is marked FAILED: %s", this->get_component_source(), error_msg); ESP_LOGE(TAG, " %s is marked FAILED: %s", this->get_component_source(), error_msg);
} }
} }
@ -191,7 +191,7 @@ bool Component::should_warn_of_blocking(uint32_t blocking_time) {
return false; return false;
} }
void Component::mark_failed() { void Component::mark_failed() {
ESP_LOGE(TAG, "Component %s was marked as failed", this->get_component_source()); ESP_LOGE(TAG, "%s was marked as failed", this->get_component_source());
this->component_state_ &= ~COMPONENT_STATE_MASK; this->component_state_ &= ~COMPONENT_STATE_MASK;
this->component_state_ |= COMPONENT_STATE_FAILED; this->component_state_ |= COMPONENT_STATE_FAILED;
this->status_set_error(); this->status_set_error();
@ -229,7 +229,7 @@ void IRAM_ATTR HOT Component::enable_loop_soon_any_context() {
} }
void Component::reset_to_construction_state() { void Component::reset_to_construction_state() {
if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) { if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) {
ESP_LOGI(TAG, "Component %s is being reset to construction state", this->get_component_source()); ESP_LOGI(TAG, "%s is being reset to construction state", this->get_component_source());
this->component_state_ &= ~COMPONENT_STATE_MASK; this->component_state_ &= ~COMPONENT_STATE_MASK;
this->component_state_ |= COMPONENT_STATE_CONSTRUCTION; this->component_state_ |= COMPONENT_STATE_CONSTRUCTION;
// Clear error status when resetting // Clear error status when resetting
@ -264,6 +264,7 @@ void Component::set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std:
bool Component::is_failed() const { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; } bool Component::is_failed() const { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; }
bool Component::is_ready() const { bool Component::is_ready() const {
return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP || return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP ||
(this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP_DONE ||
(this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_SETUP; (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_SETUP;
} }
bool Component::can_proceed() { return true; } bool Component::can_proceed() { return true; }
@ -275,14 +276,14 @@ void Component::status_set_warning(const char *message) {
return; return;
this->component_state_ |= STATUS_LED_WARNING; this->component_state_ |= STATUS_LED_WARNING;
App.app_state_ |= STATUS_LED_WARNING; App.app_state_ |= STATUS_LED_WARNING;
ESP_LOGW(TAG, "Component %s set Warning flag: %s", this->get_component_source(), message); ESP_LOGW(TAG, "%s set Warning flag: %s", this->get_component_source(), message);
} }
void Component::status_set_error(const char *message) { void Component::status_set_error(const char *message) {
if ((this->component_state_ & STATUS_LED_ERROR) != 0) if ((this->component_state_ & STATUS_LED_ERROR) != 0)
return; return;
this->component_state_ |= STATUS_LED_ERROR; this->component_state_ |= STATUS_LED_ERROR;
App.app_state_ |= STATUS_LED_ERROR; App.app_state_ |= STATUS_LED_ERROR;
ESP_LOGE(TAG, "Component %s set Error flag: %s", this->get_component_source(), message); ESP_LOGE(TAG, "%s set Error flag: %s", this->get_component_source(), message);
if (strcmp(message, "unspecified") != 0) { if (strcmp(message, "unspecified") != 0) {
// Lazy allocate the error messages vector if needed // Lazy allocate the error messages vector if needed
if (!component_error_messages) { if (!component_error_messages) {
@ -303,13 +304,13 @@ void Component::status_clear_warning() {
if ((this->component_state_ & STATUS_LED_WARNING) == 0) if ((this->component_state_ & STATUS_LED_WARNING) == 0)
return; return;
this->component_state_ &= ~STATUS_LED_WARNING; this->component_state_ &= ~STATUS_LED_WARNING;
ESP_LOGW(TAG, "Component %s cleared Warning flag", this->get_component_source()); ESP_LOGW(TAG, "%s cleared Warning flag", this->get_component_source());
} }
void Component::status_clear_error() { void Component::status_clear_error() {
if ((this->component_state_ & STATUS_LED_ERROR) == 0) if ((this->component_state_ & STATUS_LED_ERROR) == 0)
return; return;
this->component_state_ &= ~STATUS_LED_ERROR; this->component_state_ &= ~STATUS_LED_ERROR;
ESP_LOGE(TAG, "Component %s cleared Error flag", this->get_component_source()); ESP_LOGE(TAG, "%s cleared Error flag", this->get_component_source());
} }
void Component::status_momentary_warning(const std::string &name, uint32_t length) { void Component::status_momentary_warning(const std::string &name, uint32_t length) {
this->status_set_warning(); this->status_set_warning();
@ -403,7 +404,7 @@ uint32_t WarnIfComponentBlockingGuard::finish() {
} }
if (should_warn) { if (should_warn) {
const char *src = component_ == nullptr ? "<null>" : component_->get_component_source(); const char *src = component_ == nullptr ? "<null>" : component_->get_component_source();
ESP_LOGW(TAG, "Component %s took a long time for an operation (%" PRIu32 " ms)", src, blocking_time); ESP_LOGW(TAG, "%s took a long time for an operation (%" PRIu32 " ms)", src, blocking_time);
ESP_LOGW(TAG, "Components should block for at most 30 ms"); ESP_LOGW(TAG, "Components should block for at most 30 ms");
} }

View File

@ -4,6 +4,8 @@
#ifdef USE_API #ifdef USE_API
#include "esphome/components/api/api_server.h" #include "esphome/components/api/api_server.h"
#endif
#ifdef USE_API_SERVICES
#include "esphome/components/api/user_services.h" #include "esphome/components/api/user_services.h"
#endif #endif
@ -148,7 +150,7 @@ void ComponentIterator::advance() {
} }
break; break;
#endif #endif
#ifdef USE_API #ifdef USE_API_SERVICES
case IteratorState ::SERVICE: case IteratorState ::SERVICE:
if (this->at_ >= api::global_api_server->get_user_services().size()) { if (this->at_ >= api::global_api_server->get_user_services().size()) {
advance_platform = true; advance_platform = true;
@ -383,7 +385,7 @@ void ComponentIterator::advance() {
} }
bool ComponentIterator::on_end() { return true; } bool ComponentIterator::on_end() { return true; }
bool ComponentIterator::on_begin() { return true; } bool ComponentIterator::on_begin() { return true; }
#ifdef USE_API #ifdef USE_API_SERVICES
bool ComponentIterator::on_service(api::UserServiceDescriptor *service) { return true; } bool ComponentIterator::on_service(api::UserServiceDescriptor *service) { return true; }
#endif #endif
#ifdef USE_CAMERA #ifdef USE_CAMERA

View File

@ -10,7 +10,7 @@
namespace esphome { namespace esphome {
#ifdef USE_API #ifdef USE_API_SERVICES
namespace api { namespace api {
class UserServiceDescriptor; class UserServiceDescriptor;
} // namespace api } // namespace api
@ -45,7 +45,7 @@ class ComponentIterator {
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0;
#endif #endif
#ifdef USE_API #ifdef USE_API_SERVICES
virtual bool on_service(api::UserServiceDescriptor *service); virtual bool on_service(api::UserServiceDescriptor *service);
#endif #endif
#ifdef USE_CAMERA #ifdef USE_CAMERA
@ -122,7 +122,7 @@ class ComponentIterator {
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
TEXT_SENSOR, TEXT_SENSOR,
#endif #endif
#ifdef USE_API #ifdef USE_API_SERVICES
SERVICE, SERVICE,
#endif #endif
#ifdef USE_CAMERA #ifdef USE_CAMERA

View File

@ -108,7 +108,7 @@
#define USE_API_CLIENT_DISCONNECTED_TRIGGER #define USE_API_CLIENT_DISCONNECTED_TRIGGER
#define USE_API_NOISE #define USE_API_NOISE
#define USE_API_PLAINTEXT #define USE_API_PLAINTEXT
#define USE_API_YAML_SERVICES #define USE_API_SERVICES
#define USE_MD5 #define USE_MD5
#define USE_MQTT #define USE_MQTT
#define USE_NETWORK #define USE_NETWORK

View File

@ -258,7 +258,9 @@ std::string format_hex(const uint8_t *data, size_t length) {
std::string format_hex(const std::vector<uint8_t> &data) { return format_hex(data.data(), data.size()); } std::string format_hex(const std::vector<uint8_t> &data) { return format_hex(data.data(), data.size()); }
static char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; } static char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; }
std::string format_hex_pretty(const uint8_t *data, size_t length, char separator, bool show_length) {
// Shared implementation for uint8_t and string hex formatting
static std::string format_hex_pretty_uint8(const uint8_t *data, size_t length, char separator, bool show_length) {
if (data == nullptr || length == 0) if (data == nullptr || length == 0)
return ""; return "";
std::string ret; std::string ret;
@ -274,6 +276,10 @@ std::string format_hex_pretty(const uint8_t *data, size_t length, char separator
return ret + " (" + std::to_string(length) + ")"; return ret + " (" + std::to_string(length) + ")";
return ret; return ret;
} }
std::string format_hex_pretty(const uint8_t *data, size_t length, char separator, bool show_length) {
return format_hex_pretty_uint8(data, length, separator, show_length);
}
std::string format_hex_pretty(const std::vector<uint8_t> &data, char separator, bool show_length) { std::string format_hex_pretty(const std::vector<uint8_t> &data, char separator, bool show_length) {
return format_hex_pretty(data.data(), data.size(), separator, show_length); return format_hex_pretty(data.data(), data.size(), separator, show_length);
} }
@ -300,20 +306,7 @@ std::string format_hex_pretty(const std::vector<uint16_t> &data, char separator,
return format_hex_pretty(data.data(), data.size(), separator, show_length); return format_hex_pretty(data.data(), data.size(), separator, show_length);
} }
std::string format_hex_pretty(const std::string &data, char separator, bool show_length) { std::string format_hex_pretty(const std::string &data, char separator, bool show_length) {
if (data.empty()) return format_hex_pretty_uint8(reinterpret_cast<const uint8_t *>(data.data()), data.length(), separator, show_length);
return "";
std::string ret;
uint8_t multiple = separator ? 3 : 2; // 3 if separator is not \0, 2 otherwise
ret.resize(multiple * data.length() - (separator ? 1 : 0));
for (size_t i = 0; i < data.length(); i++) {
ret[multiple * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4);
ret[multiple * i + 1] = format_hex_pretty_char(data[i] & 0x0F);
if (separator && i != data.length() - 1)
ret[multiple * i + 2] = separator;
}
if (show_length && data.length() > 4)
return ret + " (" + std::to_string(data.length()) + ")";
return ret;
} }
std::string format_bin(const uint8_t *data, size_t length) { std::string format_bin(const uint8_t *data, size_t length) {

View File

@ -783,7 +783,7 @@ template<class T> class RAMAllocator {
T *reallocate(T *p, size_t n) { return this->reallocate(p, n, sizeof(T)); } T *reallocate(T *p, size_t n) { return this->reallocate(p, n, sizeof(T)); }
T *reallocate(T *p, size_t n, size_t manual_size) { T *reallocate(T *p, size_t n, size_t manual_size) {
size_t size = n * sizeof(T); size_t size = n * manual_size;
T *ptr = nullptr; T *ptr = nullptr;
#ifdef USE_ESP32 #ifdef USE_ESP32
if (this->flags_ & Flags::ALLOC_EXTERNAL) { if (this->flags_ & Flags::ALLOC_EXTERNAL) {

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