Compare commits

..
Author SHA1 Message Date
J. Nick Koston 2378962512 [datetime] Drop the inert constructors, keep the initializers 2026-09-12 04:56:11 -05:00
J. Nick Koston c4d89c89ff [datetime] Use user provided default constructors for the date and time entities 2026-09-11 21:30:30 -05:00
Clyde StubbsandClaude Sonnet 5 eecea15f4f [core][lvgl] Migrate codegen helpers from LVGL to core code (#19105)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 23:42:08 +00:00
J. Nick Koston ff9b2a1c83 [remote_receiver] Size the RMT ring buffer from receive symbols by default (#19100) 2026-09-11 18:20:40 -05:00
esphome[bot] 9f9df85aa0 Bump aioesphomeapi from 46.4.0 to 46.4.1 (#19104) 2026-09-11 23:04:48 +00:00
puddly 2b71d5496d [const] Centralize definition of CONF_MANUFACTURER (#19098) 2026-09-11 15:45:02 -05:00
esphome[bot] 2578f17dc8 Bump bundled esphome-device-builder to 1.14.7 (#19096) 2026-09-11 14:25:41 -05:00
J. Nick Koston 37e9b2b7af [remote_base] Make protocol methods non-virtual and size receiver lists from codegen (#19084) 2026-09-11 14:12:40 -05:00
Jesse Hills 6a21ab4ea7 [esp32] Trim mbedTLS to client-only defaults and stub vasprintf on the C6 (#19088) 2026-09-11 08:30:51 +00:00
Keith BurzinskiandClaude Fable 5.1 380938177c [uart] Add apply_settings_live() for in-place ESP-IDF reconfiguration (#19087)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 03:25:52 -05:00
J. Nick Koston f66ef23256 [core] Support set_internal() during setup, log error after setup (#19069) 2026-09-11 03:20:30 -05:00
esphome[bot] 3e3822e554 Bump bundled esphome-device-builder to 1.14.6 (#19072) 2026-09-10 16:22:30 +00:00
Kevin AhrendtandJ. Nick Koston 7564f5ff1a [sendspin] Add manufacturer, model, and firmware version options (#18792)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-09-10 11:06:58 -05:00
matt123p a807a8f945 [es7210] Fix 4 channel microphone support (#19034) 2026-09-10 11:11:43 -04:00
Keith Burzinskiandpuddly 280fac11e6 [serial_proxy] Add tap interface and port mode (#18955)
Co-authored-by: puddly <32534428+puddly@users.noreply.github.com>
2026-09-10 10:03:33 -05:00
esphome[bot] 9924148302 Bump aioesphomeapi from 46.3.0 to 46.4.0 (#19071) 2026-09-10 14:59:23 +00:00
luar123 66f829c760 [zigbee] wake loop on defer/set_timeout (#19050) 2026-09-10 08:08:07 -05:00
luar123 a88ec7d90b [logger] Flush uart before sleep in idf 6 (#18975) 2026-09-10 13:07:09 +00:00
luar123 54706e869c [deep_sleep] disable loop (#18962) 2026-09-10 07:18:15 -05:00
05f7d5e4f1 [mlx90614] pec validation (#6689)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-09-10 16:14:43 +12:00
Robin ThoniandJesse Hills ddbd89dd2a [network] Improve network::is_connected() to better handle multiple interfaces (#18999)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-09-10 04:06:44 +00:00
Jesse Hills 5ed59af920 [template] Surface value metadata on template entity forms (#17545) 2026-09-10 14:11:13 +12:00
Jesse Hills 4868b498cf [ci] Ask stale PR authors to merge dev instead of rebasing (#19064) 2026-09-10 14:09:58 +12:00
Jesse Hills c66fa81208 [core] Consolidate setup scripts into a cross-platform setup.py (#18856) 2026-09-10 14:09:49 +12:00
esphome[bot] 58ca345684 [ci] Refresh integration test durations (#19049) 2026-09-09 13:42:09 -04:00
Remco van Essen fd598057ef [sendspin] Fix codec enum codegen when codecs is not set (#19055) 2026-09-09 08:22:23 -04:00
Jesse Hills ef7279341f Merge branch 'beta' into dev 2026-09-09 12:18:55 +12:00
Kevin AhrendtandJesse Hills b947094f45 [sendspin] Add codec preference list to the media source (#19047)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-09-09 09:31:18 +12:00
J. Nick Koston 6c5ab89d5f [esphome][core] Give a lost OTA chunk ack time to be retransmitted (#19041) 2026-09-09 09:08:16 +12:00
J. Nick Koston 8f511a365a [noise] Bump noise-c to 0.1.26 and libsodium to 1.10021.8 (#19030) 2026-09-09 09:07:06 +12:00
Kevin Ahrendt 006f31af93 [i2s_audio] Fix spurious driver failure (#19045) 2026-09-09 09:05:02 +12:00
Kevin Ahrendt 5bb112f407 [audio][i2s_audio][micro_wake_word][microphone][mixer][resampler][speaker] Replace use_count() checks with lock and null test (#19046) 2026-09-09 09:04:34 +12:00
dependabot[bot] 4ab9298ab3 Bump esptool from 5.3.1 to 5.4.0 (#19023) 2026-09-08 22:11:36 +02:00
Kevin AhrendtandCopilot Autofix powered by AI 3926612281 [core] Fix use-after-free when deleting a running StaticTask (#19048)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-09 07:46:17 +12:00
J. Nick Koston 1ce0bed3f6 [core] Share compiled binaries across modbus integration tests (#18945) 2026-09-08 18:10:45 +02:00
esphome[bot] f91486305f Bump bundled esphome-device-builder to 1.14.5 (#19040) 2026-09-08 15:21:48 +02:00
Johnandpre-commit-ci-lite[bot] f191d5e0c3 [atm90e32] Verify offset calibration writes (#18701)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-09-08 20:02:49 +12:00
Jesse Hills 227ca90aad [core] Restore the shared git hooks after post-checkout runs script/setup (#19036) 2026-09-08 20:00:08 +12:00
Jesse Hills 1700a40b7c [core] Restore the shared git hooks after post-checkout runs script/setup (#19036) 2026-09-08 19:59:55 +12:00
raykholo 28588310e7 [anova] Re-assert temperature unit on every poll cycle (#17141) 2026-09-08 18:57:33 +12:00
10a9baff74 [rf_bridge] Fix bucket sniffing with Portisch firmware (#17683)
Co-authored-by: Bryan Li <bryanli@Bryans-MacBook-Pro.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-09-08 18:36:42 +12:00
Gytis a23f7bb569 [lvgl] Add missing label dependency to qrcode, keyboard and tabview (#18387) 2026-09-08 18:31:30 +12:00
J. Nick Koston 53075e4139 [core] Skip PlatformIO's private-package authorization probe (#18823) 2026-09-08 16:42:30 +12:00
Jonathan Swoboda 5722ccba37 [tuya] Build without a network component (#18948) 2026-09-08 16:36:04 +12:00
Jesse Hills 94e5c3839d [udp] Use cv.invalid for relocated packet_transport options (#19032) 2026-09-08 16:17:22 +12:00
Davide D M 574762f078 [debug] Check reboot source pref on ESP_RST_WDT and guard against empty source (#17537) 2026-09-08 13:59:05 +12:00
Pieter ViljoenandJesse Hills d34ffaf392 [ble_client] Report Established from nodes that never read services (#17920)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2026-09-08 01:57:50 +00:00
Samuel SiebandSamuel Sieb e6aa575f2e [dallas_temp] filter 85 temp from sensor reset (#17877)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2026-09-08 13:27:49 +12:00
AndreKR 639ce609bf [logger] Fix garbled stack traces (#17939) 2026-09-08 13:13:51 +12:00
Ryan Ronnander 62eafc477d [mqtt] Restore brightness flag in light discovery (#18950) 2026-09-08 13:09:02 +12:00
mipa87andClaude 56c3361b9a [audio] Do not treat MP3_STREAM_INFO_CHANGED as a fatal decoder error (#19028)
Co-authored-by: Claude <noreply@anthropic.com>
2026-09-08 12:13:03 +12:00
50ca381198 [i2s_audio] Keep a start request that arrives while the speaker task stops (#19027)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-09-08 12:10:55 +12:00
dependabot[bot] 89a56298c2 Bump prek from 0.5.1 to 0.5.2 (#19021)
Signed-off-by: dependabot[bot] <support@github.com>
2026-09-08 00:00:28 +00:00
390742cf9b Bump ruff from 0.16.5 to 0.16.6 (#19022)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
2026-09-07 23:47:46 +00:00
Jesse Hillsandpre-commit-ci-lite[bot] 5e37872da2 [ci] Sync pre-commit revs and prek version from requirements files (#19026)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-09-08 10:32:18 +12:00
Jesse Hills d34d3994e1 Merge branch 'beta' into dev 2026-09-07 12:50:44 +12:00
Jesse Hills d58b37faa1 [esp32_hosted] Add ESP-NOW-over-hosted shim for the ESP32-P4 (#17712) 2026-09-07 10:29:20 +12:00
J. Nick Koston 8966567be0 [core] Show the other downloader's progress while a prefetch job waits on its lock (#18983) 2026-09-07 10:28:31 +12:00
J. Nick Koston 20c7dcb1dd [mdns] Guard LEAmDNS main loop calls against lwIP re-entrancy on ESP8266 (#18990) 2026-09-07 10:26:44 +12:00
J. Nick Koston 688af60cbf [noise] Bump noise-c to 0.1.24 and libsodium to 1.10021.6 (#18989) 2026-09-07 10:12:52 +12:00
esphome[bot] 9c00f13606 Bump bundled esphome-device-builder to 1.14.4 (#19006) 2026-09-06 22:05:16 +00:00
J. Nick Koston 833dd0e812 [ota] Offer encryption with the api key so enabling it works over OTA (#18979) 2026-09-06 23:59:40 +02:00
Ricardo Sanz 8e1044e8ea [climate][template] New template climate component (#14455) 2026-09-06 14:03:07 -07:00
esphome[bot] e5200db6fd Bump bundled esphome-device-builder to 1.14.3 (#18996) 2026-09-06 09:28:02 +02:00
e3dd2f44a4 [mipi_dsi] Let IDF pick the DPHY PLL reference clock (#18984)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
2026-09-05 21:08:21 +00:00
esphome[bot] 3ef7460fca Bump bundled esphome-device-builder to 1.14.2 (#18988) 2026-09-05 15:25:58 +00:00
ae187f81f2 [wifi] Allow a forced roam check (#17349)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-09-05 21:44:37 +10:00
esphome[bot] 84f78831f9 Bump bundled esphome-device-builder to 1.14.1 (#18981) 2026-09-05 13:07:15 +02:00
Keith Burzinski 13dbbcaa32 [usb_uart] Keep the comm interface number valid when its claim fails (#18968) 2026-09-05 13:00:25 +02:00
Clyde Stubbs b66822d9bd [ai] Advice to agents to limit verbiage (#18980) 2026-09-05 12:21:47 +02:00
dependabot[bot] d1829c495d Bump prek from 0.5.0 to 0.5.1 (#18977)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-04 19:05:36 -04:00
dependabot[bot] ce87bf9b17 Bump platformdirs from 4.11.5 to 4.11.7 (#18976)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-04 19:05:26 -04:00
Jesse Hills 51ea97deff [esp32_ble] Reference count BLE advertising (#18943) 2026-09-05 08:38:55 +12:00
dependabot[bot] ab800dc09d Bump filelock from 3.32.4 to 3.32.5 (#18963)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-03 17:19:29 -04:00
J. Nick Kostonandpre-commit-ci-lite[bot] f65ab5629e [esp8266] Drop Arduino framework versions before 3.0.0 (#18917) to
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-09-03 15:16:36 -04:00
esphome[bot]andJonathan Swoboda b84532d254 Bump bundled esphome-device-builder to 1.14.0 (#18960)
Co-authored-by: esphome[bot] <115708604+esphome[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2026-09-03 12:15:06 +00:00
Keith Burzinski 6b11636491 [remote_transmitter] Fix BK7231N build by limiting the PWM path to BK7238 (#18958) 2026-09-03 08:12:36 -04:00
Jesse Hills 2bb98f2d64 Merge branch 'beta' into dev 2026-09-03 14:07:41 +12:00
Jesse Hills f3c786c784 Bump version to 2026.10.0-dev 2026-09-03 13:07:21 +12:00
dependabot[bot] 2250430999 Bump zeroconf from 0.151.2 to 0.151.3 (#18951)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-02 20:56:51 -04:00
dependabot[bot] d1068d582f Bump ninja from 1.13.0 to 1.13.2 (#18952)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-02 20:56:42 -04:00
233 changed files with 5100 additions and 3252 deletions
+11 -2
View File
@@ -244,11 +244,20 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Read prek version from requirements_test.txt
id: prek
# requirements_test.txt is the only place the version is pinned, so a
# Dependabot bump there is picked up here without a second edit.
run: |
if ! version=$(sed -nE 's/^prek==([^[:space:]#]+).*/\1/p' requirements_test.txt) || [ -z "$version" ]; then
echo "::error::No prek== pin found in requirements_test.txt."
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Run prek
uses: j178/prek-action@4e14d07f9231acabce116ccfca13b13dd9755ece # v3.0.0
with:
# Keep in sync with requirements_test.txt.
prek-version: "0.4.11"
prek-version: ${{ steps.prek.outputs.version }}
# This job only runs on pull requests, so nothing ever populates
# the cache on dev. Every run would miss and then write a per-pull
# request copy, which is what the old seed-cache job existed to
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
and will be closed if no further activity occurs within 7 days.
If you are the author of this PR, please leave a comment if you want
to keep it open. Also, please rebase your PR onto the latest dev
to keep it open. Also, please merge the latest dev branch into your
branch to ensure that it's up to date with the latest changes.
Thank you for your contribution!
@@ -0,0 +1,94 @@
# Keeps pre-commit hook revs in sync with the requirements files.
#
# Dependabot only bumps the pins in requirements*.txt. Some of those tools
# are pinned again as hook revs in .pre-commit-config.yaml. This workflow
# runs script/sync_dependency_versions.py against the pull request branch
# and pushes a commit with the revs updated.
name: Sync dependency versions
on:
# pull_request_target rather than pull_request so the App secret is
# available on Dependabot pull requests (pull_request runs opened by
# Dependabot only see Dependabot secrets). The job below only touches
# branches in this repository and only ever executes the script from the
# base branch checkout, so fork code never runs with the token.
pull_request_target:
types: [opened, synchronize, reopened]
paths:
- requirements_dev.txt
- requirements_test.txt
- .pre-commit-config.yaml
- script/sync_dependency_versions.py
# The push to the pull request branch uses the App token minted below, so
# the workflow's GITHUB_TOKEN does not need any scopes.
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
sync:
name: Sync pinned versions
runs-on: ubuntu-latest
# Same-repository branches only: a push to a fork is not possible with
# this token, and it keeps untrusted heads out of a privileged job.
if: >-
github.repository == 'esphome/esphome'
&& github.event.pull_request.head.repo.full_name == github.repository
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.ESPHOME_GITHUB_APP_CLIENT_ID }}
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
# A push made with the workflow's own GITHUB_TOKEN would not start
# CI on the new commit; a push with the App token does.
permission-contents: write # git push of the sync commit to the pull request branch
- name: Check out base branch
# Provides the script that runs below. Deliberately the base branch
# so the pull request cannot change what executes here.
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.base.sha }}
persist-credentials: false
- name: Check out pull request branch
# No allow-unsafe-pr-checkout here on purpose: checkout v7 only
# refuses heads that live in a different repository, and the job
# condition above already limits runs to same-repository branches.
# Leaving it off keeps that refusal as a backstop for fork heads.
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.ref }}
path: pull-request
token: ${{ steps.generate-token.outputs.token }}
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- name: Install yamlrocks
# The script edits YAML through yamlrocks. Take the pin from the
# base branch requirements so this workflow has no copy of its own.
run: pip install "$(grep -E '^yamlrocks==' requirements_test.txt | cut -d'#' -f1)"
- name: Sync pinned versions
run: python script/sync_dependency_versions.py --root pull-request
- name: Push changes
working-directory: pull-request
run: |
if git diff --quiet; then
echo "All pinned versions already match the requirements files."
exit 0
fi
git config user.name "esphome[bot]"
git config user.email "115708604+esphome[bot]@users.noreply.github.com"
git commit -am "Sync pinned tool versions with requirements files"
git push
+2 -3
View File
@@ -1,7 +1,6 @@
---
# See https://pre-commit.com for more information
# 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
@@ -11,7 +10,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.16.3
rev: v0.16.6
hooks:
# Run the linter.
- id: ruff
@@ -42,7 +41,7 @@ repos:
- id: pyupgrade
args: [--py312-plus]
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.37.1
rev: v1.38.0
hooks:
- id: yamllint
exclude: ^(\.clang-format|\.clang-tidy)$
+5 -1
View File
@@ -553,6 +553,7 @@ file does, and it is the authority when they disagree. The most useful starting
4. **Lint:** Run `prek` to ensure code is compliant.
5. **Commit:** Commit your changes. There is no strict format for commit messages.
6. **Pull Request:** Submit a PR against the `dev` branch. The Pull Request title must start with a `[tag]` prefix. For component work, use the component name (e.g., `[display] Fix bug`, `[abc123] Add new component`); for changes to shared/core code that isn't tied to a single component, use `[core]` (e.g., `[core] Add validator`). Update documentation, examples, and add `CODEOWNERS` entries as needed. Pull requests should always be made using the `.github/PULL_REQUEST_TEMPLATE.md` template - fill out all sections completely without removing any parts of the template.
7. **Comments:** When commenting on GitHub PRs or issues, don't tag contributors, especially bots. Avoid referring to list items (e.g. from reviews) with the form #nn - this will be interpreted by GitHub as a reference to issue or PR nn. Keep comments short and exclude irrelevant details, backstories, restatement of previous comments and anything that is already obvious to the reader.
* **Documentation Contributions:**
* Documentation is hosted in the separate `esphome/esphome.io` repository.
@@ -628,6 +629,9 @@ file does, and it is the authority when they disagree. The most useful starting
_request_listener_slot()
cg.add(hub.register_listener(var))
```
When several instances each own a list declared at the same size (one per hub of a
`MULTI_CONF` component), pass the owning object as the key, `_request_listener_slot(str(hub))`;
the define is then the largest count any one key requested instead of the total.
```cpp
#ifdef MY_COMPONENT_LISTENER_COUNT
void register_listener(MyComponentListener *listener);
@@ -839,7 +843,7 @@ file does, and it is the authority when they disagree. The most useful starting
cv.rename_key(
CONF_OLD_KEY, CONF_NEW_KEY, removed_in="2026.6.0", component="my_component"
),
cv.Schema({ ... }),
cv.Schema({...}),
)
```
For other deprecations, warn manually during validation:
+1 -1
View File
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 2026.9.0b4
PROJECT_NUMBER = 2026.10.0-dev
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
+1 -1
View File
@@ -22,7 +22,7 @@ RUN \
-r /requirements.txt
# Install the ESPHome Device Builder dashboard.
RUN uv pip install --no-cache-dir esphome-device-builder==1.14.9
RUN uv pip install --no-cache-dir esphome-device-builder==1.14.7
RUN \
platformio settings set enable_telemetry No \
+1 -3
View File
@@ -23,9 +23,7 @@ from esphome.util import safe_print
if TYPE_CHECKING:
from collections.abc import Callable
from aioesphomeapi.api_pb2 import (
SubscribeLogsResponse, # pylint: disable=no-name-in-module
)
from aioesphomeapi.api_pb2 import SubscribeLogsResponse # pylint: disable=no-name-in-module
_LOGGER = logging.getLogger(__name__)
+3 -2
View File
@@ -350,9 +350,10 @@ CONFIG_SCHEMA = cv.All(
ln882x=5, # Moderate RAM
nrf52=4, # ~256KB RAM, BSD sockets, Thread (single HA controller)
): cv.int_range(min=1, max=20),
# Max queued messages per connection, and 2 KB of backlog per slot up
# to 64 KB (a lone message is exempt), before the connection is dropped
# Maximum queued send buffers per connection before dropping connection
# Each buffer uses ~8-12 bytes overhead plus actual message size
# Platform defaults based on available RAM and typical message rates:
# CONF_MAX_SEND_QUEUE defaults are power of 2 for efficient modulo
cv.SplitDefault(
CONF_MAX_SEND_QUEUE,
esp8266=4, # Limited RAM, need to fail fast
+33 -4
View File
@@ -77,6 +77,7 @@ service APIConnection {
rpc serial_proxy_set_modem_pins(SerialProxySetModemPinsRequest) returns (void) {}
rpc serial_proxy_get_modem_pins(SerialProxyGetModemPinsRequest) returns (void) {}
rpc serial_proxy_request(SerialProxyRequest) returns (void) {}
rpc serial_proxy_set_mode(SerialProxySetModeRequest) returns (void) {}
}
@@ -2726,7 +2727,8 @@ enum SerialProxyParity {
SERIAL_PROXY_PARITY_ODD = 2;
}
// Configure UART parameters for a serial proxy instance
// Configure UART parameters for a serial proxy instance. Only the subscribed client may
// configure the port; others are refused with PORT_IN_USE (since API 1.17).
message SerialProxyConfigureRequest {
option (id) = 138;
option (source) = SOURCE_CLIENT;
@@ -2752,7 +2754,8 @@ message SerialProxyDataReceived {
bytes data = 2; // Raw data received from the serial device
}
// Write data to a serial device
// Write data to a serial device. Only the subscribed client may write; writes from
// others are ignored (since API 1.17).
message SerialProxyWriteRequest {
option (id) = 140;
option (source) = SOURCE_CLIENT;
@@ -2763,7 +2766,8 @@ message SerialProxyWriteRequest {
bytes data = 2; // Raw data to write to the serial device
}
// Set modem control pin states (RTS and DTR)
// Set modem control pin states (RTS and DTR). Only the subscribed client may set them;
// others are refused with PORT_IN_USE (since API 1.17).
message SerialProxySetModemPinsRequest {
option (id) = 141;
option (source) = SOURCE_CLIENT;
@@ -2802,6 +2806,7 @@ enum SerialProxyRequestType {
// error the device answers with INVALID_ARGUMENT.
SERIAL_PROXY_REQUEST_TYPE_CONFIGURE = 3; // Acknowledges a SerialProxyConfigureRequest
SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS = 4; // Acknowledges a SerialProxySetModemPinsRequest
SERIAL_PROXY_REQUEST_TYPE_SET_MODE = 5; // Acknowledges a SerialProxySetModeRequest (since API 1.17)
}
enum SerialProxyStatus {
@@ -2814,7 +2819,8 @@ enum SerialProxyStatus {
SERIAL_PROXY_STATUS_INVALID_ARGUMENT = 6; // Invalid instance index or parameter value
}
// Generic request message for simple serial proxy operations
// Generic request message for simple serial proxy operations. FLUSH requires an active
// subscription; it is refused with PORT_IN_USE otherwise (since API 1.17).
message SerialProxyRequest {
option (id) = 144;
option (source) = SOURCE_CLIENT;
@@ -2838,6 +2844,29 @@ message SerialProxyRequestResponse {
string error_message = 4; // Additional detail on failure (optional)
}
// How a port treats the bytes passing through it. RAW is a plain byte pipe; PROTOCOL
// activates the port's protocol-aware tap (if one is configured), letting it observe
// traffic and inject protocol bytes such as acknowledgements. Which protocol the tap
// speaks is a property of the device configuration, discoverable from the tap
// component's own API surface. A client that is about to flash firmware selects RAW
// first, which definitively disables that injection.
enum SerialProxyMode {
SERIAL_PROXY_MODE_RAW = 0;
SERIAL_PROXY_MODE_PROTOCOL = 1;
}
// Only the subscribed client may change the mode; any other caller -- including one that
// never subscribed -- is refused with PORT_IN_USE. PROTOCOL is refused with NOT_SUPPORTED
// when the port has no protocol-aware tap configured.
message SerialProxySetModeRequest {
option (id) = 152;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_SERIAL_PROXY";
uint32 instance = 1;
SerialProxyMode mode = 2;
}
// ==================== BLUETOOTH CONNECTION PARAMS ====================
message BluetoothSetConnectionParamsRequest {
option (id) = 145;
+9 -26
View File
@@ -1,37 +1,20 @@
#include "api_buffer.h"
#ifdef ESPHOME_DEBUG_API
#include "esphome/core/log.h"
#endif
#include <new>
namespace esphome::api {
#ifdef ESPHOME_DEBUG_API
void APIBuffer::debug_check_drop_(size_t drop) const {
if (drop > this->size_) {
ESP_LOGE("api.buffer", "drop_front: drop=%zu size=%u", drop, this->size_);
abort();
}
}
#endif
bool APIBuffer::grow_(size_t n) {
if (n > MAX_SIZE)
// nothrow (no zero-fill) so OOM is reportable; plain new aborts instead
// (NEW_OOM_ABORT on ESP8266 Arduino, exception stub on ESP-IDF).
// RAMAllocator is no fit here: unique_ptr needs delete[]-compatible memory.
std::unique_ptr<uint8_t[]> new_data(new (std::nothrow) uint8_t[n]);
if (new_data == nullptr)
return false;
// realloc extends in place when it can, avoiding the copy
uint8_t *grown = RAMAllocator<uint8_t>().reallocate(this->data_.get(), n);
if (grown == nullptr)
return false;
(void) this->data_.release(); // realloc already freed or reused the old block
this->data_.reset(grown);
if (this->size_)
std::memcpy(new_data.get(), this->data_.get(), this->size_);
this->data_ = std::move(new_data);
this->capacity_ = n;
return true;
}
uint8_t *APIBuffer::append(size_t n) {
const size_t old_size = this->size_;
if (!this->resize(old_size + n))
return nullptr;
return this->data_.get() + old_size;
}
} // namespace esphome::api
+4 -20
View File
@@ -25,7 +25,6 @@ namespace esphome::api {
/// writes in debug builds.
class APIBuffer {
public:
static constexpr size_t MAX_SIZE = UINT16_MAX; // API frames carry 16 bit lengths
void clear() { this->size_ = 0; }
/// Returns false if allocation fails; the buffer is left unchanged.
[[nodiscard]] inline bool reserve(size_t n) ESPHOME_ALWAYS_INLINE { return n <= this->capacity_ || this->grow_(n); }
@@ -37,19 +36,9 @@ class APIBuffer {
[[nodiscard]] inline bool reserve_and_resize(size_t reserve_size, size_t new_size) ESPHOME_ALWAYS_INLINE {
if (!this->reserve(std::max(reserve_size, new_size)))
return false;
this->size_ = static_cast<uint16_t>(new_size);
this->size_ = new_size;
return true;
}
/// Grow by n bytes; returns the new bytes, or nullptr on allocation failure.
[[nodiscard]] uint8_t *append(size_t n);
/// Drop the first `drop` bytes, sliding the rest down. Precondition: drop <= size().
void drop_front(size_t drop) {
#ifdef ESPHOME_DEBUG_API
this->debug_check_drop_(drop);
#endif
this->size_ -= drop;
std::memmove(this->data_.get(), this->data_.get() + drop, this->size_);
}
uint8_t *data() { return this->data_.get(); }
const uint8_t *data() const { return this->data_.get(); }
size_t size() const { return this->size_; }
@@ -66,14 +55,9 @@ class APIBuffer {
protected:
bool grow_(size_t n);
#ifdef ESPHOME_DEBUG_API
void debug_check_drop_(size_t drop) const;
#endif
// RAMAllocator: PSRAM when available, and it reports failure where
// new (std::nothrow) still aborts on ESP-IDF without exceptions
RAMUniquePtr<uint8_t[]> data_;
uint16_t size_{0};
uint16_t capacity_{0};
std::unique_ptr<uint8_t[]> data_;
size_t size_{0};
size_t capacity_{0};
};
} // namespace esphome::api
+16 -5
View File
@@ -364,10 +364,7 @@ void APIConnection::check_keepalive_(uint32_t now) {
ESP_LOGVV(TAG, "Sending keepalive PING");
PingRequest req;
this->flags_.sent_ping = this->send_message(req);
if (this->flags_.sent_ping) {
// Quiet for a keepalive period and the ping is on its way: a one-off stall's storage can go
this->helper_->release_overflow_buffer();
} else {
if (!this->flags_.sent_ping) {
// 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
ESP_LOGW(TAG, "Buffer full, ping queued");
@@ -1664,6 +1661,7 @@ void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
break;
case enums::SERIAL_PROXY_REQUEST_TYPE_CONFIGURE:
case enums::SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS:
case enums::SERIAL_PROXY_REQUEST_TYPE_SET_MODE:
// Response-only discriminators; never valid in a request
ESP_LOGW(TAG, "Response-only serial proxy request type: %" PRIu32, static_cast<uint32_t>(msg.type));
status = enums::SERIAL_PROXY_STATUS_INVALID_ARGUMENT;
@@ -1676,6 +1674,19 @@ void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
send_serial_proxy_ack(this, msg.instance, msg.type, status);
}
void APIConnection::on_serial_proxy_set_mode_request(const SerialProxySetModeRequest &msg) {
auto &proxies = App.get_serial_proxies();
if (msg.instance >= proxies.size()) {
ESP_LOGW(TAG, "Serial proxy instance %" PRIu32 " out of range", msg.instance);
send_serial_proxy_ack(this, msg.instance, enums::SERIAL_PROXY_REQUEST_TYPE_SET_MODE,
enums::SERIAL_PROXY_STATUS_INVALID_ARGUMENT);
return;
}
serial_proxy::SerialProxyResult result = proxies[msg.instance]->set_mode_from_client(this, msg.mode);
send_serial_proxy_ack(this, msg.instance, enums::SERIAL_PROXY_REQUEST_TYPE_SET_MODE,
serial_proxy_result_to_status(result));
}
void APIConnection::send_serial_proxy_data(const SerialProxyDataReceived &msg) {
if (!this->send_message(msg)) {
ESP_LOGV(TAG, "Serial proxy data dropped, TCP buffer full");
@@ -1802,7 +1813,7 @@ bool APIConnection::send_hello_response_(const HelloRequest &msg) {
HelloResponse resp;
resp.api_version_major = 1;
resp.api_version_minor = 16;
resp.api_version_minor = 17;
// Send only the version string - the client only logs this for debugging and doesn't use it otherwise
resp.server_info = ESPHOME_VERSION_REF;
resp.name = StringRef(App.get_name());
+1
View File
@@ -244,6 +244,7 @@ class APIConnection final : public APIServerConnectionBase {
void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg);
void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg);
void on_serial_proxy_request(const SerialProxyRequest &msg);
void on_serial_proxy_set_mode_request(const SerialProxySetModeRequest &msg);
void send_serial_proxy_data(const SerialProxyDataReceived &msg);
#endif
+1 -1
View File
@@ -171,7 +171,7 @@ APIError APIFrameHelper::write_raw_iov_(const struct iovec *iov, int iovcnt, uin
return APIError::OK;
// Queue unsent data into overflow buffer
if (!this->overflow_buf_.enqueue_iov(iov, iovcnt, total_write_len, sent)) {
if (!this->overflow_buf_.enqueue_iov(iov, iovcnt, total_write_len, static_cast<uint16_t>(sent))) {
HELPER_LOG("Overflow buffer full or out of memory, dropping connection");
this->state_ = State::FAILED;
return APIError::SOCKET_WRITE_FAILED;
@@ -219,10 +219,7 @@ class APIFrameHelper {
if (this->rx_buf_len_ == 0) {
this->rx_buf_.release();
}
this->release_overflow_buffer();
}
// Free the send backlog storage once it has drained
void release_overflow_buffer() { this->overflow_buf_.release(); }
protected:
// Drain backlogged overflow data to the socket and handle errors.
@@ -67,15 +67,15 @@ APIError APINoiseFrameHelper::init() {
}
// init prologue
uint8_t *dst = prologue_.append(PROLOGUE_INIT_LEN);
if (dst == nullptr) [[unlikely]] {
size_t old_size = prologue_.size();
if (!prologue_.resize(old_size + PROLOGUE_INIT_LEN)) [[unlikely]] {
state_ = State::FAILED;
return APIError::OUT_OF_MEMORY;
}
#ifdef USE_ESP8266
memcpy_P(dst, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
memcpy_P(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
#else
std::memcpy(dst, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
std::memcpy(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
#endif
state_ = State::CLIENT_HELLO;
@@ -272,17 +272,17 @@ APIError APINoiseFrameHelper::state_action_client_hello_() {
return handle_handshake_frame_error_(aerr);
}
// ignore contents, may be used in future for flags
// Append 2 size bytes + frame data to the prologue
// Resize for: existing prologue + 2 size bytes + frame data
size_t old_size = this->prologue_.size();
size_t rx_size = this->rx_buf_.size();
uint8_t *dst = this->prologue_.append(2 + rx_size);
if (dst == nullptr) [[unlikely]] {
if (!this->prologue_.resize(old_size + 2 + rx_size)) [[unlikely]] {
state_ = State::FAILED;
return APIError::OUT_OF_MEMORY;
}
dst[0] = (uint8_t) (rx_size >> 8);
dst[1] = (uint8_t) rx_size;
this->prologue_[old_size] = (uint8_t) (rx_size >> 8);
this->prologue_[old_size + 1] = (uint8_t) rx_size;
if (rx_size > 0) {
std::memcpy(dst + 2, this->rx_buf_.data(), rx_size);
std::memcpy(this->prologue_.data() + old_size + 2, this->rx_buf_.data(), rx_size);
}
state_ = State::SERVER_HELLO;
+64 -57
View File
@@ -1,91 +1,98 @@
#include "api_overflow_buffer.h"
#ifdef USE_API
#include <cstring>
#include <new>
namespace esphome::api {
APIOverflowBuffer::~APIOverflowBuffer() {
for (auto *entry : this->queue_) {
if (entry != nullptr)
Entry::destroy(entry);
}
}
ssize_t APIOverflowBuffer::try_drain(socket::Socket *socket) {
// Nested call from inside socket->write(); see draining_
// socket->write() can re-enter this function: a log message emitted from an
// lwip callback during the write goes out over the API and lands back in the
// frame helper's write/drain path. If a nested drain ran here it would send
// and free the entry the outer drain is still holding, causing a double free.
// Report "no progress" instead; the outer drain keeps draining, and the
// nested send is enqueued behind the existing backlog.
if (this->draining_)
return 0;
// RAII so the flag is cleared on every return path
struct DrainGuard {
APIOverflowBuffer &owner;
~DrainGuard() { this->owner.draining_ = false; }
} guard{*this};
this->draining_ = true;
explicit DrainGuard(bool &flag) : flag_(flag) { flag_ = true; }
~DrainGuard() { this->flag_ = false; }
bool &flag_;
} guard(this->draining_);
while (this->count_ > 0) {
uint8_t *msg = this->buf_.data() + this->head_;
size_t len = msg[0] | (msg[1] << 8);
Entry *front = this->queue_[this->head_];
ssize_t sent = socket->write(msg + LEN_PREFIX, len);
if (sent <= 0)
return sent;
if (static_cast<size_t>(sent) < len) {
// Step past the sent bytes and rewrite the prefix there; it lands on bytes already sent
this->head_ += sent;
len -= sent;
msg += sent;
msg[0] = len;
msg[1] = len >> 8;
ssize_t sent = socket->write(front->current_data(), front->remaining());
if (sent <= 0) {
// -1 = error (caller checks errno for EWOULDBLOCK vs hard error)
// 0 = nothing sent (treat as no progress)
return sent;
}
this->head_ += LEN_PREFIX + len;
if (static_cast<uint16_t>(sent) < front->remaining()) {
// Partially sent, update offset and stop
front->offset += static_cast<uint16_t>(sent);
return sent;
}
// Entry fully sent — unlink it before freeing so a freed pointer is never
// reachable from the queue
this->queue_[this->head_] = nullptr;
this->head_ = (this->head_ + 1) % API_MAX_SEND_QUEUE;
this->count_--;
Entry::destroy(front);
}
this->head_ = 0;
if (this->release_when_drained_) {
this->release_when_drained_ = false;
this->buf_.release();
} else {
this->buf_.clear();
}
return 0;
return 0; // All drained
}
bool APIOverflowBuffer::enqueue_iov(const struct iovec *iov, int iovcnt, size_t total_len, size_t skip) {
bool APIOverflowBuffer::enqueue_iov(const struct iovec *iov, int iovcnt, uint16_t total_len, uint16_t skip) {
if (this->count_ >= API_MAX_SEND_QUEUE)
return false;
const size_t new_len = total_len - skip;
const size_t new_bytes = LEN_PREFIX + new_len;
const size_t live = this->buf_.size() - this->head_;
// A lone message is only bound by the buffer; refusing it would just drop the connection
if (live + new_bytes > (this->count_ > 0 ? MAX_BYTES : MAX_LONE_BYTES))
uint16_t buffer_size = total_len - skip;
// nothrow: a failed allocation returns nullptr so the connection is dropped
// cleanly instead of plain new's crash or abort on OOM
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
auto *data = new (std::nothrow) uint8_t[buffer_size];
if (data == nullptr)
return false;
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
auto *entry = new (std::nothrow) Entry{data, buffer_size, 0};
if (entry == nullptr) {
delete[] data;
return false;
if (this->buf_.size() + new_bytes > this->buf_.capacity()) {
// Storage would move under an outer drain's write()
if (this->draining_)
return false;
if (this->head_ > 0) {
// Reclaim the sent prefix before growing
this->buf_.drop_front(this->head_);
this->head_ = 0;
}
if (!this->buf_.reserve(reserve_for(live + new_bytes)))
return false;
}
uint8_t *dst = this->buf_.append(new_bytes);
if (dst == nullptr)
return false;
dst[0] = new_len;
dst[1] = new_len >> 8;
dst += LEN_PREFIX;
for (const struct iovec *end = iov + iovcnt; iov != end; iov++) {
if (skip >= iov->iov_len) {
skip -= iov->iov_len;
uint16_t to_skip = skip;
uint16_t write_pos = 0;
for (int i = 0; i < iovcnt; i++) {
if (to_skip >= iov[i].iov_len) {
to_skip -= static_cast<uint16_t>(iov[i].iov_len);
} else {
const size_t len = iov->iov_len - skip;
std::memcpy(dst, static_cast<const uint8_t *>(iov->iov_base) + skip, len);
dst += len;
skip = 0;
const uint8_t *src = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_skip;
uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_skip;
std::memcpy(entry->data + write_pos, src, len);
write_pos += len;
to_skip = 0;
}
}
// Publish only after the copy completes so a half-built entry is never reachable
this->queue_[this->tail_] = entry;
this->tail_ = (this->tail_ + 1) % API_MAX_SEND_QUEUE;
this->count_++;
return true;
}
+53 -40
View File
@@ -1,6 +1,5 @@
#pragma once
#include <algorithm>
#include <cstddef>
#include <array>
#include <cstdint>
#include <sys/types.h>
@@ -9,57 +8,71 @@
#include "esphome/components/socket/headers.h"
#include "esphome/components/socket/socket.h"
#include "api_buffer.h"
#include "esphome/core/helpers.h"
namespace esphome::api {
/// TCP send backlog, only used when the socket send buffer is full.
/// One contiguous buffer per connection, allocated on the first stall and
/// kept at its high-water mark so a lossy link does not churn the heap.
/// Messages are stored as a 2 byte length prefix plus payload.
/// API_MAX_SEND_QUEUE bounds queued messages and, at 2 KB per slot, queued
/// bytes; exceeding either fails the connection.
/// Circular queue of heap-allocated byte buffers used as a TCP send backlog.
///
/// Under normal operation this buffer is **never used** — data goes straight
/// from the frame helper to the socket. It only fills when the LWIP TCP
/// send buffer is full (slow client, congested network, heavy logging).
/// The queue drains automatically on subsequent write/loop calls once the
/// socket becomes writable again.
///
/// Capacity is compile-time-fixed via API_MAX_SEND_QUEUE (set from Python
/// config). If the queue fills completely the connection is marked failed.
class APIOverflowBuffer {
public:
/// A single heap-allocated send-backlog entry.
/// Lifetime is manually managed — see destroy().
struct Entry {
uint8_t *data;
uint16_t size; // Total size of the buffer
uint16_t offset; // Current send offset within the buffer
uint16_t remaining() const { return this->size - this->offset; }
const uint8_t *current_data() const { return this->data + this->offset; }
/// Free this entry and its data buffer.
static ESPHOME_ALWAYS_INLINE void destroy(Entry *entry) {
delete[] entry->data;
delete entry; // NOLINT(cppcoreguidelines-owning-memory)
}
};
~APIOverflowBuffer();
/// True when no backlogged data is waiting.
bool empty() const { return this->count_ == 0; }
/// Drain queued messages to the socket.
/// Returns bytes written, 0 for a re-entrant call, -1 on error (check errno
/// for EWOULDBLOCK); callers only need to act on -1.
/// True when the queue has no room for another entry.
bool full() const { return this->count_ >= API_MAX_SEND_QUEUE; }
/// Number of entries currently queued.
uint8_t count() const { return this->count_; }
/// Try to drain queued data to the socket.
/// Returns bytes-written > 0 on success/partial, 0 if all drained or no progress,
/// -1 on error (caller must check errno to distinguish EWOULDBLOCK from hard errors).
/// Callers only need to act on -1; 0 and positive values both mean "no error".
/// Frees entries as they are fully sent.
ssize_t try_drain(socket::Socket *socket);
/// Queue iov data from byte offset `skip` as one message.
/// Returns false when a limit is hit, allocation fails, or storage would move
/// during a drain; the caller should fail the connection.
bool enqueue_iov(const struct iovec *iov, int iovcnt, size_t total_len, size_t skip);
/// Free the retained storage, now if empty, otherwise once it has drained.
void release() {
if (this->count_ == 0) {
this->buf_.release();
} else {
this->release_when_drained_ = true;
}
}
/// Enqueue unsent IOV data into the backlog.
/// Copies iov data starting at byte offset `skip` into a new entry.
/// Returns false if the queue is full or allocation fails (caller should fail the connection).
bool enqueue_iov(const struct iovec *iov, int iovcnt, uint16_t total_len, uint16_t skip);
protected:
static constexpr size_t LEN_PREFIX = 2;
static constexpr size_t BYTES_PER_SLOT = 2048;
// Reserve in 256 byte steps so a creeping high-water mark settles quickly
static constexpr size_t GROW_QUANTUM = 256;
// Lone message ceiling, rounded down so reserve_for() never exceeds the buffer limit
static constexpr size_t MAX_LONE_BYTES = APIBuffer::MAX_SIZE & ~(GROW_QUANTUM - 1);
static constexpr size_t MAX_BYTES = std::min(API_MAX_SEND_QUEUE * BYTES_PER_SLOT, MAX_LONE_BYTES);
static constexpr size_t reserve_for(size_t want) { return (want + GROW_QUANTUM - 1) & ~(GROW_QUANTUM - 1); }
APIBuffer buf_;
uint16_t head_{0}; // offset of the front message's length prefix; bytes before it are sent
std::array<Entry *, API_MAX_SEND_QUEUE> queue_{};
uint8_t head_{0};
uint8_t tail_{0};
uint8_t count_{0};
// socket->write() can re-enter the send path (log from an lwip callback):
// a nested drain makes no progress and a nested enqueue never moves storage
bool draining_ : 1 {false};
bool release_when_drained_ : 1 {false};
// Guards against re-entrant drains: socket->write() can re-enter the API
// send path (e.g. a log message emitted from an lwip callback), and a nested
// drain would free the entry the outer drain is still holding.
bool draining_{false};
};
} // namespace esphome::api
+13
View File
@@ -4253,6 +4253,19 @@ uint32_t SerialProxyRequestResponse::calculate_size() const {
size += ProtoSize::calc_length(1, this->error_message.size());
return size;
}
bool SerialProxySetModeRequest::decode_varint(uint32_t field_id, proto_varint_value_t value) {
switch (field_id) {
case 1:
this->instance = value;
break;
case 2:
this->mode = static_cast<enums::SerialProxyMode>(value);
break;
default:
return false;
}
return true;
}
#endif
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
bool BluetoothSetConnectionParamsRequest::decode_varint(uint32_t field_id, proto_varint_value_t value) {
+21
View File
@@ -356,6 +356,7 @@ enum SerialProxyRequestType : uint32_t {
SERIAL_PROXY_REQUEST_TYPE_FLUSH = 2,
SERIAL_PROXY_REQUEST_TYPE_CONFIGURE = 3,
SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS = 4,
SERIAL_PROXY_REQUEST_TYPE_SET_MODE = 5,
};
enum SerialProxyStatus : uint32_t {
SERIAL_PROXY_STATUS_OK = 0,
@@ -366,6 +367,10 @@ enum SerialProxyStatus : uint32_t {
SERIAL_PROXY_STATUS_PORT_IN_USE = 5,
SERIAL_PROXY_STATUS_INVALID_ARGUMENT = 6,
};
enum SerialProxyMode : uint32_t {
SERIAL_PROXY_MODE_RAW = 0,
SERIAL_PROXY_MODE_PROTOCOL = 1,
};
#endif
} // namespace enums
@@ -3403,6 +3408,22 @@ class SerialProxyRequestResponse final : public ProtoMessage {
protected:
};
class SerialProxySetModeRequest final : public ProtoDecodableMessage {
public:
static constexpr uint16_t MESSAGE_TYPE = 152;
static constexpr uint8_t ESTIMATED_SIZE = 6;
#ifdef HAS_PROTO_MESSAGE_DUMP
const LogString *message_name() const override { return LOG_STR("serial_proxy_set_mode_request"); }
#endif
uint32_t instance{0};
enums::SerialProxyMode mode{};
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, proto_varint_value_t value) override;
};
#endif
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
class BluetoothSetConnectionParamsRequest final : public ProtoDecodableMessage {
+18
View File
@@ -854,6 +854,8 @@ template<> const char *proto_enum_to_string<enums::SerialProxyRequestType>(enums
return ESPHOME_PSTR("SERIAL_PROXY_REQUEST_TYPE_CONFIGURE");
case enums::SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS:
return ESPHOME_PSTR("SERIAL_PROXY_REQUEST_TYPE_SET_MODEM_PINS");
case enums::SERIAL_PROXY_REQUEST_TYPE_SET_MODE:
return ESPHOME_PSTR("SERIAL_PROXY_REQUEST_TYPE_SET_MODE");
default:
return ESPHOME_PSTR("UNKNOWN");
}
@@ -878,6 +880,16 @@ template<> const char *proto_enum_to_string<enums::SerialProxyStatus>(enums::Ser
return ESPHOME_PSTR("UNKNOWN");
}
}
template<> const char *proto_enum_to_string<enums::SerialProxyMode>(enums::SerialProxyMode value) {
switch (value) {
case enums::SERIAL_PROXY_MODE_RAW:
return ESPHOME_PSTR("SERIAL_PROXY_MODE_RAW");
case enums::SERIAL_PROXY_MODE_PROTOCOL:
return ESPHOME_PSTR("SERIAL_PROXY_MODE_PROTOCOL");
default:
return ESPHOME_PSTR("UNKNOWN");
}
}
#endif
const char *HelloRequest::dump_to(DumpBuffer &out) const {
@@ -2805,6 +2817,12 @@ const char *SerialProxyRequestResponse::dump_to(DumpBuffer &out) const {
dump_field(out, ESPHOME_PSTR("error_message"), this->error_message);
return out.c_str();
}
const char *SerialProxySetModeRequest::dump_to(DumpBuffer &out) const {
MessageDumpHelper helper(out, ESPHOME_PSTR("SerialProxySetModeRequest"));
dump_field(out, ESPHOME_PSTR("instance"), this->instance);
dump_field(out, ESPHOME_PSTR("mode"), static_cast<enums::SerialProxyMode>(this->mode));
return out.c_str();
}
#endif
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
const char *BluetoothSetConnectionParamsRequest::dump_to(DumpBuffer &out) const {
@@ -712,6 +712,17 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui
this->on_device_capabilities_request();
break;
}
#ifdef USE_SERIAL_PROXY
case SerialProxySetModeRequest::MESSAGE_TYPE: {
SerialProxySetModeRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
this->log_receive_message_(LOG_STR("on_serial_proxy_set_mode_request"), msg);
#endif
this->on_serial_proxy_set_mode_request(msg);
break;
}
#endif
default:
break;
}
+3
View File
@@ -235,6 +235,9 @@ class APIServerConnectionBase {
void on_serial_proxy_request(const SerialProxyRequest &value){};
#endif
#ifdef USE_SERIAL_PROXY
void on_serial_proxy_set_mode_request(const SerialProxySetModeRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY_CONNECTIONS
void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &value){};
#endif
@@ -45,7 +45,15 @@ void BluedroidGattClient::setup() {
void BluedroidGattClient::loop() {
if (!esp32_ble::global_ble->is_active()) {
// ble_before_disabled_event_handler() settles the slot.
// Stack down: no CLOSE_EVT will come. Settle a live link so the consumer
// frees its slot, then re-register the app on the next enable.
auto down_st = this->state();
if (down_st != ClientState::IDLE && down_st != ClientState::INIT) {
this->release_services();
this->set_idle_();
this->listener_->on_connection_state(false, 0, ble_device_base::GATT_ERR_NOT_CONNECTED);
}
this->set_state(ClientState::INIT);
return;
}
auto st = this->state();
@@ -57,7 +65,7 @@ void BluedroidGattClient::loop() {
ESP_LOGE(TAG, "gattc app register failed: app_id=%d code=%d", this->app_id, ret);
this->mark_failed();
}
// Do not wait for REG_EVT; connect() rejects until it lands.
// Do not wait for REG_EVT; a dropped event must not wedge the slot.
this->set_idle_();
} else if (st == ClientState::DISCONNECTING || this->disconnect_pending()) {
// The one teardown safety net: a lost CLOSE_EVT, or a scheduled
@@ -70,8 +78,8 @@ void BluedroidGattClient::loop() {
this->listener_->on_connection_state(false, 0, ESP_GATT_CONN_TIMEOUT);
}
} else {
// The loop stays on while a link exists (pre-started search flush); it
// settles only back at IDLE.
// The loop stays on while a link exists (stack-down watch, pre-started
// search flush); it settles only back at IDLE.
this->deliver_pending_search_();
if (this->state() == ClientState::IDLE) {
this->disable_loop();
@@ -79,22 +87,6 @@ void BluedroidGattClient::loop() {
}
}
// Stack down: no CLOSE_EVT will come. Settle a live link so the consumer
// frees its slot, then register the app again on the next enable.
void BluedroidGattClient::ble_before_disabled_event_handler() {
auto st = this->state();
if (st != ClientState::IDLE && st != ClientState::INIT) {
this->release_services();
this->set_idle_();
this->listener_->on_connection_state(false, 0, ble_device_base::GATT_ERR_NOT_CONNECTED);
}
// The interface belongs to the torn-down stack.
this->gattc_if_ = ESP_GATT_IF_NONE;
this->set_state(ClientState::INIT);
// An idle slot runs no loop; the INIT branch must run to register again.
this->enable_loop();
}
void BluedroidGattClient::dump_config() {
ESP_LOGCONFIG(TAG, "Bluedroid GATT client %d", this->connection_index_);
if (this->is_failed()) {
@@ -105,11 +97,6 @@ void BluedroidGattClient::dump_config() {
// ---- contract ops ----
int BluedroidGattClient::connect(uint64_t address, uint8_t addr_type) {
if (this->gattc_if_ == ESP_GATT_IF_NONE) {
// Bluedroid drops an open on an unknown interface without any event.
ESP_LOGW(TAG, "[%d] Connect rejected, GATT app not registered", this->connection_index_);
return ble_device_base::GATT_ERR_NOT_CONNECTED;
}
// Only from idle: clobbering DISCONNECTING would open a new link the
// stale CLOSE_EVT then tears down.
if (this->state() != ClientState::IDLE) {
@@ -56,7 +56,6 @@ class BluedroidGattClient final : public esp32_ble_tracker::ESPBTClient, public
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
void connect() override;
void disconnect() override;
void ble_before_disabled_event_handler() override;
bool wants_parsed_advertisements() override { return false; }
void on_scan_end() override {}
bool parse_device(const ble_device_base::ESPBTDevice &device) override { return false; }
+1
View File
@@ -30,6 +30,7 @@ CONF_KEYS = "keys"
CONF_LABEL = "label"
CONF_LIBRETINY = "libretiny"
CONF_LOOP = "loop"
CONF_MANUFACTURER = "manufacturer"
CONF_NOX_INDEX = "nox_index"
CONF_ON_PACKET = "on_packet"
CONF_ON_RECEIVE = "on_receive"
+2 -1
View File
@@ -1,5 +1,5 @@
import esphome.codegen as cg
from esphome.components import climate_ir
from esphome.components import climate_ir, remote_base
from esphome.types import ConfigType
AUTO_LOAD = ["climate_ir"]
@@ -12,4 +12,5 @@ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(CoolixClimate)
async def to_code(config: ConfigType) -> None:
remote_base.request_protocol("coolix") # used from C++
await climate_ir.new_climate_ir(config)
+3 -3
View File
@@ -32,9 +32,9 @@ struct DateEntityRestoreState {
class DateEntity : public DateTimeBase {
protected:
uint16_t year_;
uint8_t month_;
uint8_t day_;
uint16_t year_{0};
uint8_t month_{0};
uint8_t day_{0};
public:
void publish_state();
+1 -1
View File
@@ -27,7 +27,7 @@ class DateTimeBase : public EntityBase {
LazyCallbackManager<void()> state_callback_;
#ifdef USE_TIME
time::RealTimeClock *rtc_;
time::RealTimeClock *rtc_{nullptr};
#endif
};
@@ -35,12 +35,12 @@ struct DateTimeEntityRestoreState {
class DateTimeEntity : public DateTimeBase {
protected:
uint16_t year_;
uint8_t month_;
uint8_t day_;
uint8_t hour_;
uint8_t minute_;
uint8_t second_;
uint16_t year_{0};
uint8_t month_{0};
uint8_t day_{0};
uint8_t hour_{0};
uint8_t minute_{0};
uint8_t second_{0};
public:
void publish_state();
+3 -3
View File
@@ -33,9 +33,9 @@ struct TimeEntityRestoreState {
class TimeEntity : public DateTimeBase {
protected:
uint8_t hour_;
uint8_t minute_;
uint8_t second_;
uint8_t hour_{0};
uint8_t minute_{0};
uint8_t second_{0};
public:
void publish_state();
+1 -5
View File
@@ -23,11 +23,7 @@ from esphome.const import (
)
from esphome.types import ConfigType
from . import ( # noqa: F401 pylint: disable=unused-import
CONF_DEBUG_ID,
FILTER_SOURCE_FILES,
DebugComponent,
)
from . import CONF_DEBUG_ID, FILTER_SOURCE_FILES, DebugComponent # noqa: F401 pylint: disable=unused-import
DEPENDENCIES = ["debug"]
+1 -5
View File
@@ -9,11 +9,7 @@ from esphome.const import (
)
from esphome.types import ConfigType
from . import ( # noqa: F401 pylint: disable=unused-import
CONF_DEBUG_ID,
FILTER_SOURCE_FILES,
DebugComponent,
)
from . import CONF_DEBUG_ID, FILTER_SOURCE_FILES, DebugComponent # noqa: F401 pylint: disable=unused-import
DEPENDENCIES = ["debug"]
@@ -44,7 +44,7 @@ bool DeepSleepComponent::prepare_to_sleep_() {
this->status_set_warning();
ESP_LOGV(TAG, "Waiting for pin to switch state to enter deep sleep...");
}
this->next_enter_deep_sleep_ = true;
this->defer_sleep_();
return false;
}
}
@@ -17,6 +17,7 @@ void DeepSleepComponent::setup() {
void DeepSleepComponent::schedule_sleep_() {
this->next_enter_deep_sleep_ = false;
this->disable_loop();
const optional<uint32_t> run_duration = get_run_duration_();
if (run_duration.has_value()) {
ESP_LOGI(TAG, "Scheduling in %" PRIu32 " ms", *run_duration);
@@ -45,7 +46,7 @@ void DeepSleepComponent::loop() {
void DeepSleepComponent::begin_sleep(bool manual) {
if (this->prevent_ && !manual) {
this->next_enter_deep_sleep_ = true;
this->defer_sleep_();
return;
}
@@ -190,6 +190,11 @@ class DeepSleepComponent final : public Component {
void schedule_sleep_();
bool should_teardown_();
void defer_sleep_() {
this->next_enter_deep_sleep_ = true;
this->enable_loop();
}
#ifdef USE_BK72XX
bool pin_prevents_sleep_(WakeUpPinItem &pin_item) const;
bool get_real_pin_state_(InternalGPIOPin &pin) const { return (pin.digital_read() ^ pin.is_inverted()); }
@@ -100,7 +100,7 @@ bool DeepSleepComponent::prepare_to_sleep_() {
this->status_set_warning();
ESP_LOGW(TAG, "Waiting for wakeup pin state change");
}
this->next_enter_deep_sleep_ = true;
this->defer_sleep_();
return false;
}
return true;
+3 -2
View File
@@ -153,13 +153,14 @@ bool ES7210::configure_mic_gain_() {
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC2_GAIN_REG44, 0x0f, regv));
// Configure mic 3
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00));
// MIC3 uses the ADC3/4 and MIC3/4 clock domains (bits 2 and 4), not the MIC1/2 domains.
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x15, 0x00));
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x00));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC3_GAIN_REG45, 0x10, 0x10));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC3_GAIN_REG45, 0x0f, regv));
// Configure mic 4
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x0b, 0x00));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_CLOCK_OFF_REG01, 0x15, 0x00));
ES7210_ERROR_CHECK(this->write_byte(ES7210_MIC34_POWER_REG4C, 0x00));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC4_GAIN_REG46, 0x10, 0x10));
ES7210_ERROR_CHECK(this->es7210_update_reg_bit_(ES7210_MIC4_GAIN_REG46, 0x0f, regv));
+118
View File
@@ -189,6 +189,13 @@ PSRAM_XIP_VARIANTS = {
VARIANT_ESP32S31,
}
# Variants whose ROM exports a full-format vsnprintf but no vasprintf
# (esp32c6.rom.newlib-normal.ld). There, the newlib printf engine is only
# linked because esp_http_client calls vasprintf; see vasprintf_stubs.cpp.
# The other variants either export both (classic ESP32, nano-format only) or
# neither, so the engine is already in the image and the wrap saves nothing.
ROM_VSNPRINTF_WITHOUT_VASPRINTF_VARIANTS = {VARIANT_ESP32C6}
# NVS encryption (HMAC peripheral scheme) is only available on variants that
# expose the HMAC peripheral (SOC_HMAC_SUPPORTED in soc_caps.h). The original
# ESP32 and ESP32-C2 do not have it. New variants with an HMAC peripheral
@@ -1732,6 +1739,8 @@ CONF_DISABLE_USB_SERIAL_JTAG_SECONDARY = "disable_usb_serial_jtag_secondary"
CONF_DISABLE_DEV_NULL_VFS = "disable_dev_null_vfs"
CONF_DISABLE_MBEDTLS_PEER_CERT = "disable_mbedtls_peer_cert"
CONF_DISABLE_MBEDTLS_PKCS7 = "disable_mbedtls_pkcs7"
CONF_DISABLE_MBEDTLS_TLS_SERVER = "disable_mbedtls_tls_server"
CONF_DISABLE_MBEDTLS_TLS_EXTRAS = "disable_mbedtls_tls_extras"
CONF_DISABLE_REGI2C_IN_IRAM = "disable_regi2c_in_iram"
CONF_DISABLE_FATFS = "disable_fatfs"
CONF_ADC_ONESHOT_IN_IRAM = "adc_oneshot_in_iram"
@@ -1746,6 +1755,8 @@ KEY_VFS_TERMIOS_REQUIRED = "vfs_termios_required"
KEY_USB_SERIAL_JTAG_SECONDARY_REQUIRED = "usb_serial_jtag_secondary_required"
KEY_MBEDTLS_PEER_CERT_REQUIRED = "mbedtls_peer_cert_required"
KEY_MBEDTLS_PKCS7_REQUIRED = "mbedtls_pkcs7_required"
KEY_MBEDTLS_TLS_SERVER_REQUIRED = "mbedtls_tls_server_required"
KEY_MBEDTLS_TLS_EXTRAS_REQUIRED = "mbedtls_tls_extras_required"
KEY_FATFS_REQUIRED = "fatfs_required"
KEY_MBEDTLS_SHA512_REQUIRED = "mbedtls_sha512_required"
KEY_ADC_ONESHOT_IRAM_REQUIRED = "adc_oneshot_iram_required"
@@ -1830,6 +1841,30 @@ def require_mbedtls_pkcs7() -> None:
CORE.data[KEY_ESP32][KEY_MBEDTLS_PKCS7_REQUIRED] = True
def require_mbedtls_tls_server() -> None:
"""Mark that the mbedTLS server-side TLS/DTLS handshake is required.
Call this from components that accept TLS connections (OpenThread's DTLS
commissioner does). This prevents CONFIG_MBEDTLS_TLS_CLIENT_ONLY from
being selected.
"""
CORE.data[KEY_ESP32][KEY_MBEDTLS_TLS_SERVER_REQUIRED] = True
def require_mbedtls_tls_extras(options: Iterable[str] | None = None) -> None:
"""Mark TLS features disabled by ``disable_mbedtls_tls_extras`` as required.
``options`` names the entries of ``MBEDTLS_TLS_EXTRA_OPTIONS`` to keep;
omit it to keep all of them. Call this from components that need AES-CCM,
deterministic ECDSA signing, static RSA/ECDH key exchange, TLS
renegotiation or session tickets, or that run a TLS client against
servers ESPHome cannot vet (wpa_supplicant's EAP client). A user-supplied
sdkconfig_options value is never overridden either.
"""
required = CORE.data[KEY_ESP32].setdefault(KEY_MBEDTLS_TLS_EXTRAS_REQUIRED, set())
required.update(MBEDTLS_TLS_EXTRA_OPTIONS if options is None else options)
def require_mbedtls_sha512() -> None:
"""Mark that mbedTLS SHA-384/SHA-512 support is required by a component.
@@ -1987,6 +2022,8 @@ FRAMEWORK_SCHEMA = cv.Schema(
cv.Optional(CONF_DISABLE_DEV_NULL_VFS, default=True): cv.boolean,
cv.Optional(CONF_DISABLE_MBEDTLS_PEER_CERT, default=True): cv.boolean,
cv.Optional(CONF_DISABLE_MBEDTLS_PKCS7, default=True): cv.boolean,
cv.Optional(CONF_DISABLE_MBEDTLS_TLS_SERVER, default=True): cv.boolean,
cv.Optional(CONF_DISABLE_MBEDTLS_TLS_EXTRAS, default=True): cv.boolean,
cv.Optional(CONF_DISABLE_REGI2C_IN_IRAM, default=True): cv.boolean,
cv.Optional(CONF_ADC_ONESHOT_IN_IRAM, default=False): cv.boolean,
cv.Optional(CONF_DISABLE_FATFS, default=True): cv.boolean,
@@ -2302,6 +2339,69 @@ async def _reconcile_certificate_bundle_sdkconfig() -> None:
set_idf_sdkconfig_default("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN", True)
# TLS features an HTTPS/MQTT client talking to a modern server never
# negotiates. Static RSA and static ECDH key exchange have no forward secrecy
# and are gone in TLS 1.3, renegotiation is deprecated, esp-tls never enables
# session tickets, AES-CCM ciphersuites are not offered by web servers, and
# deterministic ECDSA only matters when signing with a private key. Together
# they cost ~10 KB of flash whenever TLS is linked (http_request, mqtt).
# wpa_supplicant's EAP client is a second TLS client that talks to RADIUS
# servers ESPHome cannot vet, and a failed EAP handshake leaves the device
# off the network, so the wifi component re-enables all of these when eap is
# configured.
# The EC public key parsing extras stay enabled: they decide whether a peer
# certificate with a compressed point or explicit curve parameters parses,
# which no component can know ahead of time.
MBEDTLS_TLS_EXTRA_OPTIONS = (
"CONFIG_MBEDTLS_KEY_EXCHANGE_RSA",
"CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA",
"CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA",
"CONFIG_MBEDTLS_SSL_RENEGOTIATION",
"CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS",
"CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS",
"CONFIG_MBEDTLS_CCM_C",
"CONFIG_MBEDTLS_ECDSA_DETERMINISTIC",
)
# Members of the mbedTLS "TLS Protocol Role" Kconfig choice. Setting one
# member is only valid when the user has not already chosen another.
MBEDTLS_TLS_ROLE_OPTIONS = (
"CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT",
"CONFIG_MBEDTLS_TLS_SERVER_ONLY",
"CONFIG_MBEDTLS_TLS_CLIENT_ONLY",
"CONFIG_MBEDTLS_TLS_DISABLED",
)
@coroutine_with_priority(CoroPriority.FINAL)
async def _reconcile_mbedtls_tls_sdkconfig(
disable_tls_server: bool, disable_tls_extras: bool
) -> None:
"""Trim mbedTLS to what a TLS client needs unless a component asked otherwise.
Runs at FINAL priority so every require_mbedtls_tls_server() and
require_mbedtls_tls_extras() call has happened. Only the server-side
handshake (~7 KB) is a separate option; nothing in ESPHome accepts TLS
connections, but OpenThread's DTLS commissioner does. A user-supplied
sdkconfig_options value always wins; for the TLS role choice, any member
the user set leaves the whole choice alone so the pair cannot conflict.
"""
data = CORE.data[KEY_ESP32]
sdkconfig = data[KEY_SDKCONFIG_OPTIONS]
if (
disable_tls_server
and not data.get(KEY_MBEDTLS_TLS_SERVER_REQUIRED, False)
and not any(option in sdkconfig for option in MBEDTLS_TLS_ROLE_OPTIONS)
):
add_idf_sdkconfig_option("CONFIG_MBEDTLS_TLS_CLIENT_ONLY", True)
add_idf_sdkconfig_option("CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT", False)
if disable_tls_extras:
required = data.get(KEY_MBEDTLS_TLS_EXTRAS_REQUIRED, set())
for option in MBEDTLS_TLS_EXTRA_OPTIONS:
if option not in required:
set_idf_sdkconfig_default(option, False)
@coroutine_with_priority(CoroPriority.FINAL)
async def _reconcile_network_sdkconfig() -> None:
"""Reconcile WiFi/Ethernet/Bluetooth/coexistence sdkconfig flags.
@@ -2566,6 +2666,17 @@ async def to_code(config):
else:
for symbol in ("vprintf", "printf", "fprintf", "vfprintf"):
cg.add_build_flag(f"-Wl,--wrap={symbol}")
# esp_http_client calls vasprintf, which on the ESP32-C6 is the only
# reference to newlib's full printf engine (~20 KB: _svfprintf_r,
# _dtoa_r and their helpers); every other caller resolves to the
# ROM. See vasprintf_stubs.cpp. The --undefined flag is needed
# because libsrc.a is scanned before the IDF libraries that
# reference the symbol, so the stub would otherwise never be pulled
# from the archive.
if variant in ROM_VSNPRINTF_WITHOUT_VASPRINTF_VARIANTS:
cg.add_define("USE_ESP32_VASPRINTF_STUB")
cg.add_build_flag("-Wl,--wrap=vasprintf")
cg.add_build_flag("-Wl,--undefined=__wrap_vasprintf")
else:
cg.add_build_flag("-DUSE_ARDUINO")
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
@@ -2991,6 +3102,13 @@ async def to_code(config):
# FINAL priority: runs after every require_certificate_bundle() call
CORE.add_job(_reconcile_certificate_bundle_sdkconfig)
# FINAL priority: runs after every require_mbedtls_tls_*() call
CORE.add_job(
_reconcile_mbedtls_tls_sdkconfig,
advanced[CONF_DISABLE_MBEDTLS_TLS_SERVER],
advanced[CONF_DISABLE_MBEDTLS_TLS_EXTRAS],
)
# FINAL: require_*() calls can come from to_code at or below this priority, so an
# inline read would be iteration-order-dependent; reconcile once after every job ran.
CORE.add_job(
+2 -9
View File
@@ -3,18 +3,11 @@ import esphome.codegen as cg
# Re-exported for the many esp32-side users; defined in esphome.const
# and esphome.espidf so the upload/logs fast path can use them without
# importing this package.
from esphome.const import ( # noqa: F401 # pylint: disable=unused-import
KEY_ESP32,
KEY_FLASH_SIZE,
KEY_IDF_VERSION,
KEY_VARIANT,
)
from esphome.const import KEY_ESP32, KEY_FLASH_SIZE, KEY_IDF_VERSION, KEY_VARIANT # noqa: F401 # pylint: disable=unused-import
# Back compat for external components only; in-tree callers import it
# from esphome.espidf directly.
from esphome.espidf import ( # noqa: F401 # pylint: disable=unused-import
variant_to_idf_target,
)
from esphome.espidf import variant_to_idf_target # noqa: F401 # pylint: disable=unused-import
KEY_BOARD = "board"
KEY_SDKCONFIG_OPTIONS = "sdkconfig_options"
@@ -0,0 +1,53 @@
/*
* Linker wrap stub for vasprintf() on variants whose ROM exports a
* full-format vsnprintf() but no vasprintf() (ESP32-C6, newlib only).
*
* On those chips every snprintf/vsnprintf call in the image resolves to
* the ROM, so the newlib printf engine (_svfprintf_r, _dtoa_r and their
* helpers, ~20 KB) is not linked at all until something references a
* printf-family function the ROM lacks. esp_http_client does exactly that
* through vasprintf() in its header and auth helpers, so adding
* http_request to a build costs the whole engine on top of the HTTP and
* TLS code itself.
*
* This stub reimplements vasprintf() on top of the ROM vsnprintf(), which
* keeps the engine out of the image. It is only compiled in when codegen
* defines USE_ESP32_VASPRINTF_STUB, which is gated on the variant's ROM
* linker script and on the same newlib condition as printf_stubs.cpp.
*/
#include "esphome/core/defines.h"
#if defined(USE_ESP_IDF) && defined(USE_ESP32_VASPRINTF_STUB)
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
namespace esphome::esp32 {}
// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
extern "C" {
int __wrap_vasprintf(char **strp, const char *fmt, va_list ap) {
va_list ap_copy;
va_copy(ap_copy, ap);
int len = vsnprintf(nullptr, 0, fmt, ap_copy);
va_end(ap_copy);
if (len < 0) {
return len;
}
// vasprintf's contract is a malloc'd buffer the caller releases with free()
char *buf = static_cast<char *>(malloc(static_cast<size_t>(len) + 1)); // NOLINT(cppcoreguidelines-no-malloc)
if (buf == nullptr) {
return -1;
}
vsnprintf(buf, static_cast<size_t>(len) + 1, fmt, ap);
*strp = buf;
return len;
}
} // extern "C"
// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
#endif // USE_ESP_IDF && USE_ESP32_VASPRINTF_STUB
+13 -22
View File
@@ -83,23 +83,18 @@ void ESP32BLE::setup() {
}
}
// Queue the transition for loop(). A pending transition the other way is
// cancelled instead, since nothing was torn down or brought up yet; any other
// state is already there or on its way.
void ESP32BLE::request_state_(bool enable) {
if (enable) {
if (this->state_ == BLE_COMPONENT_STATE_DISABLED) {
this->state_ = BLE_COMPONENT_STATE_ENABLE;
} else if (this->state_ == BLE_COMPONENT_STATE_DISABLE) {
this->state_ = BLE_COMPONENT_STATE_ACTIVE;
}
} else {
if (this->state_ == BLE_COMPONENT_STATE_ACTIVE) {
this->state_ = BLE_COMPONENT_STATE_DISABLE;
} else if (this->state_ == BLE_COMPONENT_STATE_ENABLE) {
this->state_ = BLE_COMPONENT_STATE_DISABLED;
}
}
void ESP32BLE::enable() {
if (this->state_ != BLE_COMPONENT_STATE_DISABLED)
return;
this->state_ = BLE_COMPONENT_STATE_ENABLE;
}
void ESP32BLE::disable() {
if (this->state_ == BLE_COMPONENT_STATE_DISABLED)
return;
this->state_ = BLE_COMPONENT_STATE_DISABLE;
}
#ifdef USE_ESP32_BLE_ADVERTISING
@@ -585,11 +580,7 @@ void ESP32BLE::loop_handle_state_transition_not_active_() {
this->mark_failed();
return;
}
this->drain_ble_events_();
// A status callback may have asked for BLE back; the stack is down now, so
// that request becomes a bring-up.
this->state_ =
this->state_ == BLE_COMPONENT_STATE_ACTIVE ? BLE_COMPONENT_STATE_ENABLE : BLE_COMPONENT_STATE_DISABLED;
this->state_ = BLE_COMPONENT_STATE_DISABLED;
} else if (this->state_ == BLE_COMPONENT_STATE_ENABLE) {
ESP_LOGD(TAG, "Enabling");
this->state_ = BLE_COMPONENT_STATE_OFF;
+2 -11
View File
@@ -102,8 +102,8 @@ class ESP32BLE final : public Component {
}
uint32_t get_advertising_cycle_time() const { return this->advertising_cycle_time_; }
void enable() { this->request_state_(true); }
void disable() { this->request_state_(false); }
void enable();
void disable();
ESPHOME_ALWAYS_INLINE bool is_active() { return this->state_ == BLE_COMPONENT_STATE_ACTIVE; }
void setup() override;
void loop() override;
@@ -176,15 +176,6 @@ class ESP32BLE final : public Component {
bool ble_setup_();
bool ble_dismantle_();
void request_state_(bool enable);
// Drop what the old stack queued; the next stack reuses the same interface ids.
void drain_ble_events_() {
BLEEvent *ble_event;
while ((ble_event = this->ble_events_.pop()) != nullptr) {
this->ble_event_pool_.release(ble_event);
}
this->ble_events_.get_and_reset_dropped_count();
}
bool ble_pre_setup_();
#ifdef USE_ESP32_BLE_ADVERTISING
void advertising_init_();
@@ -42,7 +42,7 @@ void BLEClientBase::set_state(espbt::ClientState st) {
void BLEClientBase::loop() {
if (!esp32_ble::global_ble->is_active()) {
// ble_before_disabled_event_handler() resets the client.
this->set_state(espbt::ClientState::INIT);
return;
}
if (this->state() == espbt::ClientState::INIT) {
@@ -72,21 +72,6 @@ void BLEClientBase::loop() {
float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
void BLEClientBase::ble_before_disabled_event_handler() {
auto st = this->state();
if (st != espbt::ClientState::IDLE && st != espbt::ClientState::INIT) {
// No CLOSE_EVT will come: free the services and settle the link.
this->release_services();
this->set_idle_();
this->on_disconnect_complete(ESP_GATT_CONN_TERMINATE_LOCAL_HOST);
}
// The interface belongs to the torn-down stack.
this->gattc_if_ = ESP_GATT_IF_NONE;
this->set_state(espbt::ClientState::INIT);
// An idle client runs no loop; the INIT branch must run to register again.
this->enable_loop();
}
void BLEClientBase::dump_config() {
ESP_LOGCONFIG(TAG,
" Address: %s\n"
@@ -108,10 +93,6 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
return false;
if (this->state() != espbt::ClientState::IDLE)
return false;
// Not registered on this stack yet; promoting now would stop the scan for a
// connect that connect() rejects anyway.
if (this->gattc_if_ == ESP_GATT_IF_NONE)
return false;
this->log_event_("Found device");
if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG)
@@ -136,15 +117,6 @@ void BLEClientBase::connect() {
this->connection_index_, this->address_str_);
return;
}
if (this->gattc_if_ == ESP_GATT_IF_NONE) {
// Bluedroid drops an open on an unknown interface without any event.
this->log_warning_("Connect rejected, GATT app not registered");
// INIT stays so loop() still registers; only a promoted client goes back.
if (this->state() == espbt::ClientState::DISCOVERED) {
this->set_state(espbt::ClientState::IDLE);
}
return;
}
ESP_LOGI(TAG, "[%d] [%s] 0x%02x Connecting", this->connection_index_, this->address_str_, this->remote_addr_type_);
this->paired_ = false;
// A registration whose event never arrived must not block this connection's release.
@@ -227,10 +199,7 @@ void BLEClientBase::release_services() {
#ifndef CONFIG_BT_GATTC_CACHE_NVS_FLASH
// Only the cache clean makes the stack's database unsafe to walk.
this->services_released_ = true;
// A stack on its way down frees its own cache.
if (esp32_ble::global_ble->is_active()) {
esp_ble_gattc_cache_clean(this->remote_bda_);
}
esp_ble_gattc_cache_clean(this->remote_bda_);
#endif
}
@@ -41,7 +41,6 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
void connect() override;
esp_err_t pair();
void disconnect() override;
void ble_before_disabled_event_handler() override;
void unconditional_disconnect();
void release_services();
@@ -115,7 +114,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
#endif
// Group 3: 4-byte types
int gattc_if_{ESP_GATT_IF_NONE};
int gattc_if_;
esp_gatt_status_t status_{ESP_GATT_OK};
// Group 4: Arrays
@@ -140,7 +139,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
uint8_t pending_notify_regs_{0};
bool auto_connect_{false};
bool paired_{false};
// Set by release_services() on RAM-cache builds; the stack's GATT database must not be walked after it
// Set only when release_services() cleans the stack's GATT cache, which no API may then walk
bool services_released_{false};
// 8 bytes used, no padding
@@ -156,11 +155,10 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
void log_connection_params_(const char *param_type);
void handle_connection_result_(esp_err_t ret);
/// Hook called once a connection has been fully torn down (after release_services() and
/// set_idle_()): CLOSE_EVT, the DISCONNECTING safety timeout, or the BLE stack going down.
/// set_idle_()), from both the CLOSE_EVT handler and the DISCONNECTING safety timeout.
/// Subclasses with extra per-connection accounting (e.g. bluetooth_proxy slot state)
/// override this to release that state. `reason` is the controller reason code,
/// ESP_GATT_CONN_TIMEOUT for the safety timeout, or ESP_GATT_CONN_TERMINATE_LOCAL_HOST
/// for the stack going down.
/// override this to release that state. `reason` is the controller reason code, or
/// ESP_GATT_CONN_TIMEOUT for the safety-timeout path.
virtual void on_disconnect_complete(esp_err_t reason) {}
/// Transition to IDLE and reset conn_id — call when the connection is fully dead.
void set_idle_() {
@@ -3,6 +3,7 @@ import encodings
from esphome import automation
import esphome.codegen as cg
from esphome.components import esp32_ble
from esphome.components.const import CONF_MANUFACTURER
from esphome.components.esp32 import request_bluetooth
from esphome.components.esp32_ble import BTLoggers, bt_uuid
import esphome.config_validation as cv
@@ -41,7 +42,6 @@ CONF_DESCRIPTORS = "descriptors"
CONF_ENDIANNESS = "endianness"
CONF_FIRMWARE_VERSION = "firmware_version"
CONF_INDICATE = "indicate"
CONF_MANUFACTURER = "manufacturer"
CONF_MANUFACTURER_DATA = "manufacturer_data"
CONF_MAX_CLIENTS = "max_clients"
CONF_ON_WRITE = "on_write"
@@ -62,11 +62,6 @@ void ESP32BLETracker::on_ota_global_state(ota::OTAState state, float progress, u
for (auto *client : this->clients_) {
client->disconnect();
}
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
// The OTA transfer blocks the main loop, so the revert in loop() cannot run. No
// active-connection gate here: every client was just told to disconnect.
this->update_coex_preference_(false);
#endif
#endif
} else if ((state == ota::OTA_ERROR || state == ota::OTA_ABORT) && this->scan_continuous_before_ota_) {
this->scan_continuous_before_ota_ = false;
@@ -79,11 +74,11 @@ void ESP32BLETracker::on_ota_global_state(ota::OTAState state, float progress, u
void ESP32BLETracker::loop() {
if (!this->parent_->is_active()) {
this->ble_was_disabled_ = true;
return;
}
if (this->ble_was_disabled_) {
} else if (this->ble_was_disabled_) {
this->ble_was_disabled_ = false;
// First start after boot or after the stack came back.
// If the BLE stack was disabled, we need to start the scan again.
if (this->scan_continuous_) {
this->start_scan();
}
@@ -223,27 +218,7 @@ void ESP32BLETracker::stop_scan() {
this->stop_scan_();
}
void ESP32BLETracker::ble_before_disabled_event_handler() {
// Tell the controller to stop; a scan still starting has nothing to stop yet.
if (this->scanner_state_ == ScannerState::RUNNING || this->scanner_state_ == ScannerState::FAILED) {
this->stop_scan_();
}
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
for (auto *client : this->clients_) {
client->ble_before_disabled_event_handler();
}
this->skip_next_scan_end_ = false;
#endif
// The stop above never completes (stack torn down, events dropped); settle
// here so start_scan_() sees IDLE once the stack is back.
if (this->scanner_state_ != ScannerState::IDLE) {
this->cleanup_scan_state_(true);
}
// A failure latched by the old stack must not be handled against the next.
this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS;
this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
this->ble_was_disabled_ = true;
}
void ESP32BLETracker::ble_before_disabled_event_handler() { this->stop_scan_(); }
bool ESP32BLETracker::stop_scan_() {
if (this->scanner_state_ != ScannerState::RUNNING && this->scanner_state_ != ScannerState::FAILED) {
@@ -113,9 +113,6 @@ class ESPBTClient : public ESPBTDeviceListener {
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
virtual void connect() = 0;
virtual void disconnect() = 0;
/// Called right before the BLE stack is dismantled. Nothing in flight will
/// complete, and the GATT app must register again once the stack is back.
virtual void ble_before_disabled_event_handler() {}
bool disconnect_pending() const { return this->want_disconnect_; }
void cancel_pending_disconnect() { this->want_disconnect_ = false; }
@@ -842,14 +842,7 @@ bool ESPHomeOTAComponent::handle_auth_send_() {
const size_t hex_size = hasher.get_size() * 2;
const size_t nonce_len = hasher.get_size() / 4;
const size_t auth_buf_size = 1 + 3 * hex_size;
// Internal RAM first: 128 of these bytes go straight into the hardware SHA engine
this->auth_buf_ =
RAMAllocator<uint8_t>(RAMAllocator<uint8_t>::PREFER_INTERNAL).make_unique_array_for_overwrite(auth_buf_size);
if (!this->auth_buf_) {
this->log_auth_warning_(LOG_STR("No memory"));
this->send_error_and_cleanup_(ota::OTA_RESPONSE_ERROR_UNKNOWN);
return false;
}
this->auth_buf_ = std::make_unique<uint8_t[]>(auth_buf_size);
this->auth_buf_pos_ = 0;
char *buf = reinterpret_cast<char *>(this->auth_buf_.get() + 1);
+2 -2
View File
@@ -145,13 +145,13 @@ class ESPHomeOTAComponent final : public ota::OTAComponent {
#ifdef USE_OTA_PASSWORD
std::string password_;
RAMUniquePtr<uint8_t[]> auth_buf_;
std::unique_ptr<uint8_t[]> auth_buf_;
#endif // USE_OTA_PASSWORD
#ifdef USE_OTA_ENCRYPTION
#ifndef USE_OTA_ENCRYPTION_FROM_API
noise::NoiseContext noise_ctx_;
#endif
RAMUniquePtr<NoiseSession> noise_;
std::unique_ptr<NoiseSession> noise_;
#endif // USE_OTA_ENCRYPTION
socket::ListenSocket *server_{nullptr};
@@ -7,6 +7,7 @@
#include "esphome/core/log.h"
#include <cstring>
#include <new>
#ifdef USE_ESP8266
#include <pgmspace.h>
@@ -42,8 +43,9 @@ ESPHomeOTAComponent::NoiseSession::~NoiseSession() {
bool ESPHomeOTAComponent::noise_start_session_(uint8_t server_feature_flags) {
// A provisioned key cleared between the offer and here is not guarded: the
// session runs on the zero key load_psk fills in and fails the client's MAC.
// Default placement, PSRAM first where present: the session only lives for one upload
this->noise_ = RAMAllocator<NoiseSession>().make_unique();
// Default-init: the frame buffer is written before it is read
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
this->noise_ = std::unique_ptr<NoiseSession>(new (std::nothrow) NoiseSession);
static constexpr size_t PROLOGUE_ACK_LEN = 2; // OTA_RESPONSE_OK + version
static constexpr size_t PROLOGUE_CLIENT_FEATURES_LEN = 1;
static constexpr size_t PROLOGUE_FEATURE_ACK_LEN = 2; // OTA_RESPONSE_FEATURE_FLAGS + server flags
@@ -6,21 +6,17 @@
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <cstring>
#include <new>
namespace esphome::ethernet {
namespace {
// Context returned by init() and handed back to read/write/deinit. There is one W5500 per device, so a
// single static instance replaces a heap allocation that could fail. It is always clear when init() runs:
// esp_eth_mac_new_w5500() calls deinit() on every failure after init() succeeded, and nothing else
// uninstalls the driver
// Per-device context returned by init() and handed back to read/write/deinit.
struct W5500CustomSpiContext {
spi_device_handle_t handle;
SemaphoreHandle_t lock;
};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - intentional mutable state
W5500CustomSpiContext w5500_context{};
// Transfers up to the ESP32 SPI hardware FIFO size (64 bytes) stay on the polling path; larger
// transfers (the frame payloads) use the blocking, DMA-backed transmit.
@@ -29,20 +25,23 @@ constexpr uint32_t W5500_SPI_LOCK_TIMEOUT_MS = 50;
void *w5500_custom_spi_init(const void *spi_config) {
const auto *config = static_cast<const eth_w5500_config_t *>(spi_config);
auto *ctx = &w5500_context;
auto *ctx = new (std::nothrow) W5500CustomSpiContext{};
if (ctx == nullptr) {
return nullptr;
}
// The W5500 SPI frame carries the 16-bit address in the command phase and the 8-bit control
// byte in the address phase; mirror what the stock driver configures.
spi_device_interface_config_t devcfg = *config->spi_devcfg;
devcfg.command_bits = 16;
devcfg.address_bits = 8;
if (spi_bus_add_device(config->spi_host_id, &devcfg, &ctx->handle) != ESP_OK) {
ctx->handle = nullptr;
delete ctx;
return nullptr;
}
ctx->lock = xSemaphoreCreateMutex();
if (ctx->lock == nullptr) {
spi_bus_remove_device(ctx->handle);
ctx->handle = nullptr;
delete ctx;
return nullptr;
}
return ctx;
@@ -52,7 +51,7 @@ esp_err_t w5500_custom_spi_deinit(void *spi_ctx) {
auto *ctx = static_cast<W5500CustomSpiContext *>(spi_ctx);
spi_bus_remove_device(ctx->handle);
vSemaphoreDelete(ctx->lock);
*ctx = {};
delete ctx;
return ESP_OK;
}
@@ -53,13 +53,6 @@ void I2SAudioSpeakerBase::dump_config() {
void I2SAudioSpeakerBase::loop() {
uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
// A stop that arrives while stopped cancels any start that has not been processed yet
constexpr uint32_t stop_bits = SpeakerEventGroupBits::COMMAND_STOP | SpeakerEventGroupBits::COMMAND_STOP_GRACEFULLY;
if ((event_group_bits & stop_bits) && (this->state_ == speaker::STATE_STOPPED)) {
xEventGroupClearBits(this->event_group_, stop_bits | SpeakerEventGroupBits::COMMAND_START);
event_group_bits &= ~(stop_bits | SpeakerEventGroupBits::COMMAND_START);
}
if ((event_group_bits & SpeakerEventGroupBits::COMMAND_START) && (this->state_ == speaker::STATE_STOPPED)) {
this->state_ = speaker::STATE_STARTING;
xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::COMMAND_START);
@@ -246,6 +239,8 @@ void I2SAudioSpeakerBase::start() {
if ((this->state_ == speaker::STATE_STARTING) || (this->state_ == speaker::STATE_RUNNING))
return;
// Mark STARTING immediately to avoid transient STOPPED observations before loop() processes COMMAND_START.
this->state_ = speaker::STATE_STARTING;
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::COMMAND_START);
}
@@ -254,10 +249,11 @@ void I2SAudioSpeakerBase::stop() { this->stop_(false); }
void I2SAudioSpeakerBase::finish() { this->stop_(true); }
void I2SAudioSpeakerBase::stop_(bool wait_on_empty) {
if (!this->is_ready() || this->is_failed())
if (this->is_failed())
return;
if (this->state_ == speaker::STATE_STOPPED)
return;
// Always set the bit, even when stopped, so loop() can cancel a start that is still pending
if (wait_on_empty) {
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::COMMAND_STOP_GRACEFULLY);
} else {
-5
View File
@@ -59,11 +59,6 @@ void Infrared::setup() {
// Set up traits based on configuration
this->traits_.set_supports_transmitter(this->has_transmitter());
this->traits_.set_supports_receiver(this->has_receiver());
// Register as listener for received IR data
if (this->receiver_ != nullptr) {
this->receiver_->register_listener(this);
}
}
void Infrared::dump_config() {
+2 -1
View File
@@ -119,7 +119,8 @@ class Infrared : public Component, public EntityBase, public remote_base::Remote
void dump_config() override;
float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; }
/// Set the remote receiver component
/// Set the remote receiver component; the listener registration happens from codegen, see
/// remote_base.attach_receiver
void set_receiver(remote_base::RemoteReceiverBase *receiver) { this->receiver_ = receiver; }
/// Set the remote transmitter component
void set_transmitter(remote_base::RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; }
+7 -3
View File
@@ -3,7 +3,12 @@
from typing import Any
import esphome.codegen as cg
from esphome.components import infrared, remote_receiver, remote_transmitter
from esphome.components import (
infrared,
remote_base,
remote_receiver,
remote_transmitter,
)
from esphome.components.const import CONF_RECEIVER_FREQUENCY
import esphome.config_validation as cv
from esphome.const import CONF_CARRIER_DUTY_PERCENT, CONF_FREQUENCY
@@ -82,8 +87,7 @@ async def to_code(config: dict[str, Any]) -> None:
# Link receiver if specified
if CONF_REMOTE_RECEIVER_ID in config:
receiver = await cg.get_variable(config[CONF_REMOTE_RECEIVER_ID])
cg.add(var.set_receiver(receiver))
await remote_base.attach_receiver(var, config, CONF_REMOTE_RECEIVER_ID)
# Set receiver demodulation frequency if specified (metadata only, no hardware effect)
if CONF_RECEIVER_FREQUENCY in config:
@@ -97,10 +97,6 @@ void RfProxy::setup() {
// remote_transmitter/receiver always uses OOK (on-off keying)
this->traits_.add_supported_modulation(radio_frequency::RadioFrequencyModulation::RADIO_FREQUENCY_MODULATION_OOK);
if (this->receiver_ != nullptr) {
this->receiver_->register_listener(this);
}
}
void RfProxy::dump_config() {
+2 -1
View File
@@ -56,7 +56,8 @@ class RfProxy final : public radio_frequency::RadioFrequency {
/// Set the remote transmitter component
void set_transmitter(remote_base::RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; }
/// Set the remote receiver component
/// Set the remote receiver component; the listener registration happens from codegen, see
/// remote_base.attach_receiver
void set_receiver(remote_base::RemoteReceiverBase *receiver) { this->receiver_ = receiver; }
/// Set the fixed carrier frequency in Hz (metadata: advertised via traits, does not tune hardware)
@@ -1,7 +1,12 @@
"""Radio Frequency platform implementation using remote_base (remote_transmitter/receiver)."""
import esphome.codegen as cg
from esphome.components import radio_frequency, remote_receiver, remote_transmitter
from esphome.components import (
radio_frequency,
remote_base,
remote_receiver,
remote_transmitter,
)
import esphome.config_validation as cv
from esphome.const import CONF_CARRIER_DUTY_PERCENT, CONF_FREQUENCY
import esphome.final_validate as fv
@@ -66,5 +71,4 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_transmitter(transmitter))
if CONF_REMOTE_RECEIVER_ID in config:
receiver = await cg.get_variable(config[CONF_REMOTE_RECEIVER_ID])
cg.add(var.set_receiver(receiver))
await remote_base.attach_receiver(var, config, CONF_REMOTE_RECEIVER_ID)
+11 -2
View File
@@ -3,6 +3,7 @@
#include "esphome/components/esp32/crash_handler.h"
#include <esp_log.h>
#include <esp_idf_version.h>
#include <driver/uart.h>
#include <soc/soc_caps.h>
@@ -16,8 +17,10 @@
#include <driver/usb_serial_jtag_vfs.h>
#endif
#endif
#include "esp_idf_version.h"
#if defined(CONFIG_PM_ENABLE) && defined(CONFIG_FREERTOS_USE_TICKLESS_IDLE) && \
(ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0))
#include "esp_sleep.h"
#endif
#include "freertos/FreeRTOS.h"
#include <fcntl.h>
@@ -87,6 +90,12 @@ void init_uart(uart_port_t uart_num, uint32_t baud_rate, int tx_buffer_size) {
// ESP-IDF requires rx_buffer_size > UART_HW_FIFO_LEN (128 bytes).
const int min_rx_buffer_size = UART_HW_FIFO_LEN(uart_num) + 1;
uart_driver_install(uart_num, min_rx_buffer_size, tx_buffer_size, 0, nullptr, 0);
#if defined(CONFIG_PM_ENABLE) && defined(CONFIG_FREERTOS_USE_TICKLESS_IDLE) && \
(ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0))
// Always flush before going to light sleep. Could be disabled for devices
// without TOP_PD or if source_clk = UART_SCLK_RTC
esp_sleep_set_console_uart_handling_mode(ESP_SLEEP_ALWAYS_FLUSH_UART);
#endif
}
void Logger::pre_setup() {
+1 -2
View File
@@ -14,7 +14,7 @@ from esphome.const import (
CONF_TIMEOUT,
)
from esphome.core import Lambda
from esphome.cpp_generator import TemplateArguments, get_variable
from esphome.cpp_generator import StaticCastExpression, TemplateArguments, get_variable
from esphome.cpp_types import nullptr
from .defines import (
@@ -30,7 +30,6 @@ from .defines import (
CONF_SHOW_SNOW,
CONF_TOP_LAYER,
PARTS,
StaticCastExpression,
add_warning,
get_focused_widgets,
get_options,
+1 -42
View File
@@ -10,12 +10,7 @@ from typing import Any
from esphome import codegen as cg, config_validation as cv
from esphome.const import CONF_ITEMS
from esphome.core import CORE, ID, Lambda
from esphome.cpp_generator import (
CallExpression,
LambdaExpression,
MockObj,
MockObjClass,
)
from esphome.cpp_generator import MockObj, StaticCastExpression, call_lambda
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import Expression, SafeExpType
@@ -157,17 +152,6 @@ def get_refreshed_widgets() -> set:
return _get_data(KEY_REFRESHED_WIDGETS, set())
class StaticCastExpression(Expression):
__slots__ = ("type", "exp")
def __init__(self, type: Any, exp: SafeExpType):
self.type = str(type)
self.exp = cg.safe_exp(exp)
def __str__(self):
return f"static_cast<{self.type}>({self.exp})"
def add_define(macro: str, value="1"):
lv_defines = get_defines()
value = str(value)
@@ -192,31 +176,6 @@ def addr(arg) -> MockObj:
return MockObj(f"&{arg}")
def call_lambda(lamb: LambdaExpression) -> Expression:
"""
Given a lambda, either reduce to a simple expression or call it, possibly with parameters
from the surrounding context
:param lamb:
:return:
"""
expr = lamb.content.strip()
if expr.startswith("return") and expr.endswith(";"):
# Convert a lambda returning a simple expression to just that expression
expr = cg.RawExpression(expr[6:-1].strip())
# Don't cast if the return type is a class
if isinstance(lamb.return_type, MockObjClass):
return expr
return StaticCastExpression(lamb.return_type, expr)
# If lambda has parameters, call it with their names
# Parameter names come from hardcoded component code (like "x", "it", "event")
# not from user input, so they're safe to use directly
if lamb.parameters and lamb.parameters.parameters:
return CallExpression(
lamb, *[MockObj(x.id) for x in lamb.parameters.parameters]
)
return CallExpression(lamb)
class LValidator:
"""
A validator for a particular type used in LVGL. Usable in configs as a validator, also
+1 -3
View File
@@ -16,7 +16,7 @@ from esphome.const import (
CONF_VALUE,
)
from esphome.core import CORE, ID, Lambda
from esphome.cpp_generator import MockObj
from esphome.cpp_generator import MockObj, StaticCastExpression, call_lambda
from esphome.cpp_types import ESPTime, int32, uint32
from esphome.helpers import cpp_string_escape
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
@@ -33,9 +33,7 @@ from .defines import (
LV_FONTS,
LValidator,
LvConstant,
StaticCastExpression,
add_lv_use,
call_lambda,
get_esphome_fonts_used,
get_lv_fonts_used,
get_lv_images_used,
+1 -2
View File
@@ -16,7 +16,7 @@ from esphome.const import (
)
from esphome.core import ID, EsphomeError, TimePeriod
from esphome.coroutine import FakeAwaitable
from esphome.cpp_generator import MockObj
from esphome.cpp_generator import MockObj, call_lambda
from esphome.schema_extractors import EnableSchemaExtraction
from esphome.types import Expression
@@ -42,7 +42,6 @@ from ..defines import (
STATES,
LValidator,
add_lv_use,
call_lambda,
get_styles_used,
get_theme_widget_map,
get_widget_map,
@@ -227,7 +227,6 @@ LIST_ID_SCHEMA = cv.Schema({cv.Required(CONF_ID): cv.use_id(lv_list_t)})
)
async def list_add_text_to_code(config, action_id, template_arg, args):
widgets = await get_widgets(config)
await _wait_list_triggers_completed()
async def do_add_text(w: Widget):
text = await lv_text.process(config[CONF_TEXT])
@@ -371,7 +370,6 @@ async def list_add_to_code(config, action_id, template_arg, args):
_register_lv_uses(w_type_name, w_conf)
_register_dynamic_widget_style_uses(w_conf)
widgets = await get_widgets(config)
await _wait_list_triggers_completed()
async def do_add(w: Widget):
index = None
@@ -505,7 +503,6 @@ LIST_REMOVE_SCHEMA = LIST_ID_SCHEMA.extend(
)
async def list_remove_to_code(config, action_id, template_arg, args):
widgets = await get_widgets(config)
await _wait_list_triggers_completed()
async def do_remove(w: Widget):
index = await lv_int.process(config[CONF_INDEX])
@@ -539,7 +536,6 @@ async def list_remove_to_code(config, action_id, template_arg, args):
)
async def list_clear_to_code(config, action_id, template_arg, args):
widgets = await get_widgets(config)
await _wait_list_triggers_completed()
async def do_clear(w: Widget):
await _wait_list_triggers_completed()
+2 -1
View File
@@ -1,6 +1,6 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import climate, remote_transmitter, sensor, uart
from esphome.components import climate, remote_base, remote_transmitter, sensor, uart
from esphome.components.climate import ClimateMode, ClimatePreset, ClimateSwingMode
from esphome.components.remote_base import CONF_TRANSMITTER_ID
import esphome.config_validation as cv
@@ -280,6 +280,7 @@ async def to_code(config):
cg.add(var.set_response_timeout(config[CONF_TIMEOUT].total_milliseconds))
cg.add(var.set_request_attempts(config[CONF_NUM_ATTEMPTS]))
if CONF_TRANSMITTER_ID in config:
remote_base.request_protocol("midea") # ir_transmitter.h uses it from C++
cg.add_define("USE_REMOTE_TRANSMITTER")
transmitter_ = await cg.get_variable(config[CONF_TRANSMITTER_ID])
cg.add(var.set_transmitter(transmitter_))
+5 -1
View File
@@ -1,5 +1,5 @@
import esphome.codegen as cg
from esphome.components import climate_ir
from esphome.components import climate_ir, remote_base
import esphome.config_validation as cv
from esphome.const import CONF_USE_FAHRENHEIT
from esphome.types import ConfigType
@@ -19,5 +19,9 @@ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(MideaIR).extend(
async def to_code(config: ConfigType) -> None:
# midea_ir uses MideaProtocol from C++ and auto-loads coolix, whose coolix.cpp uses
# CoolixProtocol even when no coolix climate is configured
remote_base.request_protocol("midea")
remote_base.request_protocol("coolix")
var = await climate_ir.new_climate_ir(config)
cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT]))
+2 -2
View File
@@ -35,8 +35,8 @@ void MipiDsi::setup() {
.bus_id = 0, // index from 0, specify the DSI host to use
.num_data_lanes =
this->lanes_, // Number of data lanes to use, can't set a value that exceeds the chip's capability
.phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, // Clock source for the DPHY
.lane_bit_rate_mbps = this->lane_bit_rate_, // Bit rate of the data lanes, in Mbps
// phy_clk_src left at 0 to enable runtime auto-select.
.lane_bit_rate_mbps = this->lane_bit_rate_, // Bit rate of the data lanes, in Mbps
};
auto err = esp_lcd_new_dsi_bus(&bus_config, &this->bus_handle_);
if (err != ESP_OK) {
+139 -46
View File
@@ -26,44 +26,129 @@ static const uint8_t MLX90614_ID4 = 0x3F;
static const char *const TAG = "mlx90614";
// The EEPROM cell has a limited number of write cycles, so stop retrying after a few failures
static constexpr uint8_t EMISSIVITY_WRITE_ATTEMPTS = 3;
// SMBus packet error code: CRC-8 with polynomial 0x07, MSB first
static uint8_t crc8_pec(const uint8_t *data, uint8_t len) { return crc8(data, len, 0x00, 0x07, true); }
void MLX90614Component::setup() {
if (!this->write_emissivity_()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
this->mark_failed();
if (std::isnan(this->emissivity_)) {
return;
}
this->emissivity_write_attempts_ = EMISSIVITY_WRITE_ATTEMPTS;
this->try_write_emissivity_();
if (this->emissivity_write_attempts_ != 0) {
this->status_set_warning(LOG_STR("Failed to write emissivity, will retry"));
}
}
void MLX90614Component::try_write_emissivity_() {
if (this->emissivity_write_attempts_ == 0) {
return;
}
if (this->write_emissivity_()) {
this->emissivity_write_attempts_ = 0;
return;
}
if (--this->emissivity_write_attempts_ == 0) {
ESP_LOGE(TAG, "Giving up on writing emissivity after %u attempts", EMISSIVITY_WRITE_ATTEMPTS);
this->emissivity_write_failed_ = true;
}
}
bool MLX90614Component::write_emissivity_() {
if (std::isnan(this->emissivity_))
// Skip the write when the EEPROM already holds the desired value to save write cycles
uint16_t current_emissivity;
if (this->read_register_(MLX90614_EMISSIVITY, current_emissivity) != i2c::ERROR_OK) {
return false;
}
const auto desired_emissivity = static_cast<uint16_t>(this->emissivity_ * 0xFFFF);
if (current_emissivity == desired_emissivity) {
return true;
uint16_t value = (uint16_t) (this->emissivity_ * 65535);
if (!this->write_bytes_(MLX90614_EMISSIVITY, 0)) {
return false;
}
delay(10);
if (!this->write_bytes_(MLX90614_EMISSIVITY, value)) {
return false;
}
delay(10);
return true;
return this->write_register_(MLX90614_EMISSIVITY, desired_emissivity);
}
bool MLX90614Component::write_bytes_(uint8_t reg, uint16_t data) {
bool MLX90614Component::write_register_(uint8_t reg, uint16_t data) {
// The PEC covers the whole write transaction: SLA+W, command, data low, data high
uint8_t buf[5];
buf[0] = this->address_ << 1;
buf[1] = reg;
buf[2] = data & 0xFF;
buf[3] = data >> 8;
buf[4] = crc8(buf, 4, 0x00, 0x07, true);
return this->write_bytes(reg, buf + 2, 3);
// See datasheet 8.3.3.1 EEPROM write sequence
// 1. Write 0x0000 into the cell of interest (erases the cell)
buf[2] = buf[3] = 0;
buf[4] = crc8_pec(buf, 4);
auto ec = this->write_register(reg, buf + 2, 3);
if (ec != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Can't erase register 0x%02X, error %d", reg, ec);
return false;
}
// 2. Wait at least 5ms
delay(10);
// 3. Write the new value
if (data != 0) {
buf[2] = data & 0xFF;
buf[3] = data >> 8;
buf[4] = crc8_pec(buf, 4);
ec = this->write_register(reg, buf + 2, 3);
if (ec != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Can't write register 0x%02X, error %d", reg, ec);
return false;
}
// 4. Wait at least 5ms
delay(10);
}
// 5. Read back to confirm the value was stored
uint16_t read_back;
ec = this->read_register_(reg, read_back);
if (ec != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Can't check register 0x%02X value, error %d", reg, ec);
return false;
}
if (read_back != data) {
ESP_LOGW(TAG, "Read back mismatch on register 0x%02X. Expected 0x%04X, got 0x%04X", reg, data, read_back);
return false;
}
return true;
}
i2c::ErrorCode MLX90614Component::read_register_(uint8_t reg, uint16_t &data) {
// The PEC covers the whole read transaction: SLA+W, command, SLA+R, data low, data high
uint8_t buf[6];
buf[0] = this->address_ << 1;
buf[1] = reg;
buf[2] = (this->address_ << 1) | 0x01;
const auto ec = this->read_register(reg, buf + 3, 3);
if (ec != i2c::ERROR_OK) {
ESP_LOGW(TAG, "i2c read error %d", ec);
return ec;
}
const auto expected_pec = crc8_pec(buf, 5);
if (buf[5] != expected_pec) {
ESP_LOGW(TAG, "i2c CRC error. Expected 0x%02X, got 0x%02X", expected_pec, buf[5]);
return i2c::ERROR_CRC;
}
data = encode_uint16(buf[4], buf[3]);
return i2c::ERROR_OK;
}
void MLX90614Component::dump_config() {
ESP_LOGCONFIG(TAG, "MLX90614:");
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
if (this->emissivity_write_attempts_ != 0) {
ESP_LOGW(TAG, " Emissivity not written yet, will retry");
}
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Ambient", this->ambient_sensor_);
@@ -71,33 +156,41 @@ void MLX90614Component::dump_config() {
}
void MLX90614Component::update() {
uint8_t emissivity[3];
if (this->read_register(MLX90614_EMISSIVITY, emissivity, 3) != i2c::ERROR_OK) {
this->status_set_warning();
return;
// Temperature reads run regardless of the emissivity state so a failure still shows up as NAN
this->try_write_emissivity_();
// Publishes NAN on a bus or CRC failure so a stuck reading is visible instead of silently stale
auto publish_sensor = [this](sensor::Sensor *sensor, uint8_t reg) {
if (sensor == nullptr) {
return i2c::ERROR_OK;
}
uint16_t raw;
const auto ec = this->read_register_(reg, raw);
if (ec != i2c::ERROR_OK) {
sensor->publish_state(NAN);
return ec;
}
// Bit 15 set means the device flagged the reading as invalid
const float temperature = (raw & 0x8000) ? NAN : raw * 0.02f - 273.15f;
ESP_LOGD(TAG, "'%s': Got temperature=%.1f°C", sensor->get_name().c_str(), temperature);
sensor->publish_state(temperature);
return ec;
};
const auto object_ec = publish_sensor(this->object_sensor_, MLX90614_TEMPERATURE_OBJECT_1);
const auto ambient_ec = publish_sensor(this->ambient_sensor_, MLX90614_TEMPERATURE_AMBIENT);
if (object_ec != i2c::ERROR_OK || ambient_ec != i2c::ERROR_OK) {
this->status_set_warning(LOG_STR("Failed to read some sensors"));
} else if (this->emissivity_write_failed_) {
this->status_set_warning(LOG_STR("Failed to write emissivity"));
} else if (this->emissivity_write_attempts_ != 0) {
this->status_set_warning(LOG_STR("Failed to write emissivity, will retry"));
} else {
this->status_clear_warning();
}
uint8_t raw_object[3];
if (this->read_register(MLX90614_TEMPERATURE_OBJECT_1, raw_object, 3) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
uint8_t raw_ambient[3];
if (this->read_register(MLX90614_TEMPERATURE_AMBIENT, raw_ambient, 3) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
float ambient = raw_ambient[1] & 0x80 ? NAN : encode_uint16(raw_ambient[1], raw_ambient[0]) * 0.02f - 273.15f;
float object = raw_object[1] & 0x80 ? NAN : encode_uint16(raw_object[1], raw_object[0]) * 0.02f - 273.15f;
ESP_LOGD(TAG, "Got Temperature=%.1f°C Ambient=%.1f°C", object, ambient);
if (this->ambient_sensor_ != nullptr && !std::isnan(ambient))
this->ambient_sensor_->publish_state(ambient);
if (this->object_sensor_ != nullptr && !std::isnan(object))
this->object_sensor_->publish_state(object);
this->status_clear_warning();
}
} // namespace esphome::mlx90614
+6 -1
View File
@@ -18,13 +18,18 @@ class MLX90614Component final : public PollingComponent, public i2c::I2CDevice {
void set_emissivity(float emissivity) { emissivity_ = emissivity; }
protected:
void try_write_emissivity_();
bool write_emissivity_();
bool write_bytes_(uint8_t reg, uint16_t data);
bool write_register_(uint8_t reg, uint16_t data);
i2c::ErrorCode read_register_(uint8_t reg, uint16_t &data);
sensor::Sensor *ambient_sensor_{nullptr};
sensor::Sensor *object_sensor_{nullptr};
float emissivity_{NAN};
// Remaining attempts to program the emissivity EEPROM cell, bounded to limit cell wear
uint8_t emissivity_write_attempts_{0};
bool emissivity_write_failed_{false};
};
} // namespace esphome::mlx90614
+1 -6
View File
@@ -14,12 +14,7 @@ from esphome.const import (
)
from esphome.core import CORE, TimePeriod
from . import ( # noqa: F401 pylint: disable=unused-import
FILTER_SOURCE_FILES,
Nextion,
nextion_ns,
nextion_ref,
)
from . import FILTER_SOURCE_FILES, Nextion, nextion_ns, nextion_ref # noqa: F401 pylint: disable=unused-import
from .base_component import (
CONF_AUTO_WAKE_ON_TOUCH,
CONF_COMMAND_SPACING,
+78 -74
View File
@@ -13,11 +13,6 @@ namespace esphome::nextion {
static const char *const TAG = "nextion";
// A user entity may be named sleep_wake too; only the internal NO_RESULT command clears the sleeping flag
static bool is_sleep_wake_command(const NextionComponentBase *component) {
return component->get_queue_type() == NextionQueueType::NO_RESULT && component->get_variable_name() == "sleep_wake";
}
// Nextion command terminator: three consecutive 0xFF bytes (per Nextion Instruction Set v1.1).
static constexpr uint8_t COMMAND_DELIMITER[3] = {0xFF, 0xFF, 0xFF};
static constexpr size_t DELIMITER_SIZE = sizeof(COMMAND_DELIMITER);
@@ -168,17 +163,6 @@ bool Nextion::check_connect_() {
#endif // USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
}
// NO_RESULT components are owned by their entry; every other component is a user entity. Entry and
// component storage comes from RAMAllocator, so delete is not valid for either.
void Nextion::release_queue_entry_(NextionQueue *nb) {
if (nb->component != nullptr && nb->component->get_queue_type() == NextionQueueType::NO_RESULT) {
nb->component->~NextionComponentBase();
RAMAllocator<NextionComponentBase>().deallocate(nb->component, 1);
}
nb->~NextionQueue();
RAMAllocator<NextionQueue>().deallocate(nb, 1);
}
void Nextion::reset_(bool reset_nextion) {
uint8_t d;
@@ -186,12 +170,15 @@ void Nextion::reset_(bool reset_nextion) {
this->read_byte(&d);
}
for (auto *entry : this->nextion_queue_) {
this->release_queue_entry_(entry);
if (entry->component != nullptr && entry->component->get_queue_type() == NextionQueueType::NO_RESULT) {
delete entry->component; // NOLINT(cppcoreguidelines-owning-memory)
}
delete entry; // NOLINT(cppcoreguidelines-owning-memory)
}
this->nextion_queue_.clear();
#ifdef USE_NEXTION_WAVEFORM
for (auto *entry : this->waveform_queue_) {
this->release_queue_entry_(entry);
delete entry; // NOLINT(cppcoreguidelines-owning-memory)
}
this->waveform_queue_.clear();
#endif // USE_NEXTION_WAVEFORM
@@ -434,9 +421,6 @@ bool Nextion::remove_from_q_(bool report_empty) {
NextionQueue *nb = this->nextion_queue_.front();
if (!nb || !nb->component) {
ESP_LOGE(TAG, "Invalid queue");
if (nb != nullptr) {
this->release_queue_entry_(nb);
}
this->nextion_queue_.pop_front();
return false;
}
@@ -444,10 +428,13 @@ bool Nextion::remove_from_q_(bool report_empty) {
ESP_LOGN(TAG, "Removed: %s", component->get_variable_name().c_str());
if (is_sleep_wake_command(component)) {
this->is_sleeping_ = false;
if (component->get_queue_type() == NextionQueueType::NO_RESULT) {
if (component->get_variable_name() == "sleep_wake") {
this->is_sleeping_ = false;
}
delete component; // NOLINT(cppcoreguidelines-owning-memory)
}
this->release_queue_entry_(nb);
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
this->nextion_queue_.pop_front();
return true;
}
@@ -557,7 +544,7 @@ void Nextion::process_nextion_commands_() {
ESP_LOGW(TAG, "Invalid waveform ID %d/ch %d", component->get_component_id(),
component->get_wave_channel_id());
ESP_LOGN(TAG, "Remove waveform ID %d/ch %d", component->get_component_id(), component->get_wave_channel_id());
this->release_queue_entry_(nb);
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
this->waveform_queue_.pop();
}
#else // USE_NEXTION_WAVEFORM
@@ -660,9 +647,6 @@ void Nextion::process_nextion_commands_() {
NextionQueue *nb = this->nextion_queue_.front();
if (!nb || !nb->component) {
ESP_LOGE(TAG, "Invalid queue entry");
if (nb != nullptr) {
this->release_queue_entry_(nb);
}
this->nextion_queue_.pop_front();
return;
}
@@ -676,7 +660,7 @@ void Nextion::process_nextion_commands_() {
component->set_state_from_string(to_process, true, false);
}
this->release_queue_entry_(nb);
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
this->nextion_queue_.pop_front();
break;
@@ -703,9 +687,6 @@ void Nextion::process_nextion_commands_() {
NextionQueue *nb = this->nextion_queue_.front();
if (!nb || !nb->component) {
ESP_LOGE(TAG, "Invalid queue");
if (nb != nullptr) {
this->release_queue_entry_(nb);
}
this->nextion_queue_.pop_front();
return;
}
@@ -722,7 +703,7 @@ void Nextion::process_nextion_commands_() {
component->set_state_from_int(value, true, false);
}
this->release_queue_entry_(nb);
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
this->nextion_queue_.pop_front();
break;
@@ -909,7 +890,7 @@ void Nextion::process_nextion_commands_() {
ESP_LOGN(TAG, "Send waveform: component id %d, waveform id %d, size %zu", component->get_component_id(),
component->get_wave_channel_id(), buffer_to_send);
component->clear_wave_buffer(buffer_to_send);
this->release_queue_entry_(nb);
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
this->waveform_queue_.pop();
#else // USE_NEXTION_WAVEFORM
ESP_LOGW(TAG, "Waveform transmit ready but waveform not enabled");
@@ -939,10 +920,14 @@ void Nextion::purge_stale_queue_entries_() {
ESP_LOGV(TAG, "Remove old queue '%s':'%s'", component->get_queue_type_string(),
component->get_variable_name().c_str());
if (is_sleep_wake_command(component)) {
this->is_sleeping_ = false;
if (component->get_queue_type() == NextionQueueType::NO_RESULT) {
if (component->get_variable_name() == "sleep_wake") {
this->is_sleeping_ = false;
}
delete component; // NOLINT(cppcoreguidelines-owning-memory)
}
this->release_queue_entry_(*it);
delete *it; // NOLINT(cppcoreguidelines-owning-memory)
it = this->nextion_queue_.erase(it);
} else {
@@ -1094,34 +1079,6 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool
return response.length();
}
// Allocates a queue entry owning a bare NO_RESULT component; nullptr when the queue is full or memory is out
NextionQueue *Nextion::make_no_result_entry_(const std::string &variable_name) {
#ifdef USE_NEXTION_MAX_QUEUE_SIZE
if (this->max_queue_size_ > 0 && this->nextion_queue_.size() >= this->max_queue_size_) {
ESP_LOGW(TAG, "Queue full (%zu), drop: %s", this->nextion_queue_.size(), variable_name.c_str());
return nullptr;
}
#endif
auto *nextion_queue = RAMAllocator<nextion::NextionQueue>().allocate(1);
if (nextion_queue == nullptr) {
ESP_LOGW(TAG, "Queue alloc failed");
return nullptr;
}
new (nextion_queue) nextion::NextionQueue;
nextion_queue->component = RAMAllocator<nextion::NextionComponentBase>().allocate(1);
if (nextion_queue->component == nullptr) {
ESP_LOGW(TAG, "Component alloc failed");
this->release_queue_entry_(nextion_queue);
return nullptr;
}
new (nextion_queue->component) nextion::NextionComponentBase;
nextion_queue->component->set_variable_name(variable_name);
nextion_queue->queue_time = App.get_loop_component_start_time();
return nextion_queue;
}
/**
* @brief Add a command to the Nextion queue that expects no response.
*
@@ -1133,11 +1090,36 @@ NextionQueue *Nextion::make_no_result_entry_(const std::string &variable_name) {
* @param variable_name Name of the variable or component associated with the command.
*/
void Nextion::add_no_result_to_queue_(const std::string &variable_name) {
auto *nextion_queue = this->make_no_result_entry_(variable_name);
if (nextion_queue == nullptr)
#ifdef USE_NEXTION_MAX_QUEUE_SIZE
if (this->max_queue_size_ > 0 && this->nextion_queue_.size() >= this->max_queue_size_) {
ESP_LOGW(TAG, "Queue full (%zu), drop: %s", this->nextion_queue_.size(), variable_name.c_str());
return;
}
#endif
RAMAllocator<nextion::NextionQueue> allocator;
nextion::NextionQueue *nextion_queue = allocator.allocate(1);
if (nextion_queue == nullptr) {
ESP_LOGW(TAG, "Queue alloc failed");
return;
}
new (nextion_queue) nextion::NextionQueue();
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
nextion_queue->component = new (std::nothrow) nextion::NextionComponentBase;
if (nextion_queue->component == nullptr) {
ESP_LOGW(TAG, "Component alloc failed");
nextion_queue->~NextionQueue();
allocator.deallocate(nextion_queue, 1);
return;
}
nextion_queue->component->set_variable_name(variable_name);
nextion_queue->queue_time = App.get_loop_component_start_time();
this->nextion_queue_.push_back(nextion_queue);
ESP_LOGN(TAG, "Queue NORESULT: %s", variable_name.c_str());
ESP_LOGN(TAG, "Queue NORESULT: %s", nextion_queue->component->get_variable_name().c_str());
}
/**
@@ -1171,10 +1153,32 @@ void Nextion::add_no_result_to_queue_with_command_(const std::string &variable_n
#ifdef USE_NEXTION_COMMAND_SPACING
void Nextion::add_no_result_to_queue_with_pending_command_(const std::string &variable_name,
const std::string &command) {
auto *nextion_queue = this->make_no_result_entry_(variable_name);
if (nextion_queue == nullptr)
#ifdef USE_NEXTION_MAX_QUEUE_SIZE
if (this->max_queue_size_ > 0 && this->nextion_queue_.size() >= this->max_queue_size_) {
ESP_LOGW(TAG, "Queue full (%zu), drop: %s", this->nextion_queue_.size(), variable_name.c_str());
return;
}
#endif
RAMAllocator<nextion::NextionQueue> allocator;
nextion::NextionQueue *nextion_queue = allocator.allocate(1);
if (nextion_queue == nullptr) {
ESP_LOGW(TAG, "Queue alloc failed");
return;
}
new (nextion_queue) nextion::NextionQueue();
nextion_queue->component = new (std::nothrow) nextion::NextionComponentBase;
if (nextion_queue->component == nullptr) {
ESP_LOGW(TAG, "Component alloc failed");
nextion_queue->~NextionQueue();
allocator.deallocate(nextion_queue, 1);
return;
}
nextion_queue->component->set_variable_name(variable_name);
nextion_queue->queue_time = App.get_loop_component_start_time();
nextion_queue->pending_command = command; // Store command for retry
this->nextion_queue_.push_back(nextion_queue);
ESP_LOGVV(TAG, "Queue with pending command: %s", variable_name.c_str());
}
@@ -1308,7 +1312,7 @@ void Nextion::add_to_get_queue(NextionComponentBase *component) {
ESP_LOGW(TAG, "Queue alloc failed");
return;
}
new (nextion_queue) nextion::NextionQueue;
new (nextion_queue) nextion::NextionQueue();
nextion_queue->component = component;
nextion_queue->queue_time = App.get_loop_component_start_time();
@@ -1330,7 +1334,7 @@ void Nextion::add_to_get_queue(NextionComponentBase *component) {
if (this->send_command_(command)) {
this->nextion_queue_.push_back(nextion_queue);
} else {
this->release_queue_entry_(nextion_queue);
delete nextion_queue; // NOLINT(cppcoreguidelines-owning-memory)
}
#endif // USE_NEXTION_COMMAND_SPACING
}
@@ -1351,14 +1355,14 @@ void Nextion::add_addt_command_to_queue(NextionComponentBase *component) {
ESP_LOGW(TAG, "Queue alloc failed");
return;
}
new (nextion_queue) nextion::NextionQueue;
new (nextion_queue) nextion::NextionQueue();
nextion_queue->component = component;
nextion_queue->queue_time = App.get_loop_component_start_time();
if (!this->waveform_queue_.push(nextion_queue)) {
ESP_LOGW(TAG, "Waveform queue full, drop");
this->release_queue_entry_(nextion_queue);
delete nextion_queue; // NOLINT(cppcoreguidelines-owning-memory)
return;
}
if (this->waveform_queue_.size() == 1)
-2
View File
@@ -1469,8 +1469,6 @@ class Nextion final : public NextionBase, public PollingComponent, public uart::
void all_components_send_state_(bool force_update = false);
uint32_t comok_sent_ = 0;
bool remove_from_q_(bool report_empty = true);
void release_queue_entry_(NextionQueue *nb);
NextionQueue *make_no_result_entry_(const std::string &variable_name);
/**
* @brief Status flags for Nextion display state management
@@ -23,7 +23,8 @@ class NextionComponentBase;
class NextionQueue {
public:
NextionComponentBase *component{nullptr};
virtual ~NextionQueue() = default;
NextionComponentBase *component;
uint32_t queue_time = 0;
// Store command for retry if spacing blocked it
@@ -104,6 +105,6 @@ class NextionComponentBase {
int wave_max_length_ = 255;
#endif // USE_NEXTION_WAVEFORM
bool needs_to_send_update_{false};
bool needs_to_send_update_;
};
} // namespace esphome::nextion
+2 -2
View File
@@ -88,12 +88,12 @@ def encryption_schema(config: ConfigType | None) -> ConfigType:
async def to_code(config: ConfigType) -> None:
cg.add_define("USE_NOISE")
cg.add_library("esphome/noise-c", "0.1.30")
cg.add_library("esphome/noise-c", "0.1.26")
# noise-c depends on libsodium, but declaring it here too lets the
# library manager see the full set up front instead of discovering
# libsodium only after noise-c has downloaded, so the two can download
# in parallel. The version must match noise-c's library.json.
cg.add_library("esphome/libsodium", "1.10021.11")
cg.add_library("esphome/libsodium", "1.10021.8")
# Enable optimized memzero/memcmp in libsodium instead of volatile byte loops
cg.add_build_flag("-DHAVE_WEAK_SYMBOLS=1")
cg.add_build_flag("-DHAVE_INLINE_ASM=1")
+10
View File
@@ -13,6 +13,8 @@ from esphome.components.esp32 import (
get_esp32_variant,
include_builtin_idf_component,
only_on_variant,
require_mbedtls_tls_extras,
require_mbedtls_tls_server,
require_vfs_select,
)
from esphome.components.mdns import MDNSComponent, enable_mdns_storage
@@ -109,6 +111,14 @@ def set_sdkconfig_options(config: ConfigType) -> None:
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_ENABLED", True)
# OpenThread's DTLS commissioner is a TLS server, and its crypto platform
# uses AES-CCM and deterministic ECDSA directly. Keep the esp32 component
# from trimming them out of mbedTLS.
require_mbedtls_tls_server()
require_mbedtls_tls_extras(
("CONFIG_MBEDTLS_CCM_C", "CONFIG_MBEDTLS_ECDSA_DETERMINISTIC")
)
if not config.get(CONF_TLV):
if pan_id := config.get(CONF_PAN_ID):
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PANID", pan_id)
@@ -9,6 +9,7 @@
#include <array>
#include <cstring>
#include <memory>
#include <new>
#include <esp_image_format.h>
#include <esp_partition.h>
#include <esp_rom_crc.h>
@@ -234,11 +235,9 @@ bool IDFOTABackend::verify_signed_image_(const esp_partition_t *incoming) {
// runs mid-OTA on the loop task, on top of the caller's live 1 KB OTA buffer
// and mbedtls's own ~1 KB verify scratch, so keeping it off the stack widens
// a thin margin. One short-lived allocation right before reboot is not the
// fragmentation pattern the project guards against. An OOM returns nullptr
// and fails closed like every other error path. Internal RAM first: the
// block is an esp_partition_read target.
auto block =
RAMAllocator<uint8_t>(RAMAllocator<uint8_t>::PREFER_INTERNAL).make_unique_array_for_overwrite(SIG_BLOCK_SIZE);
// fragmentation pattern the project guards against. nothrow so an OOM here
// fails closed like every other error path, rather than aborting.
std::unique_ptr<uint8_t[]> block(new (std::nothrow) uint8_t[SIG_BLOCK_SIZE]);
if (!block) {
OTA_IDF_SIG_LOG(ESP_LOGE, "out of memory");
return false;
+102 -7
View File
@@ -1,6 +1,11 @@
from collections.abc import Callable
from pathlib import Path
from typing import Any
from esphome import automation
import esphome.codegen as cg
from esphome.components import binary_sensor
from esphome.config_helpers import filter_source_files_from_defines
import esphome.config_validation as cv
from esphome.const import (
CONF_ADDRESS,
@@ -40,11 +45,14 @@ from esphome.const import (
CONF_ZERO,
)
from esphome.core import ID, coroutine
from esphome.cpp_generator import MockObj
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import ConfigType
from esphome.util import Registry, SimpleRegistry
AUTO_LOAD = ["binary_sensor"]
CONF_RECEIVER_ID = "receiver_id"
CONF_TRANSMITTER_ID = "transmitter_id"
CONF_FIRST = "first"
@@ -90,9 +98,42 @@ REMOTE_TRANSMITTABLE_SCHEMA = cv.Schema(
)
async def register_listener(var, config):
# Listener and dumper lists are StaticVectors sized from these counts, so every registration
# must go through add_listener / add_dumper. Every receiver's list gets the same capacity, so
# the slots are keyed by receiver and the define is the largest count any one receiver needs.
LISTENER_COUNT_DEFINE = "REMOTE_BASE_LISTENER_COUNT"
DUMPER_COUNT_DEFINE = "REMOTE_BASE_DUMPER_COUNT"
_request_listener_slot = cg.slot_counter(LISTENER_COUNT_DEFINE)
_request_dumper_slot = cg.slot_counter(DUMPER_COUNT_DEFINE)
def add_listener(receiver: MockObj, listener: MockObj) -> None:
_request_listener_slot(str(receiver))
cg.add(receiver.register_listener(listener))
def add_dumper(receiver: MockObj, dumper: MockObj) -> None:
_request_dumper_slot(str(receiver))
cg.add(receiver.register_dumper(dumper))
async def register_listener(var: MockObj, config: ConfigType) -> None:
receiver = await cg.get_variable(config[CONF_RECEIVER_ID])
cg.add(receiver.register_listener(var))
add_listener(receiver, var)
async def attach_receiver(
var: MockObj, config: ConfigType, key: str = CONF_RECEIVER_ID
) -> None:
"""Link the configured receiver to an entity and register the entity as its listener.
The C++ set_receiver() no longer registers the listener; the slot for it is counted here.
"""
receiver = await cg.get_variable(config[key])
cg.add(var.set_receiver(receiver))
add_listener(receiver, var)
async def register_transmittable(var, config):
@@ -100,8 +141,53 @@ async def register_transmittable(var, config):
cg.add(var.set_transmitter(transmitter_))
def register_binary_sensor(name, type, schema):
return BINARY_SENSOR_REGISTRY.register(name, type, schema)
# Registry names that share a protocol source file
def _protocol_stem(name: str) -> str:
if name.startswith("rc_switch"):
return "rc_switch"
if name == "canalsatld":
return "canalsat"
return name
def protocol_define(name: str) -> str:
return f"USE_REMOTE_PROTOCOL_{_protocol_stem(name).upper()}"
_PROTOCOL_STEMS = sorted(
path.name.removesuffix("_protocol.cpp")
for path in Path(__file__).parent.glob("*_protocol.cpp")
)
def request_protocol(name: str) -> None:
"""Keep a protocol's source file in the build; components using it from C++ must call this."""
if _protocol_stem(name) not in _PROTOCOL_STEMS:
raise ValueError(
f"Unknown remote protocol {name!r}; expected one of {', '.join(_PROTOCOL_STEMS)}"
)
cg.add_define(protocol_define(name))
# Only the protocol sources a configuration uses are compiled
FILTER_SOURCE_FILES = filter_source_files_from_defines(
{f"{stem}_protocol.cpp": protocol_define(stem) for stem in _PROTOCOL_STEMS}
)
def register_binary_sensor(
name: str, type: MockObj, schema: cv.Schema | dict
) -> Callable[[Callable[[MockObj, ConfigType], Any]], Callable]:
registerer = BINARY_SENSOR_REGISTRY.register(name, type, schema)
def decorator(func: Callable[[MockObj, ConfigType], Any]) -> Callable:
async def new_func(var: MockObj, config: ConfigType) -> None:
request_protocol(name)
await coroutine(func)(var, config)
return registerer(new_func)
return decorator
def register_trigger(name, type, data_type):
@@ -114,6 +200,7 @@ def register_trigger(name, type, data_type):
def decorator(func):
async def new_func(config):
request_protocol(name)
var = cg.new_Pvariable(config[CONF_TRIGGER_ID])
await coroutine(func)(var, config)
await automation.build_automation(var, [(data_type, "x")], config)
@@ -131,6 +218,7 @@ def register_dumper(name, type, schema=None):
def decorator(func):
async def new_func(config, dumper_id):
request_protocol(name)
var = cg.new_Pvariable(dumper_id)
await coroutine(func)(var, config)
return var
@@ -171,6 +259,7 @@ def register_action(name, type_, schema):
def decorator(func):
async def new_func(config, action_id, template_arg, args):
request_protocol(name)
var = cg.new_Pvariable(action_id, template_arg)
await register_transmittable(var, config)
if CONF_REPEAT in config:
@@ -213,7 +302,13 @@ DUMPER_REGISTRY = Registry()
def validate_dumpers(value):
if isinstance(value, str) and value.lower() == "all":
return validate_dumpers(list(DUMPER_REGISTRY.keys()))
return cv.validate_registry("dumper", DUMPER_REGISTRY)(value)
entries = cv.validate_registry("dumper", DUMPER_REGISTRY)(value)
# a dumper listed twice would register twice; the receiver holds one secondary dumper
return list(
{
next(k for k in entry if k in DUMPER_REGISTRY): entry for entry in entries
}.values()
)
def validate_triggers(base_schema):
@@ -1439,7 +1534,7 @@ def validate_rc_switch_raw_code(value):
def build_rc_switch_protocol(config):
if isinstance(config, int):
return rc_switch_protocols[config]
return rc_switch_protocol(config)
pl = config[CONF_PULSE_LENGTH]
return RCSwitchBase(
config[CONF_SYNC][0] * pl,
@@ -1526,7 +1621,7 @@ RC_SWITCH_TRANSMITTER = cv.Schema(
}
)
rc_switch_protocols = ns.RC_SWITCH_PROTOCOLS
rc_switch_protocol = ns.rc_switch_protocol
RCSwitchData = ns.struct("RCSwitchData")
RCSwitchBase = ns.class_("RCSwitchBase")
RCSwitchTrigger = ns.class_("RCSwitchTrigger", RemoteReceiverTrigger)
@@ -191,9 +191,9 @@ class ABBWelcomeData {
class ABBWelcomeProtocol : public RemoteProtocol<ABBWelcomeData> {
public:
void encode(RemoteTransmitData *dst, const ABBWelcomeData &src) override;
optional<ABBWelcomeData> decode(RemoteReceiveData src) override;
void dump(const ABBWelcomeData &data) override;
void encode(RemoteTransmitData *dst, const ABBWelcomeData &src);
optional<ABBWelcomeData> decode(RemoteReceiveData src);
void dump(const ABBWelcomeData &data);
protected:
void encode_byte_(RemoteTransmitData *dst, uint8_t data) const;
@@ -15,9 +15,9 @@ struct AEHAData {
class AEHAProtocol : public RemoteProtocol<AEHAData> {
public:
void encode(RemoteTransmitData *dst, const AEHAData &data) override;
optional<AEHAData> decode(RemoteReceiveData src) override;
void dump(const AEHAData &data) override;
void encode(RemoteTransmitData *dst, const AEHAData &data);
optional<AEHAData> decode(RemoteReceiveData src);
void dump(const AEHAData &data);
private:
std::string format_data_(const std::vector<uint8_t> &data);
@@ -16,9 +16,9 @@ struct Beo4Data {
class Beo4Protocol : public RemoteProtocol<Beo4Data> {
public:
void encode(RemoteTransmitData *dst, const Beo4Data &data) override;
optional<Beo4Data> decode(RemoteReceiveData src) override;
void dump(const Beo4Data &data) override;
void encode(RemoteTransmitData *dst, const Beo4Data &data);
optional<Beo4Data> decode(RemoteReceiveData src);
void dump(const Beo4Data &data);
};
DECLARE_REMOTE_PROTOCOL(Beo4)
@@ -13,9 +13,9 @@ struct BrennenstuhlData {
class BrennenstuhlProtocol : public RemoteProtocol<BrennenstuhlData> {
public:
void encode(RemoteTransmitData *dst, const BrennenstuhlData &data) override;
optional<BrennenstuhlData> decode(RemoteReceiveData src) override;
void dump(const BrennenstuhlData &data) override;
void encode(RemoteTransmitData *dst, const BrennenstuhlData &data);
optional<BrennenstuhlData> decode(RemoteReceiveData src);
void dump(const BrennenstuhlData &data);
};
DECLARE_REMOTE_PROTOCOL(Brennenstuhl)
@@ -21,9 +21,9 @@ struct ByronSXData {
class ByronSXProtocol : public RemoteProtocol<ByronSXData> {
public:
void encode(RemoteTransmitData *dst, const ByronSXData &data) override;
optional<ByronSXData> decode(RemoteReceiveData src) override;
void dump(const ByronSXData &data) override;
void encode(RemoteTransmitData *dst, const ByronSXData &data);
optional<ByronSXData> decode(RemoteReceiveData src);
void dump(const ByronSXData &data);
};
DECLARE_REMOTE_PROTOCOL(ByronSX)
@@ -19,9 +19,9 @@ struct CanalSatLDData : public CanalSatData {};
class CanalSatBaseProtocol : public RemoteProtocol<CanalSatData> {
public:
void encode(RemoteTransmitData *dst, const CanalSatData &data) override;
optional<CanalSatData> decode(RemoteReceiveData src) override;
void dump(const CanalSatData &data) override;
void encode(RemoteTransmitData *dst, const CanalSatData &data);
optional<CanalSatData> decode(RemoteReceiveData src);
void dump(const CanalSatData &data);
protected:
uint16_t frequency_;
@@ -21,9 +21,9 @@ struct CoolixData {
class CoolixProtocol : public RemoteProtocol<CoolixData> {
public:
void encode(RemoteTransmitData *dst, const CoolixData &data) override;
optional<CoolixData> decode(RemoteReceiveData data) override;
void dump(const CoolixData &data) override;
void encode(RemoteTransmitData *dst, const CoolixData &data);
optional<CoolixData> decode(RemoteReceiveData data);
void dump(const CoolixData &data);
};
DECLARE_REMOTE_PROTOCOL(Coolix)
@@ -13,9 +13,9 @@ struct DishData {
class DishProtocol : public RemoteProtocol<DishData> {
public:
void encode(RemoteTransmitData *dst, const DishData &data) override;
optional<DishData> decode(RemoteReceiveData src) override;
void dump(const DishData &data) override;
void encode(RemoteTransmitData *dst, const DishData &data);
optional<DishData> decode(RemoteReceiveData src);
void dump(const DishData &data);
};
DECLARE_REMOTE_PROTOCOL(Dish)
@@ -20,9 +20,9 @@ struct DooyaData {
class DooyaProtocol : public RemoteProtocol<DooyaData> {
public:
void encode(RemoteTransmitData *dst, const DooyaData &data) override;
optional<DooyaData> decode(RemoteReceiveData src) override;
void dump(const DooyaData &data) override;
void encode(RemoteTransmitData *dst, const DooyaData &data);
optional<DooyaData> decode(RemoteReceiveData src);
void dump(const DooyaData &data);
};
DECLARE_REMOTE_PROTOCOL(Dooya)
@@ -19,9 +19,9 @@ struct DraytonData {
class DraytonProtocol : public RemoteProtocol<DraytonData> {
public:
void encode(RemoteTransmitData *dst, const DraytonData &data) override;
optional<DraytonData> decode(RemoteReceiveData src) override;
void dump(const DraytonData &data) override;
void encode(RemoteTransmitData *dst, const DraytonData &data);
optional<DraytonData> decode(RemoteReceiveData src);
void dump(const DraytonData &data);
};
DECLARE_REMOTE_PROTOCOL(Drayton)
@@ -21,9 +21,9 @@ struct DysonData {
class DysonProtocol : public RemoteProtocol<DysonData> {
public:
void encode(RemoteTransmitData *dst, const DysonData &data) override;
optional<DysonData> decode(RemoteReceiveData src) override;
void dump(const DysonData &data) override;
void encode(RemoteTransmitData *dst, const DysonData &data);
optional<DysonData> decode(RemoteReceiveData src);
void dump(const DysonData &data);
};
DECLARE_REMOTE_PROTOCOL(Dyson)
@@ -31,9 +31,9 @@ class GoboxProtocol : public RemoteProtocol<GoboxData> {
void dump_timings_(const RawTimings &timings) const;
public:
void encode(RemoteTransmitData *dst, const GoboxData &data) override;
optional<GoboxData> decode(RemoteReceiveData src) override;
void dump(const GoboxData &data) override;
void encode(RemoteTransmitData *dst, const GoboxData &data);
optional<GoboxData> decode(RemoteReceiveData src);
void dump(const GoboxData &data);
};
DECLARE_REMOTE_PROTOCOL(Gobox)
@@ -13,9 +13,9 @@ struct HaierData {
class HaierProtocol : public RemoteProtocol<HaierData> {
public:
void encode(RemoteTransmitData *dst, const HaierData &data) override;
optional<HaierData> decode(RemoteReceiveData src) override;
void dump(const HaierData &data) override;
void encode(RemoteTransmitData *dst, const HaierData &data);
optional<HaierData> decode(RemoteReceiveData src);
void dump(const HaierData &data);
protected:
void encode_byte_(RemoteTransmitData *dst, uint8_t item);
@@ -14,9 +14,9 @@ struct JVCData {
class JVCProtocol : public RemoteProtocol<JVCData> {
public:
void encode(RemoteTransmitData *dst, const JVCData &data) override;
optional<JVCData> decode(RemoteReceiveData src) override;
void dump(const JVCData &data) override;
void encode(RemoteTransmitData *dst, const JVCData &data);
optional<JVCData> decode(RemoteReceiveData src);
void dump(const JVCData &data);
};
DECLARE_REMOTE_PROTOCOL(JVC)
@@ -24,9 +24,9 @@ struct KeeloqData {
class KeeloqProtocol : public RemoteProtocol<KeeloqData> {
public:
void encode(RemoteTransmitData *dst, const KeeloqData &data) override;
optional<KeeloqData> decode(RemoteReceiveData src) override;
void dump(const KeeloqData &data) override;
void encode(RemoteTransmitData *dst, const KeeloqData &data);
optional<KeeloqData> decode(RemoteReceiveData src);
void dump(const KeeloqData &data);
};
DECLARE_REMOTE_PROTOCOL(Keeloq)
+3 -3
View File
@@ -16,9 +16,9 @@ struct LGData {
class LGProtocol : public RemoteProtocol<LGData> {
public:
void encode(RemoteTransmitData *dst, const LGData &data) override;
optional<LGData> decode(RemoteReceiveData src) override;
void dump(const LGData &data) override;
void encode(RemoteTransmitData *dst, const LGData &data);
optional<LGData> decode(RemoteReceiveData src);
void dump(const LGData &data);
};
DECLARE_REMOTE_PROTOCOL(LG)
@@ -27,9 +27,9 @@ struct MagiQuestData {
class MagiQuestProtocol : public RemoteProtocol<MagiQuestData> {
public:
void encode(RemoteTransmitData *dst, const MagiQuestData &data) override;
optional<MagiQuestData> decode(RemoteReceiveData src) override;
void dump(const MagiQuestData &data) override;
void encode(RemoteTransmitData *dst, const MagiQuestData &data);
optional<MagiQuestData> decode(RemoteReceiveData src);
void dump(const MagiQuestData &data);
};
DECLARE_REMOTE_PROTOCOL(MagiQuest)
@@ -67,9 +67,9 @@ class MideaData {
class MideaProtocol : public RemoteProtocol<MideaData> {
public:
void encode(RemoteTransmitData *dst, const MideaData &src) override;
optional<MideaData> decode(RemoteReceiveData src) override;
void dump(const MideaData &data) override;
void encode(RemoteTransmitData *dst, const MideaData &src);
optional<MideaData> decode(RemoteReceiveData src);
void dump(const MideaData &data);
};
DECLARE_REMOTE_PROTOCOL(Midea)
@@ -13,9 +13,9 @@ struct MirageData {
class MirageProtocol : public RemoteProtocol<MirageData> {
public:
void encode(RemoteTransmitData *dst, const MirageData &data) override;
optional<MirageData> decode(RemoteReceiveData src) override;
void dump(const MirageData &data) override;
void encode(RemoteTransmitData *dst, const MirageData &data);
optional<MirageData> decode(RemoteReceiveData src);
void dump(const MirageData &data);
protected:
void encode_byte_(RemoteTransmitData *dst, uint8_t item);
@@ -14,9 +14,9 @@ struct NECData {
class NECProtocol : public RemoteProtocol<NECData> {
public:
void encode(RemoteTransmitData *dst, const NECData &data) override;
optional<NECData> decode(RemoteReceiveData src) override;
void dump(const NECData &data) override;
void encode(RemoteTransmitData *dst, const NECData &data);
optional<NECData> decode(RemoteReceiveData src);
void dump(const NECData &data);
};
DECLARE_REMOTE_PROTOCOL(NEC)
@@ -24,9 +24,9 @@ class NexaProtocol : public RemoteProtocol<NexaData> {
void zero(RemoteTransmitData *dst) const;
void sync(RemoteTransmitData *dst) const;
void encode(RemoteTransmitData *dst, const NexaData &data) override;
optional<NexaData> decode(RemoteReceiveData src) override;
void dump(const NexaData &data) override;
void encode(RemoteTransmitData *dst, const NexaData &data);
optional<NexaData> decode(RemoteReceiveData src);
void dump(const NexaData &data);
};
DECLARE_REMOTE_PROTOCOL(Nexa)

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