diff --git a/.github/scripts/auto-label-pr/index.js b/.github/scripts/auto-label-pr/index.js index 8b0c8215031..1d071b38662 100644 --- a/.github/scripts/auto-label-pr/index.js +++ b/.github/scripts/auto-label-pr/index.js @@ -53,7 +53,7 @@ module.exports = async ({ github, context }) => { }); const currentLabels = currentLabelsData.map(label => label.name); const managedLabels = currentLabels.filter(label => - label.startsWith('component: ') || MANAGED_LABELS.includes(label) + label.startsWith('component: ') || label.startsWith('platform: ') || MANAGED_LABELS.includes(label) ); // Check for mega-PR early - if present, skip most automatic labeling diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ebbe52d205..586263c6514 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -286,7 +286,7 @@ jobs: fi echo "version=$version" >> "$GITHUB_OUTPUT" - name: Run prek - uses: j178/prek-action@4e14d07f9231acabce116ccfca13b13dd9755ece # v3.0.0 + uses: j178/prek-action@1382ad72dea72f9d53832f2bcb65b3e87a947204 # v3.0.1 with: prek-version: ${{ steps.prek.outputs.version }} # This job only runs on pull requests, so nothing ever populates @@ -626,7 +626,7 @@ jobs: apt-get install -y libc6-dbg - name: Run CodSpeed benchmarks - uses: CodSpeedHQ/action@373d6868929f444bc08d901fd0eb0ad52a8875ea # v5.2.1 + uses: CodSpeedHQ/action@c4fd08a3a159bd0cc208da1e0edf32b8c47d75e5 # v5.4.0 with: run: | . venv/bin/activate @@ -893,14 +893,20 @@ jobs: matrix: include: - id: clang-tidy - name: Run script/clang-tidy for ESP32 IDF 1/3 - options: --environment esp32-idf-tidy --split-num 3 --split-at 1 + name: Run script/clang-tidy for ESP32 IDF 1/5 + options: --environment esp32-idf-tidy --split-num 5 --split-at 1 - id: clang-tidy - name: Run script/clang-tidy for ESP32 IDF 2/3 - options: --environment esp32-idf-tidy --split-num 3 --split-at 2 + name: Run script/clang-tidy for ESP32 IDF 2/5 + options: --environment esp32-idf-tidy --split-num 5 --split-at 2 - id: clang-tidy - name: Run script/clang-tidy for ESP32 IDF 3/3 - options: --environment esp32-idf-tidy --split-num 3 --split-at 3 + name: Run script/clang-tidy for ESP32 IDF 3/5 + options: --environment esp32-idf-tidy --split-num 5 --split-at 3 + - id: clang-tidy + name: Run script/clang-tidy for ESP32 IDF 4/5 + options: --environment esp32-idf-tidy --split-num 5 --split-at 4 + - id: clang-tidy + name: Run script/clang-tidy for ESP32 IDF 5/5 + options: --environment esp32-idf-tidy --split-num 5 --split-at 5 steps: - name: Check out code from GitHub @@ -1416,31 +1422,27 @@ jobs: echo "::warning::ci_memory_impact_extract.py not found on target branch, skipping memory impact analysis" fi - # Check if test files exist on the target branch for the requested - # components and platform. When a PR adds new test files for a platform, - # the target branch won't have them yet, so skip instead of failing. - # This check must be done here (not in determine-jobs.py) because - # determine-jobs runs on the PR branch and cannot see what the target - # branch has. + # The build below uses --base-only, so skip unless the target branch has + # test..yaml for every requested component (test-*.yaml + # variants alone compile nothing, and a partial set would compare a + # smaller target build against the PR build). Checked here, not in + # determine-jobs.py, because determine-jobs runs on the PR branch and + # cannot see the target branch. - name: Check for test files on target branch id: check-tests if: steps.check-script.outputs.skip != 'true' run: | components='${{ toJSON(fromJSON(needs.determine-jobs.outputs.memory_impact).components) }}' platform="${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}" - found=false + missing="" for component in $(echo "$components" | jq -r '.[]'); do - # Check for test files matching the platform (test.platform.yaml or test-*.platform.yaml) - for f in tests/components/${component}/test*.${platform}.yaml; do - if [ -f "$f" ]; then - found=true - break 2 - fi - done + if [ ! -f "tests/components/${component}/test.${platform}.yaml" ]; then + missing="${missing} ${component}" + fi done - if [ "$found" = false ]; then + if [ -n "$missing" ]; then echo "skip=true" >> $GITHUB_OUTPUT - echo "::warning::No test files found on target branch for platform ${platform}, skipping memory impact analysis" + echo "::warning::No test.${platform}.yaml on target branch for${missing}, skipping memory impact analysis" else echo "skip=false" >> $GITHUB_OUTPUT fi diff --git a/.github/workflows/external-component-bot.yml b/.github/workflows/external-component-bot.yml index 104988d7a58..bfe96f3944a 100644 --- a/.github/workflows/external-component-bot.yml +++ b/.github/workflows/external-component-bot.yml @@ -125,14 +125,20 @@ jobs: } async function getEsphomeAndComponentChanges(github, owner, repo, prNumber) { - const changedFiles = await github.rest.pulls.listFiles({ - owner: owner, - repo: repo, - pull_number: prNumber, - }); + const changedFiles = await github.paginate( + github.rest.pulls.listFiles, + { + owner: owner, + repo: repo, + pull_number: prNumber, + per_page: 100, + } + ); - const esphomeChanges = changedFiles.data - .filter(file => file.filename !== "esphome/core/defines.h" && file.filename.startsWith('esphome/')) + // Files used only for development and CI, which do not affect use as an external component + const ignoredFiles = ["esphome/core/defines.h", "esphome/idf_component.yml"]; + const esphomeChanges = changedFiles + .filter(file => !ignoredFiles.includes(file.filename) && file.filename.startsWith('esphome/')) .map(file => { const match = file.filename.match(/esphome\/([^/]+)/); return match ? match[1] : null; @@ -144,7 +150,7 @@ jobs: } const uniqueEsphomeChanges = [...new Set(esphomeChanges)]; - const componentChanges = changedFiles.data + const componentChanges = changedFiles .filter(file => file.filename.startsWith('esphome/components/')) .map(file => { const match = file.filename.match(/esphome\/components\/([^/]+)\//); diff --git a/.github/workflows/status-check-labels.yml b/.github/workflows/status-check-labels.yml index 72987c25b16..5f0966c1515 100644 --- a/.github/workflows/status-check-labels.yml +++ b/.github/workflows/status-check-labels.yml @@ -7,10 +7,6 @@ on: permissions: pull-requests: read # issues.listLabelsOnIssue to detect blocking labels (needs-docs, needs-developer-docs, merge-after-release, chained-pr) -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number }} - cancel-in-progress: true - jobs: check: name: Check blocking labels diff --git a/.github/workflows/sync-dependency-versions.yml b/.github/workflows/sync-dependency-versions.yml index 5599691ed19..73a3f7dbaa9 100644 --- a/.github/workflows/sync-dependency-versions.yml +++ b/.github/workflows/sync-dependency-versions.yml @@ -15,6 +15,8 @@ on: # base branch checkout, so fork code never runs with the token. pull_request_target: types: [opened, synchronize, reopened] + branches: + - dev paths: - requirements_dev.txt - requirements_test.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 48c0d1d6fd4..727e8e2f9aa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.16.9 + rev: v0.16.10 hooks: # Run the linter. - id: ruff diff --git a/AGENTS.md b/AGENTS.md index 6a60fc47b17..8194cd50407 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -177,6 +177,13 @@ file does, and it is the authority when they disagree. The most useful starting Pick the primitive by cadence: under 250 ms use a gated `loop()`; 500 ms and above use `set_interval`. Full reasoning, including why `set_interval` costs more below 500 ms: https://developers.esphome.io/architecture/components/advanced/#quick-rule-of-thumb + * **Scheduler ids:** name a timer only when it must be cancelled or replaced, and use a + `static constexpr uint32_t` id, never a string. Ids are per component instance and cannot clash with other + components, so number them from 0 and keep all of a component's ids together in one place. + ```cpp + static constexpr uint32_t READ_TIMEOUT_ID = 0; + this->set_timeout(READ_TIMEOUT_ID, 50, [this]() { this->read_(); }); + ``` * **Don't override a default with the same value:** if a base class method already returns what you want, do not override it. `Component::get_setup_priority()` returns `setup_priority::DATA`, so a component that wants `DATA` should simply leave it alone. @@ -230,6 +237,10 @@ file does, and it is the authority when they disagree. The most useful starting ``` * **Component Metadata:** + - `DOMAIN`: Required in every component's `__init__.py`, set to the component's own name as a plain + string (e.g. `DOMAIN = "my_component"`). Other code refers to the component through it, e.g. + `from esphome.components.my_component import DOMAIN`. Keep it in alphabetical order with the other + metadata constants. CI (`lint_component_domain`) fails without it. - `DEPENDENCIES`: List of required components - `AUTO_LOAD`: Components to automatically load - `CONFLICTS_WITH`: Incompatible components @@ -243,6 +254,7 @@ file does, and it is the authority when they disagree. The most useful starting import esphome.config_validation as cv from esphome.const import CONF_KEY, CONF_ID + DOMAIN = "my_component" CONF_PARAM = "param" # A constant that does not yet exist in esphome/const.py my_component_ns = cg.esphome_ns.namespace("my_component") @@ -384,6 +396,25 @@ file does, and it is the authority when they disagree. The most useful starting ) ``` + When the callback's parameters differ from the automation's arguments, or the trigger should only + fire for some values, pass `params`, `forward` and `when`; the helper then generates a capture-less + lambda (stored inline, nothing allocated). Name the parent with `automation.parent_ref(var)` and build + `forward` from `MockObj` calls, not f-strings of C++: + ```python + parent = automation.parent_ref(var) + index = cg.RawExpression("index") + await automation.build_callback_automation( + var, + "add_on_state_callback", + [(cg.StringRef, "x"), (cg.size_t, "i")], + conf, + params=[(cg.size_t, "index")], + forward=[cg.StringRef(parent.option_at(index)), index], + ) + ``` + `build_parent_callback_automation`, `build_trigger_callback`, `build_callback_automations` and + `build_trigger_automations` cover the other shapes; see their docstrings in `esphome/automation.py`. + **C++ -- no trigger class needed.** The callback registration method must be templatized to accept both `std::function` and lightweight forwarder structs (which avoid heap allocation): ```cpp class MyComponent : public Component { diff --git a/CODEOWNERS b/CODEOWNERS index b453cf45389..a851e62a3e5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -187,10 +187,13 @@ esphome/components/esp32_rmt/* @jesserockz esphome/components/esp32_rmt_led_strip/* @jesserockz esphome/components/esp8266/* @esphome/core esphome/components/esp_ldo/* @clydebarrow +esphome/components/espectre/* @francescopace +esphome/components/esphome/ota/* @esphome/core esphome/components/espnow/* @jesserockz esphome/components/espnow/packet_transport/* @EasilyBoredEngineer esphome/components/ethernet_info/* @gtjadsonsantos esphome/components/event/* @nohat +esphome/components/exponential_moving_average/* @clydebarrow esphome/components/exposure_notifications/* @OttoWinter esphome/components/ezo/* @ssieb esphome/components/ezo_pmp/* @carlos-sarmiento @@ -245,8 +248,11 @@ esphome/components/hmac_md5/* @dwmw2 esphome/components/hmac_sha256/* @dwmw2 esphome/components/hoermann_hcp/* @zweckj esphome/components/homeassistant/* @esphome/core @OttoWinter +esphome/components/homeassistant/button/* @jesserockz esphome/components/homeassistant/number/* @landonr +esphome/components/homeassistant/select/* @jesserockz esphome/components/homeassistant/switch/* @Links2004 +esphome/components/homeassistant/text/* @jesserockz esphome/components/honeywell_hih_i2c/* @Benichou34 esphome/components/honeywellabp/* @RubyBailey esphome/components/honeywellabp2_i2c/* @jpfaff @@ -282,6 +288,7 @@ esphome/components/inkplate/* @jesserockz @JosipKuci esphome/components/integration/* @OttoWinter esphome/components/internal_temperature/* @Mat931 esphome/components/interval/* @esphome/core +esphome/components/ir_rf_base/* @bdraco @kbx81 esphome/components/ir_rf_proxy/* @kbx81 esphome/components/it8951/* @koosoli @limengdu @Passific esphome/components/jsn_sr04t/* @Mafus1 @@ -512,6 +519,7 @@ esphome/components/sound_level/* @kahrendt esphome/components/spa06_base/* @danielkent-net esphome/components/spa06_i2c/* @danielkent-net esphome/components/spa06_spi/* @danielkent-net +esphome/components/spd2010/* @grischard esphome/components/speaker/* @jesserockz @kahrendt esphome/components/speaker/media_player/* @kahrendt @synesthesiam esphome/components/speaker_source/* @kahrendt @@ -608,6 +616,7 @@ esphome/components/ufire_ec/* @pvizeli esphome/components/ufire_ise/* @pvizeli esphome/components/ufm01/* @ljungqvist esphome/components/ultrasonic/* @ssieb @swoboda1337 +esphome/components/unicode/* @esphome/core esphome/components/update/* @jesserockz esphome/components/uponor_smatrix/* @kroimon esphome/components/usb_cdc_acm/* @kbx81 @@ -651,6 +660,7 @@ esphome/components/xiaomi_body_scale/* @dckiller51 esphome/components/xiaomi_hhccjcy10/* @fariouche esphome/components/xiaomi_lywsd02mmc/* @juanluss31 esphome/components/xiaomi_lywsd03mmc/* @ahpohl +esphome/components/xiaomi_mccgq02hl/* @ahpohl @morph027 esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc401/* @vevsvevs esphome/components/xiaomi_rtcgq02lm/* @jesserockz diff --git a/Doxyfile b/Doxyfile index 16193713239..d9a33a23b70 100644 --- a/Doxyfile +++ b/Doxyfile @@ -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.10.0-dev +PROJECT_NUMBER = 2026.11.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 diff --git a/docker/Dockerfile b/docker/Dockerfile index 8d3a5d2cc50..9edbd6e4f9b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -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.19.0 +RUN uv pip install --no-cache-dir esphome-device-builder==1.23.0 RUN \ platformio settings set enable_telemetry No \ diff --git a/esphome/__main__.py b/esphome/__main__.py index 40457dbcc2b..905339d2bcc 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -7,6 +7,7 @@ import importlib import logging import os from pathlib import Path +import platform import re import sys import time @@ -54,7 +55,7 @@ from esphome.const import ( ) from esphome.core import CORE, EsphomeError, coroutine from esphome.enum import StrEnum -from esphome.helpers import get_bool_env, indent, is_ip_address +from esphome.helpers import IS_MACOS, get_bool_env, indent, is_ip_address from esphome.log import AnsiFore, color, setup_log from esphome.stacktrace import LogLineProcessor from esphome.types import ConfigType @@ -829,9 +830,9 @@ def write_cpp_file() -> int: def compile_program(args: ArgsProtocol, config: ConfigType) -> int: if CORE.skip_bootloader and not (CORE.is_esp32 and CORE.using_toolchain_esp_idf): - # Info, not a warning: an orchestrator cannot see YAML toolchain - # overrides, this is its expected no-op, and a full build is safe. - _LOGGER.info( + # Debug only: an orchestrator cannot see YAML toolchain overrides, + # so this is its expected no-op, and a full build is safe. + _LOGGER.debug( "--skip-bootloader ignored: only supported on ESP32 with the " "esp-idf toolchain" ) @@ -1311,9 +1312,9 @@ def _choose_ota_platform(config: ConfigType, requested: str | None) -> str: # platform's final-validate hook merges duplicates anyway. available: dict[str, None] = {} for ota_item in config.get(CONF_OTA, []): - platform = ota_item.get(CONF_PLATFORM) - if platform in (CONF_ESPHOME, CONF_WEB_SERVER): - available[platform] = None + ota_platform = ota_item.get(CONF_PLATFORM) + if ota_platform in (CONF_ESPHOME, CONF_WEB_SERVER): + available[ota_platform] = None if not available: raise EsphomeError( @@ -1945,7 +1946,10 @@ def run_multiple_configs( safe_print() cmd = command_builder(f) - rc = run_external_process(*cmd) + # The parent already logged the Intel macOS warning; children skip it. + rc = run_external_process( + *cmd, env={**os.environ, _INTEL_MACOS_WARNED_ENV: "1"} + ) if rc == 0: print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {str(f)}") @@ -2614,6 +2618,27 @@ def _warn_if_source_tree_mismatch() -> None: ) +_INTEL_MACOS_REMOVAL = "2027.6.0" +_INTEL_MACOS_WARNED_ENV = "ESPHOME_INTEL_MACOS_WARNED" + + +def _warn_if_intel_macos() -> None: + """Warn that Intel (x86_64) Python on macOS loses support by _INTEL_MACOS_REMOVAL.""" + if ( + not IS_MACOS + or platform.machine() != "x86_64" + or _INTEL_MACOS_WARNED_ENV in os.environ + ): + return + _LOGGER.warning( + "Support for Intel Macs will end in ESPHome %s or earlier. The Python " + "packages ESPHome depends on have stopped publishing Intel macOS builds, " + "so future releases will not install on this machine. On an Apple " + "Silicon Mac, switch to a native arm64 Python.", + _INTEL_MACOS_REMOVAL, + ) + + def run_esphome(argv): from esphome.address_cache import AddressCache @@ -2633,6 +2658,7 @@ def run_esphome(argv): setup_log(log_level=args.log_level) _warn_if_source_tree_mismatch() + _warn_if_intel_macos() if args.command in PRE_CONFIG_ACTIONS: try: diff --git a/esphome/arduino8266/framework.py b/esphome/arduino8266/framework.py index b731b746751..16fd5d62829 100644 --- a/esphome/arduino8266/framework.py +++ b/esphome/arduino8266/framework.py @@ -49,40 +49,40 @@ class FrameworkRelease(NamedTuple): # Arduino core version -> its build in esphome-libs/arduino-esp8266 FRAMEWORK_RELEASES: dict[Version, FrameworkRelease] = { Version(3, 1, 2): FrameworkRelease( - "3.1.2-esphome.1", - "e80751e3123676b967143e39c61f2d8693946db4c7806f2a83dcaaf797ecd582", - 37189311, + "3.1.2-esphome.3", + "3af533a85163804e164500a5b254dc23b922080e25ecb7dc37b3dd8ac84651db", + 37185650, ), } TOOLCHAIN_PACKAGE = "toolchain-xtensa-lx106-elf" # gcc 10.3, the toolchain Arduino core 3.x builds with; the build # generator's compile flags are tuned to it. -TOOLCHAIN_VERSION = "10.3.0-esphome.2" +TOOLCHAIN_VERSION = "10.3.0-esphome.3" _TOOLCHAIN_RELEASES = ( "https://github.com/esphome-libs/xtensa-lx106-elf-toolchain/releases/" ) # Registry system tag -> (sha256, size) of that host's archive TOOLCHAIN_BUILDS: dict[str, tuple[str, int]] = { "darwin_arm64": ( - "849cede44d4d5c6ea0f14099783239f559f46327bea314281814f2652b486201", - 60830321, + "ad0ea929238d9b527c43e0d20257acc2a5680bdb0d2b1cf8f2c99169786217e9", + 60747260, ), "darwin_x86_64": ( - "ca69904daabf0c5983b372423e5e62f49182a793e992c052e94666852470c897", - 64149487, + "997090cac80c8eb59604ec52b73fb3568e5f2d9b2971f83c3f2a5c545c9c949c", + 64065752, ), "linux_aarch64": ( - "60a49a4f082bf246544bd409a9517dbbcab19bb30ac9decbee544b896aaccbd6", - 67573397, + "403f14d0d5755682fcd6eaea1e4c457a1d8cad30690e52fb0c804d8db266b908", + 67487067, ), "linux_x86_64": ( - "1fba33ca1494ec79f2776e0e37eca93282d30f8bb9992f5f4f9a655d6fff1db4", - 68431336, + "64f21fa3ba736b363dc8c234dbbb39e005e574e33de08a78e9a00c55a64ae706", + 68343668, ), "windows_amd64": ( - "af9066b0e5bf036f04f2bd9d08b89b81a7f183c57dac0abcaff71dd861cf5f3b", - 67664137, + "1e62abf8df041ce5158f5770dffef955f2f981ce7f7adee88fcd5a8197705aa6", + 67536405, ), } diff --git a/esphome/automation.py b/esphome/automation.py index c8d0e4c5743..6b6af4ed72e 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -1,4 +1,4 @@ -from collections.abc import Callable +from collections.abc import Callable, Sequence from dataclasses import dataclass, field import logging import string @@ -21,9 +21,9 @@ from esphome.const import ( CONF_TYPE_ID, CONF_UPDATE_INTERVAL, ) -from esphome.core import CORE, ID, EsphomeError, Lambda +from esphome.core import CORE, ID, EsphomeError, HexInt, Lambda from esphome.cpp_generator import ( - FlashStringLiteral, + Expression, LambdaExpression, MockObj, MockObjClass, @@ -35,6 +35,32 @@ from esphome.types import ConfigType, SafeExpType from esphome.util import Registry +def progmem_bytes(name: str, data: bytes | list[int]) -> MockObj: + """Shared PROGMEM table for constant bytes; equal payloads share one, empty is nullptr.""" + if not data: + return cg.nullptr + return cg.shared_progmem_array( + name, cg.uint8, cg.ArrayInitializer(*(HexInt(x) for x in data)) + ) + + +async def templatable_bytes( + value: Any, + args: TemplateArgsType, + set_template: MockObj, + set_static: MockObj, + table_name: str, +) -> None: + """Set a TemplatableBytes: a lambda via set_template, constant bytes via set_static.""" + if cg.is_template(value): + fn = await cg.templatable(value, args, cg.std_vector.template(cg.uint8)) + cg.add(set_template(fn)) + elif len(value) > 0xFFFF: + raise EsphomeError(f"Byte payload is {len(value)} bytes; the maximum is 65535") + else: + cg.add(set_static(progmem_bytes(table_name, value), len(value))) + + def maybe_simple_id(*validators): """Allow a raw ID to be specified in place of a config block. If the value that's being validated is a dictionary, it's passed as-is to the specified validators. Otherwise, it's @@ -219,9 +245,7 @@ ApplyCondition = cg.esphome_ns.class_("ApplyCondition", Condition) def flash_string(config: ConfigType, value: str) -> str: """Default renderer for ``std::string`` constants; copies the literal out of flash on ESP8266.""" - if CORE.is_esp8266: - return f"progmem_string({FlashStringLiteral(value)})" - return str(cg.safe_exp(value)) + return str(cg.progmem_string(value)) def literal_with_length(config: ConfigType, value: str) -> str: @@ -233,13 +257,24 @@ def literal_with_length(config: ConfigType, value: str) -> str: return f"{cg.safe_exp(value)}, {len(value.encode('utf-8'))}" +def string_ref_literal(config: ConfigType, value: str) -> str: + """Renderer for a ``StringRef`` comparison: a flash literal on ESP8266, else ``StringRef(literal, length)``.""" + if CORE.is_esp8266: + return str(cg.FlashStringLiteral(value)) + return f"StringRef({literal_with_length(config, value)})" + + +def _names_parent(text: str) -> bool: + return any(f == "parent" for _, f, _, _ in string.Formatter().parse(text)) + + @dataclass(frozen=True) class ApplyCall: """One statement from config keys, e.g. ``"set_range({}, {})"`` with ``((CONF_LOW, cg.float_), ...)``. Each arg is ``(conf_key, type_)`` or ``(conf_key, type_, const_fn)``. A ``conf_key`` may be a - path into nested sections. A plain ``str`` ``type_`` is raw C++ type text and may use - ``{parent}``. ``const_fn(config, value)`` renders a constant's argument text; a lambda or an + path into nested sections. ``target`` and a plain ``str`` ``type_`` (raw C++ type text) may + name the parent object as ``{parent}``. ``const_fn(config, value)`` renders a constant's argument text; a lambda or an id bypasses it. The statement is skipped when none of its keys is set, always emitted when it has no keys, and a partial set is a config error. """ @@ -251,13 +286,13 @@ class ApplyCall: fields = [ f for _, f, _, _ in string.Formatter().parse(self.target) if f is not None ] - if any(fields): + if any(f not in ("", "parent") for f in fields): raise ValueError( - f"apply target {self.target!r}: only bare {{}} placeholders" + f"apply target {self.target!r}: only {{}} and {{parent}} placeholders" ) - if len(fields) != len(self.args): + if (count := fields.count("")) != len(self.args): raise ValueError( - f"apply target {self.target!r} has {len(fields)} " + f"apply target {self.target!r} has {count} " f"placeholder(s) for {len(self.args)} config key(s)" ) if any(len(arg) not in (2, 3) for arg in self.args): @@ -265,6 +300,12 @@ class ApplyCall: f"apply target {self.target!r}: each arg is (conf_key, type_[, const_fn])" ) + @property + def names_parent(self) -> bool: + return _names_parent(self.target) or any( + isinstance(arg[1], str) and _names_parent(arg[1]) for arg in self.args + ) + @property def members(self) -> list[tuple[Any, Any, Any]]: """Each arg as ``(conf_key, type_, const_fn or None)``.""" @@ -339,9 +380,16 @@ def _check_key_in_schema( schema = schema.schema[markers[part]] +def parent_ref(var: MockObj) -> MockObj: + """``var`` named from global scope, so a trigger argument cannot shadow it. + + Also how a generated callback names its Automation. + """ + return MockObj(f"::{var}", "->") + + async def _apply_parent(config: ConfigType, id_key: str = CONF_ID) -> str: - # Global-scope qualified so a trigger arg named like the id cannot shadow it. - return f"::{await cg.get_variable(config[id_key])}" + return str(parent_ref(await cg.get_variable(config[id_key]))) def _apply_lambda_args(args: TemplateArgsType) -> TemplateArgsType: @@ -373,7 +421,7 @@ async def _render_values( members: list[tuple[Any, Any, Any]], values: list[Any], config: ConfigType, - parent: str, + parent: str | None, lambda_args: TemplateArgsType, compare: bool = False, ) -> list[str]: @@ -451,12 +499,12 @@ def register_apply_action( statements: list[str] = [] for target, members in statements_spec: values = _apply_values(config, members) - if members and all(value is None for value in values): + if not _apply_call_active(members, values): continue exprs = await _render_values( name, target, members, values, config, parent, lambda_args ) - statements.append(f"{receiver}{target.format(*exprs)};") + statements.append(f"{receiver}{target.format(*exprs, parent=parent)};") if call: statements = [ f"auto apply_call = {parent}->{call}();", @@ -470,6 +518,27 @@ def register_apply_action( register_action(name, ApplyAction, schema, synchronous=True)(builder) +def _apply_call_active(members: list[tuple[Any, Any, Any]], values: list[Any]) -> bool: + """An ``ApplyCall`` is emitted unless it has keys and none of them is set.""" + return not members or any(value is not None for value in values) + + +async def _render_check( + name: str, + target: str, + members: list[tuple[Any, Any, Any]], + values: list[Any], + config: ConfigType, + parent: str | None, + lambda_args: TemplateArgsType, +) -> str: + """Render a boolean check with ``values`` compared against config.""" + exprs = await _render_values( + name, target, members, values, config, parent, lambda_args, compare=True + ) + return target.format(*exprs, parent=parent) + + def register_apply_condition( name: str, schema: cv.Schema, check: str | ApplyCall, id_key: str = CONF_ID ) -> None: @@ -495,22 +564,16 @@ def register_apply_condition( ) -> MockObj: parent = await _apply_parent(config, id_key) lambda_args = _apply_lambda_args(args) - exprs = await _render_values( - name, - call.target, - members, - _apply_values(config, members), - config, - parent, - lambda_args, - compare=True, + values = _apply_values(config, members) + check = await _render_check( + name, call.target, members, values, config, parent, lambda_args ) return _apply_function( condition_id, cg.bool_, template_arg, lambda_args, - [f"return {parent}->{call.target.format(*exprs)};"], + [f"return {parent}->{check};"], ) register_condition(name, ApplyCondition, schema)(builder) @@ -646,52 +709,20 @@ XorCondition = cg.esphome_ns.class_("XorCondition", Condition) @register_condition("and", AndCondition, validate_condition_list) -async def and_condition_to_code( - config: ConfigType, - condition_id: ID, - template_arg: cg.TemplateArguments, - args: TemplateArgsType, -) -> MockObj: - conditions = await build_condition_list(config, template_arg, args) - return cg.new_Pvariable( - condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions - ) - - -@register_condition("or", OrCondition, validate_condition_list) -async def or_condition_to_code( - config: ConfigType, - condition_id: ID, - template_arg: cg.TemplateArguments, - args: TemplateArgsType, -) -> MockObj: - conditions = await build_condition_list(config, template_arg, args) - return cg.new_Pvariable( - condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions - ) - - @register_condition("all", AndCondition, validate_condition_list) -async def all_condition_to_code( - config: ConfigType, - condition_id: ID, - template_arg: cg.TemplateArguments, - args: TemplateArgsType, -) -> MockObj: - conditions = await build_condition_list(config, template_arg, args) - return cg.new_Pvariable( - condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions - ) - - +@register_condition("or", OrCondition, validate_condition_list) @register_condition("any", OrCondition, validate_condition_list) -async def any_condition_to_code( +@register_condition("xor", XorCondition, validate_condition_list) +async def condition_group_to_code( config: ConfigType, condition_id: ID, template_arg: cg.TemplateArguments, args: TemplateArgsType, ) -> MockObj: conditions = await build_condition_list(config, template_arg, args) + # A group of one is that condition for and, or and xor; skip the wrapper. + if len(conditions) == 1: + return conditions[0] return cg.new_Pvariable( condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions ) @@ -708,19 +739,6 @@ async def not_condition_to_code( return cg.new_Pvariable(condition_id, template_arg, condition) -@register_condition("xor", XorCondition, validate_condition_list) -async def xor_condition_to_code( - config: ConfigType, - condition_id: ID, - template_arg: cg.TemplateArguments, - args: TemplateArgsType, -) -> MockObj: - conditions = await build_condition_list(config, template_arg, args) - return cg.new_Pvariable( - condition_id, cg.TemplateArguments(len(conditions), *template_arg), conditions - ) - - @register_condition("lambda", LambdaCondition, cv.returning_lambda) async def lambda_condition_to_code( config: ConfigType, @@ -1030,23 +1048,78 @@ def has_non_synchronous_actions(actions: ConfigType) -> bool: return False -async def build_automation( - trigger: MockObj, args: TemplateArgsType, config: ConfigType +async def _new_automation( + args: TemplateArgsType, config: ConfigType, *ctor_args: MockObj ) -> MockObj: - arg_types = [arg[0] for arg in args] - templ = cg.TemplateArguments(*arg_types) - obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, trigger) + """Create the Automation for ``config`` with its actions.""" + templ = cg.TemplateArguments(*(arg[0] for arg in args)) + obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ, *ctor_args) actions = await build_action_list(config[CONF_THEN], templ, args) cg.add(obj.add_actions(actions)) return obj +async def build_automation( + trigger: MockObj, args: TemplateArgsType, config: ConfigType +) -> MockObj: + return await _new_automation(args, config, trigger) + + +async def build_trigger_callback( + args: TemplateArgsType, + config: ConfigType, + params: TemplateArgsType, + forward: Sequence[str | Expression] | None = None, + when: str | ApplyCall | None = None, + parent: MockObj | None = None, +) -> LambdaExpression: + """Build the Automation for ``config`` and return a stateless callback that triggers it. + + ``params`` are the parent callback's parameters, ``forward`` the expressions passed to + ``trigger()`` (default: the parameter names; write the parent as ``parent_ref(var)``), + ``when`` a filter the callback returns early on, skipped like any ``ApplyCall`` when none + of its keys is set. ``when`` may name ``parent`` as ``{parent}``, e.g. + ``"{parent}->is_fully_open()"``. + """ + members: list[tuple[Any, Any, Any]] = [] + if when is not None: + call = when if isinstance(when, ApplyCall) else ApplyCall(when) + members = call.members + if parent is None and call.names_parent: + raise ValueError(f"trigger filter {call.target!r} names {{parent}}") + obj = await _new_automation(args, config) + lambda_args = _apply_lambda_args(params) + statements: list[str] = [] + if when is not None: + values = _apply_values(config, members) + if _apply_call_active(members, values): + check = await _render_check( + "trigger filter", + call.target, + members, + values, + config, + None if parent is None else str(parent_ref(parent)), + lambda_args, + ) + statements.append(f"if (!({check}))\n return;") + if forward is None: + forward = [name for _, name in params] + statements.append(f"{parent_ref(obj)}->trigger({', '.join(map(str, forward))});") + return LambdaExpression( + ["\n".join(statements)], lambda_args, capture="", return_type=cg.void + ) + + async def build_callback_automation( parent: MockObj, callback_method: str, args: TemplateArgsType, config: ConfigType, forwarder: MockObj | MockObjClass | None = None, + params: TemplateArgsType | None = None, + forward: Sequence[str | Expression] | None = None, + when: str | ApplyCall | None = None, ) -> None: """Build an Automation and register it as a callback on the parent. @@ -1058,6 +1131,9 @@ async def build_callback_automation( pointer-sized (single Automation* field) to fit inline in Callback::ctx_ and avoid heap allocation. + With ``params``, ``forward`` or ``when`` the callback is instead the stateless + lambda of ``build_trigger_callback``; ``forwarder`` cannot be combined with them. + :param parent: The component object (e.g., button, sensor). :param callback_method: Name of the callback method (e.g., "add_on_press_callback"). :param args: Automation template args as list of (type, name) tuples. @@ -1066,22 +1142,56 @@ async def build_callback_automation( TriggerForwarder. Pass any struct type whose aggregate init takes a single Automation pointer (e.g., TriggerOnTrueForwarder). """ - arg_types = [arg[0] for arg in args] - templ = cg.TemplateArguments(*arg_types) - obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ) - actions = await build_action_list(config[CONF_THEN], templ, args) - cg.add(obj.add_actions(actions)) + if params is not None or forward is not None or when is not None: + if forwarder is not None: + raise ValueError( + "forwarder cannot be combined with params, forward or when" + ) + callback = await build_trigger_callback( + args, config, args if params is None else params, forward, when, parent + ) + cg.add(getattr(parent, callback_method)(callback)) + return + obj = await _new_automation(args, config) # Use template forwarder structs for deduplication. The compiler generates # one operator() per forwarder type; different automation pointers are just # data in the struct. if forwarder is None: - forwarder = TriggerForwarder.template(templ) + forwarder = TriggerForwarder.template(*(arg[0] for arg in args)) # RawExpression for aggregate init — both forwarder and obj are codegen # MockObjs (not user input), and there's no Expression type for positional # aggregate initialization (StructInitializer uses named fields). cg.add(getattr(parent, callback_method)(cg.RawExpression(f"{forwarder}{{{obj}}}"))) +async def build_parent_callback_automation( + parent: MockObj, callback_method: str, arg: tuple[Any, str], config: ConfigType +) -> None: + """Register an Automation that receives ``parent`` from a callback that carries nothing. + + ``arg`` is the automation's ``(type, name)``, e.g. ``(Fan.operator("ptr"), "x")``. + """ + await build_callback_automation( + parent, callback_method, [arg], config, params=[], forward=[parent_ref(parent)] + ) + + +async def build_trigger_automations( + parent: MockObj | None, + config: ConfigType, + entries: tuple[tuple[str, TemplateArgsType], ...], +) -> None: + """Instantiate each entry's Trigger class, with ``parent`` when given, and build its automations. + + ``entries`` are ``(conf_key, args)`` pairs; the class comes from the entry's ``CONF_TRIGGER_ID``. + """ + ctor_args = () if parent is None else (parent,) + for conf_key, args in entries: + for conf in config.get(conf_key, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], *ctor_args) + await build_automation(trigger, args, conf) + + @dataclass(frozen=True, slots=True) class CallbackAutomation: """A single callback automation entry for build_callback_automations.""" @@ -1090,6 +1200,9 @@ class CallbackAutomation: callback_method: str args: TemplateArgsType = field(default_factory=list) forwarder: MockObj | MockObjClass | None = None + params: TemplateArgsType | None = None + forward: Sequence[str | Expression] | None = None + when: str | ApplyCall | None = None async def build_callback_automations( @@ -1111,4 +1224,7 @@ async def build_callback_automations( entry.args, conf, forwarder=entry.forwarder, + params=entry.params, + forward=entry.forward, + when=entry.when, ) diff --git a/esphome/build_gen/arduino8266.py b/esphome/build_gen/arduino8266.py index 5b14af5a1d7..44369136a01 100644 --- a/esphome/build_gen/arduino8266.py +++ b/esphome/build_gen/arduino8266.py @@ -54,6 +54,7 @@ from esphome.components.esp8266.const import ( KEY_ESP8266, KEY_FLASH_SIZE, KEY_SCANF_FLOAT, + THROW_STUBS_HEADER, ) from esphome.core import CORE, EsphomeError from esphome.framework_helpers import get_project_cxx_compile_flags @@ -1087,32 +1088,8 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool: if "USE_ESP8266_WAVEFORM_STUBS" in flag_defines: core_exclude |= _CORE_EXCLUDE_WAVEFORM - archives = [] - # variant_dir existence was already enforced with the include dirs - variant_sources = collect_sources(variant_dir) - if variant_sources: - objs = compile_edges(lines, variant_sources, variant_dir, "variant") - lines.append(f"build libFrameworkArduinoVariant.a: ar {' '.join(objs)}") - archives.append("libFrameworkArduinoVariant.a") - - core_objs = compile_edges( - lines, collect_sources(core_dir, core_exclude), core_dir, "core" - ) - if not core_objs: - # An empty archive would link into a wall of undefined references - # (app_entry, the exception vectors) far from the cause - raise EsphomeError( - f"{_INCOMPLETE_INSTALL}: no core sources in {core_dir}; {_CLEAN_HINT}" - ) - lines.append(f"build libFrameworkArduino.a: ar {' '.join(core_objs)}") - archives.append("libFrameworkArduino.a") - - lib_archives, direct_objs = library_edges(lines, libraries) - archives += lib_archives - - # One source of truth with the PlatformIO path: esp8266/__init__ pins - # build_src_flags (the throw_stubs force-include); -include paths - # resolve against the source root + # esp8266/__init__ pins build_src_flags (the throw_stubs force-include), as on + # the PlatformIO path; -include paths resolve against the source root src_other: list[str] = [] src_includes: list[str] = [] src_it = iter( @@ -1131,7 +1108,45 @@ def write_project(paths: InstalledPaths, ccache: str | None) -> bool: src_includes.append(tok[len("-include") :]) else: src_other.append(_shell_token(tok)) + # The throw stubs abort: none anywhere with exceptions on, else the core and + # libraries get them too (src keeps them after its pch include) + if config.exceptions: + src_includes = [h for h in src_includes if h != THROW_STUBS_HEADER] include_flags = [f"-include {_q(src_dir / h)}" for h in src_includes] + framework_flags = "" + if THROW_STUBS_HEADER in src_includes: + stubs_flag = include_flags[src_includes.index(THROW_STUBS_HEADER)] + lines.append(f"frameworkflags = {stubs_flag}") + framework_flags = "$frameworkflags" + archives = [] + # variant_dir existence was already enforced with the include dirs + variant_sources = collect_sources(variant_dir) + if variant_sources: + objs = compile_edges( + lines, variant_sources, variant_dir, "variant", flags=framework_flags + ) + lines.append(f"build libFrameworkArduinoVariant.a: ar {' '.join(objs)}") + archives.append("libFrameworkArduinoVariant.a") + + core_objs = compile_edges( + lines, + collect_sources(core_dir, core_exclude), + core_dir, + "core", + flags=framework_flags, + ) + if not core_objs: + # An empty archive would link into a wall of undefined references + # (app_entry, the exception vectors) far from the cause + raise EsphomeError( + f"{_INCOMPLETE_INSTALL}: no core sources in {core_dir}; {_CLEAN_HINT}" + ) + lines.append(f"build libFrameworkArduino.a: ar {' '.join(core_objs)}") + archives.append("libFrameworkArduino.a") + + lib_archives, direct_objs = library_edges(lines, libraries, framework_flags) + archives += lib_archives + # One shared variable instead of repeating the flags line on every src # edge (hundreds of edges in a real project) lines.append(f"srcflags = {' '.join(src_other + include_flags)}") diff --git a/esphome/build_gen/espidf.py b/esphome/build_gen/espidf.py index 42a1de5a4b9..a826a2b6283 100644 --- a/esphome/build_gen/espidf.py +++ b/esphome/build_gen/espidf.py @@ -160,7 +160,7 @@ def _cmake_quote(value: str) -> str: # CONFIG_APP_BUILD_BOOTLOADER is hidden and force-selected, so it can only be # cleared at the CMake level (the same state IDF's RAM-app build type uses). -# The macro is IDF's __build_process_project_includes plus two added lines; +# The macro is IDF's __build_process_project_includes plus a few added lines; # the flag is ignored and the bootloader builds as usual if IDF changes it. IDF_BOOTLOADER_OVERRIDE = """\ # ESPHome bootloader skip switch; see esphome/espidf/toolchain.py. @@ -173,6 +173,9 @@ if(ESPHOME_SKIP_BOOTLOADER) # skipped) bootloader project_include leaks; keep it defined, or # its empty TARGET_SRC_NAME sends file(GLOB_RECURSE) across /. idf_build_get_property(idf_target IDF_TARGET) + # partition_table's V1 ECDSA signing reads this key, which the + # skipped bootloader project_include also sets. + get_filename_component(SECURE_BOOT_SIGNING_KEY "${CONFIG_SECURE_BOOT_SIGNING_KEY}" ABSOLUTE BASE_DIR "${project_dir}") idf_build_get_property(build_properties __BUILD_PROPERTIES) foreach(build_property ${build_properties}) idf_build_get_property(val ${build_property}) @@ -198,6 +201,10 @@ endif() BOOTLOADER_OVERRIDE_ADDED_LINES = ( 'set(CONFIG_APP_BUILD_BOOTLOADER "")', "idf_build_get_property(idf_target IDF_TARGET)", + ( + "get_filename_component(SECURE_BOOT_SIGNING_KEY" + ' "${CONFIG_SECURE_BOOT_SIGNING_KEY}" ABSOLUTE BASE_DIR "${project_dir}")' + ), ) _MACRO = re.compile( @@ -230,6 +237,35 @@ def idf_macro_matches(idf_path: Path) -> bool: return live == _EXPECTED_MACRO +def _cmake_args_for_write(minimal: bool) -> dict[str, str]: + """The cg.add_cmake_arg() values for one project write. + + The discovery (minimal) write lets every built-in component register so + the list it finds, cached per target, serves any exclusion set; the full + write drops the excluded names from that list and passes all of them to + IDF. Names outside the framework's components directory (managed + components) stay excluded on the discovery write too, since the list + only holds built-in ones. A checkout supplied through IDF_PATH caches + nothing, so its discovery keeps every exclusion. + """ + from esphome.espidf.toolchain import _esphome_manages_idf, _get_idf_path + + args = dict(CORE.cmake_args) + if ( + not minimal + or not _esphome_manages_idf() + or not (excluded := args.get("EXCLUDE_COMPONENTS")) + ): + return args + root = _get_idf_path() / "components" + outside = [name for name in excluded.split(";") if not (root / name).is_dir()] + if outside: + args["EXCLUDE_COMPONENTS"] = ";".join(outside) + else: + del args["EXCLUDE_COMPONENTS"] + return args + + def get_project_cmakelists( minimal: bool = False, builtin_components: list[str] | None = None ) -> str: @@ -293,11 +329,10 @@ def get_project_cmakelists( # CMake variables registered via cg.add_cmake_arg(). Emitted before # include(project.cmake) so values like EXCLUDE_COMPONENTS are already - # set when project.cmake seeds the component list, and on minimal - # (discovery) writes too so excluded components never register. + # set when project.cmake seeds the component list. cmake_args = "\n".join( f"set({name} {_cmake_quote(value)})" - for name, value in sorted(CORE.cmake_args.items()) + for name, value in sorted(_cmake_args_for_write(minimal).items()) ) # Per-project list exposed as a CMake variable so converted PIO libs @@ -320,10 +355,10 @@ def get_project_cmakelists( # component's REQUIRES including real IDF components). Referenced by # src/CMakeLists and by each converted PIO lib's CMakeLists. Skipped # on minimal writes because project_description.json may be stale. - # Excluded components are dropped here as well: a stale - # project_description.json from a build without exclusions may still - # list them, and requiring an excluded component pulls it back into - # the build (IDF requirement expansion overrides EXCLUDE_COMPONENTS). + # Excluded components are dropped here as well: the discovery list + # (cached per target) still holds them, and requiring an excluded + # component pulls it back into the build (IDF requirement expansion + # overrides EXCLUDE_COMPONENTS). # Derived from the EXCLUDE_COMPONENTS cmake arg emitted above so the # two can never disagree within one generated file. builtin_components_property = ( @@ -390,15 +425,24 @@ project({CORE.name}) # Emit per-memory-type JSON size data for ESPHome to read post-build. # json2 stays small; raw dumps every symbol (~2s on a large map) and # this command runs inside the link edge, blocking everything downstream. -# The map is a BYPRODUCT so ninja knows the link writes it; IDF's size -# target depends on the map and can then be built in the same run as all. -# IDF's cmakev2 declares the map itself, so drop this line on that switch. +# IDF's size target depends on the map, so ninja has to know the link +# writes it for size to build in the same run as all. IDF declares the +# map itself since espressif/esp-idf#19201 (and in cmakev2), and ninja +# rejects two rules for one output, so declare it here only when IDF has +# not. add_custom_command(OUTPUT) marks its outputs GENERATED. +get_source_file_property(esphome_map_declared + ${{CMAKE_BINARY_DIR}}/${{CMAKE_PROJECT_NAME}}.map GENERATED) +if(esphome_map_declared) + set(esphome_map_byproducts) +else() + set(esphome_map_byproducts BYPRODUCTS ${{CMAKE_BINARY_DIR}}/${{CMAKE_PROJECT_NAME}}.map) +endif() add_custom_command( TARGET ${{CMAKE_PROJECT_NAME}}.elf POST_BUILD COMMAND ${{PYTHON}} -m esp_idf_size {size_ng_flag} --format=json2 -o ${{CMAKE_BINARY_DIR}}/esp_idf_size.json ${{CMAKE_PROJECT_NAME}}.map - BYPRODUCTS ${{CMAKE_BINARY_DIR}}/${{CMAKE_PROJECT_NAME}}.map + ${{esphome_map_byproducts}} WORKING_DIRECTORY ${{CMAKE_BINARY_DIR}} VERBATIM ) @@ -547,10 +591,9 @@ def write_project( ) # Snapshot the exclusion set so has_outdated_files() can trigger a - # discovery reconfigure when it changes. Excluded components never - # register in project_description.json, so re-including one (e.g. a - # config gains mqtt) requires a fresh discovery pass before the - # ESPHOME_PROJECT_BUILTIN_COMPONENTS property can list it. + # reconfigure when it changes. The ESPHOME_PROJECT_BUILTIN_COMPONENTS + # property drops excluded names, so re-including one (e.g. a config + # gains mqtt) needs a reconfigure to rebuild it from the discovery list. write_file_if_changed( CORE.relative_build_path("exclude_components.esphomeinternal"), ";".join(get_excluded_builtin_components()), diff --git a/esphome/build_helpers/ninja_gen.py b/esphome/build_helpers/ninja_gen.py index 8e72b36b3fd..a2ee244a29f 100644 --- a/esphome/build_helpers/ninja_gen.py +++ b/esphome/build_helpers/ninja_gen.py @@ -207,10 +207,12 @@ def compile_edges( def library_edges( - lines: list[str], libraries: list[ArduinoLibrary] + lines: list[str], libraries: list[ArduinoLibrary], extra_flags: str = "" ) -> tuple[list[str], list[str]]: """Emit every library's compile and archive edges. + ``extra_flags`` (raw ninja text) precede each library's own flags. + Returns the archive names and the objects that link directly. A library's own include dirs lead its compile lines, as PlatformIO searched them first: the include path is one global list, so another library's @@ -232,7 +234,7 @@ def library_edges( lib.sources, common_parent(lib.sources), f"lib/{lib.name}", - flags=" ".join(_shell_token(f) for f in lib.flags), + flags=" ".join(filter(None, [extra_flags, *map(_shell_token, lib.flags)])), own_includes=" ".join(f"-I{_q(d)}" for d in lib.include_dirs), ) if not lib.lib_archive: diff --git a/esphome/codegen.py b/esphome/codegen.py index daf53db43fd..4db01a21740 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -31,13 +31,16 @@ from esphome.cpp_generator import ( # noqa: F401 add_global, add_library, add_platformio_option, + extern_progmem_array, get_variable, get_variable_with_full_id, + is_static_pointer, is_template, new_Pvariable, new_variable, process_lambda, progmem_array, + progmem_string, safe_exp, set_cpp_standard, shared_progmem_array, diff --git a/esphome/components/a01nyub/__init__.py b/esphome/components/a01nyub/__init__.py index 4c84847fb69..45206b66bc2 100644 --- a/esphome/components/a01nyub/__init__.py +++ b/esphome/components/a01nyub/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@MrSuicideParrot"] +DOMAIN = "a01nyub" diff --git a/esphome/components/a01nyub/a01nyub.cpp b/esphome/components/a01nyub/a01nyub.cpp index 6111af2b7e5..8c97169cbf1 100644 --- a/esphome/components/a01nyub/a01nyub.cpp +++ b/esphome/components/a01nyub/a01nyub.cpp @@ -6,7 +6,7 @@ namespace esphome::a01nyub { -static const char *const TAG = "a01nyub.sensor"; +ESPHOME_LOG_TAG(TAG, "a01nyub.sensor"); void A01nyubComponent::loop() { uint8_t data; diff --git a/esphome/components/a02yyuw/__init__.py b/esphome/components/a02yyuw/__init__.py index 6724dbb970d..f6a28dfa18c 100644 --- a/esphome/components/a02yyuw/__init__.py +++ b/esphome/components/a02yyuw/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@TH-Braemer"] +DOMAIN = "a02yyuw" diff --git a/esphome/components/a02yyuw/a02yyuw.cpp b/esphome/components/a02yyuw/a02yyuw.cpp index 2832334ef19..20e8f6c7b56 100644 --- a/esphome/components/a02yyuw/a02yyuw.cpp +++ b/esphome/components/a02yyuw/a02yyuw.cpp @@ -6,7 +6,7 @@ namespace esphome::a02yyuw { -static const char *const TAG = "a02yyuw.sensor"; +ESPHOME_LOG_TAG(TAG, "a02yyuw.sensor"); void A02yyuwComponent::loop() { uint8_t data; diff --git a/esphome/components/a4988/__init__.py b/esphome/components/a4988/__init__.py index e69de29bb2d..55797725d61 100644 --- a/esphome/components/a4988/__init__.py +++ b/esphome/components/a4988/__init__.py @@ -0,0 +1 @@ +DOMAIN = "a4988" diff --git a/esphome/components/a4988/a4988.cpp b/esphome/components/a4988/a4988.cpp index d8fc6752f37..4af9e8baef7 100644 --- a/esphome/components/a4988/a4988.cpp +++ b/esphome/components/a4988/a4988.cpp @@ -3,7 +3,7 @@ namespace esphome::a4988 { -static const char *const TAG = "a4988.stepper"; +ESPHOME_LOG_TAG(TAG, "a4988.stepper"); void A4988::setup() { if (this->sleep_pin_ != nullptr) { diff --git a/esphome/components/absolute_humidity/__init__.py b/esphome/components/absolute_humidity/__init__.py index 8f113b48f6a..5cfae55a40a 100644 --- a/esphome/components/absolute_humidity/__init__.py +++ b/esphome/components/absolute_humidity/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@DAVe3283"] +DOMAIN = "absolute_humidity" diff --git a/esphome/components/absolute_humidity/absolute_humidity.cpp b/esphome/components/absolute_humidity/absolute_humidity.cpp index 3137a59fad6..36b6534c0de 100644 --- a/esphome/components/absolute_humidity/absolute_humidity.cpp +++ b/esphome/components/absolute_humidity/absolute_humidity.cpp @@ -3,7 +3,7 @@ namespace esphome::absolute_humidity { -static const char *const TAG{"absolute_humidity.sensor"}; +ESPHOME_LOG_TAG(TAG, "absolute_humidity.sensor"); void AbsoluteHumidityComponent::setup() { this->temperature_sensor_->add_on_state_callback([this](float state) { diff --git a/esphome/components/ac_dimmer/__init__.py b/esphome/components/ac_dimmer/__init__.py index e69de29bb2d..7025ecff72d 100644 --- a/esphome/components/ac_dimmer/__init__.py +++ b/esphome/components/ac_dimmer/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ac_dimmer" diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index 477962a0404..e126a3d52ab 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -14,7 +14,7 @@ namespace esphome::ac_dimmer { -static const char *const TAG = "ac_dimmer"; +ESPHOME_LOG_TAG(TAG, "ac_dimmer"); // Global array to store dimmer objects static AcDimmerDataStore *all_dimmers[32]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/ac_dimmer/hw_timer_esp_idf.cpp b/esphome/components/ac_dimmer/hw_timer_esp_idf.cpp index 543b4760851..fd26e6bccd3 100644 --- a/esphome/components/ac_dimmer/hw_timer_esp_idf.cpp +++ b/esphome/components/ac_dimmer/hw_timer_esp_idf.cpp @@ -9,7 +9,7 @@ #include "esp_clk_tree.h" #include "soc/clk_tree_defs.h" -static const char *const TAG = "hw_timer_esp_idf"; +ESPHOME_LOG_TAG(TAG, "hw_timer_esp_idf"); namespace esphome::ac_dimmer { diff --git a/esphome/components/adalight/__init__.py b/esphome/components/adalight/__init__.py index afdfefaba67..caf66167b1e 100644 --- a/esphome/components/adalight/__init__.py +++ b/esphome/components/adalight/__init__.py @@ -9,6 +9,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType DEPENDENCIES = ["uart"] +DOMAIN = "adalight" adalight_ns = cg.esphome_ns.namespace("adalight") AdalightLightEffect = adalight_ns.class_( diff --git a/esphome/components/adalight/adalight_light_effect.cpp b/esphome/components/adalight/adalight_light_effect.cpp index bf6849acaf7..e3510d380eb 100644 --- a/esphome/components/adalight/adalight_light_effect.cpp +++ b/esphome/components/adalight/adalight_light_effect.cpp @@ -3,7 +3,7 @@ namespace esphome::adalight { -static const char *const TAG = "adalight_light_effect"; +ESPHOME_LOG_TAG(TAG, "adalight_light_effect"); static const uint32_t ADALIGHT_ACK_INTERVAL = 1000; static const uint32_t ADALIGHT_RECEIVE_TIMEOUT = 1000; diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py index c397e746b0f..6966a86f6b9 100644 --- a/esphome/components/adc/__init__.py +++ b/esphome/components/adc/__init__.py @@ -22,6 +22,7 @@ from esphome.core import CORE from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] +DOMAIN = "adc" adc_ns = cg.esphome_ns.namespace("adc") diff --git a/esphome/components/adc/adc_sensor_common.cpp b/esphome/components/adc/adc_sensor_common.cpp index 70211000c3d..dcb41382015 100644 --- a/esphome/components/adc/adc_sensor_common.cpp +++ b/esphome/components/adc/adc_sensor_common.cpp @@ -3,7 +3,7 @@ namespace esphome::adc { -static const char *const TAG = "adc"; +ESPHOME_LOG_TAG(TAG, "adc"); const LogString *sampling_mode_to_str(SamplingMode mode) { switch (mode) { diff --git a/esphome/components/adc/adc_sensor_esp32.cpp b/esphome/components/adc/adc_sensor_esp32.cpp index c9887cea7cd..6bab6fc4697 100644 --- a/esphome/components/adc/adc_sensor_esp32.cpp +++ b/esphome/components/adc/adc_sensor_esp32.cpp @@ -6,7 +6,7 @@ namespace esphome::adc { -static const char *const TAG = "adc"; +ESPHOME_LOG_TAG(TAG, "adc"); adc_oneshot_unit_handle_t ADCSensor::shared_adc_handles[2] = {nullptr, nullptr}; diff --git a/esphome/components/adc/adc_sensor_esp8266.cpp b/esphome/components/adc/adc_sensor_esp8266.cpp index 77a192e0254..db231f75984 100644 --- a/esphome/components/adc/adc_sensor_esp8266.cpp +++ b/esphome/components/adc/adc_sensor_esp8266.cpp @@ -13,7 +13,7 @@ ADC_MODE(ADC_VCC) namespace esphome::adc { -static const char *const TAG = "adc"; +ESPHOME_LOG_TAG(TAG, "adc"); void ADCSensor::setup() { #ifndef USE_ADC_SENSOR_VCC diff --git a/esphome/components/adc/adc_sensor_libretiny.cpp b/esphome/components/adc/adc_sensor_libretiny.cpp index dfa545b3951..2ea9da935fe 100644 --- a/esphome/components/adc/adc_sensor_libretiny.cpp +++ b/esphome/components/adc/adc_sensor_libretiny.cpp @@ -5,7 +5,7 @@ namespace esphome::adc { -static const char *const TAG = "adc"; +ESPHOME_LOG_TAG(TAG, "adc"); void ADCSensor::setup() { #ifndef USE_ADC_SENSOR_VCC diff --git a/esphome/components/adc/adc_sensor_rp2.cpp b/esphome/components/adc/adc_sensor_rp2.cpp index ce665e85015..5756ac038aa 100644 --- a/esphome/components/adc/adc_sensor_rp2.cpp +++ b/esphome/components/adc/adc_sensor_rp2.cpp @@ -17,7 +17,7 @@ namespace esphome::adc { -static const char *const TAG = "adc"; +ESPHOME_LOG_TAG(TAG, "adc"); // The on-die temperature sensor sits on the last ADC channel: input 4 on RP2040 // and RP2350A, but input 8 on RP2350B, which has eight external channels rather diff --git a/esphome/components/adc/adc_sensor_zephyr.cpp b/esphome/components/adc/adc_sensor_zephyr.cpp index bf45059740f..c2f4aedd497 100644 --- a/esphome/components/adc/adc_sensor_zephyr.cpp +++ b/esphome/components/adc/adc_sensor_zephyr.cpp @@ -7,7 +7,7 @@ namespace esphome::adc { -static const char *const TAG = "adc"; +ESPHOME_LOG_TAG(TAG, "adc"); void ADCSensor::setup() { if (!adc_is_ready_dt(this->channel_)) { diff --git a/esphome/components/adc128s102/__init__.py b/esphome/components/adc128s102/__init__.py index 684147752d4..125b4113c57 100644 --- a/esphome/components/adc128s102/__init__.py +++ b/esphome/components/adc128s102/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_ID from esphome.types import ConfigType DEPENDENCIES = ["spi"] +DOMAIN = "adc128s102" MULTI_CONF = True CODEOWNERS = ["@DeerMaximum"] diff --git a/esphome/components/adc128s102/adc128s102.cpp b/esphome/components/adc128s102/adc128s102.cpp index ef0db4730a1..bbe95d03bec 100644 --- a/esphome/components/adc128s102/adc128s102.cpp +++ b/esphome/components/adc128s102/adc128s102.cpp @@ -3,7 +3,7 @@ namespace esphome::adc128s102 { -static const char *const TAG = "adc128s102"; +ESPHOME_LOG_TAG(TAG, "adc128s102"); float ADC128S102::get_setup_priority() const { return setup_priority::HARDWARE; } diff --git a/esphome/components/adc128s102/sensor/adc128s102_sensor.cpp b/esphome/components/adc128s102/sensor/adc128s102_sensor.cpp index 01b6a3f2b83..3cc3f0c0561 100644 --- a/esphome/components/adc128s102/sensor/adc128s102_sensor.cpp +++ b/esphome/components/adc128s102/sensor/adc128s102_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::adc128s102 { -static const char *const TAG = "adc128s102.sensor"; +ESPHOME_LOG_TAG(TAG, "adc128s102.sensor"); ADC128S102Sensor::ADC128S102Sensor(uint8_t channel) : channel_(channel) {} diff --git a/esphome/components/addressable_light/__init__.py b/esphome/components/addressable_light/__init__.py index e69de29bb2d..28a33c66d84 100644 --- a/esphome/components/addressable_light/__init__.py +++ b/esphome/components/addressable_light/__init__.py @@ -0,0 +1 @@ +DOMAIN = "addressable_light" diff --git a/esphome/components/addressable_light/addressable_light_display.cpp b/esphome/components/addressable_light/addressable_light_display.cpp index 4cbcb3324be..6a3df025e62 100644 --- a/esphome/components/addressable_light/addressable_light_display.cpp +++ b/esphome/components/addressable_light/addressable_light_display.cpp @@ -3,7 +3,7 @@ namespace esphome::addressable_light { -static const char *const TAG = "addressable_light.display"; +ESPHOME_LOG_TAG(TAG, "addressable_light.display"); int AddressableLightDisplay::get_width_internal() { return this->width_; } int AddressableLightDisplay::get_height_internal() { return this->height_; } diff --git a/esphome/components/ade7880/__init__.py b/esphome/components/ade7880/__init__.py index e69de29bb2d..f3dd325f5cf 100644 --- a/esphome/components/ade7880/__init__.py +++ b/esphome/components/ade7880/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ade7880" diff --git a/esphome/components/ade7880/ade7880.cpp b/esphome/components/ade7880/ade7880.cpp index 0f4189ad90c..e131980ebb0 100644 --- a/esphome/components/ade7880/ade7880.cpp +++ b/esphome/components/ade7880/ade7880.cpp @@ -15,7 +15,7 @@ namespace esphome::ade7880 { -static const char *const TAG = "ade7880"; +ESPHOME_LOG_TAG(TAG, "ade7880"); void IRAM_ATTR ADE7880Store::gpio_intr(ADE7880Store *arg) { arg->reset_done = true; } diff --git a/esphome/components/ade7953/__init__.py b/esphome/components/ade7953/__init__.py index d3078a0b679..512ac1a3f34 100644 --- a/esphome/components/ade7953/__init__.py +++ b/esphome/components/ade7953/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@angelnu"] +DOMAIN = "ade7953" diff --git a/esphome/components/ade7953_base/__init__.py b/esphome/components/ade7953_base/__init__.py index 71250ac94e3..808de99984c 100644 --- a/esphome/components/ade7953_base/__init__.py +++ b/esphome/components/ade7953_base/__init__.py @@ -27,6 +27,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@angelnu"] +DOMAIN = "ade7953_base" CONF_CURRENT_A = "current_a" CONF_CURRENT_B = "current_b" diff --git a/esphome/components/ade7953_base/ade7953_base.cpp b/esphome/components/ade7953_base/ade7953_base.cpp index 1adf44f8f72..21a16c5081a 100644 --- a/esphome/components/ade7953_base/ade7953_base.cpp +++ b/esphome/components/ade7953_base/ade7953_base.cpp @@ -5,7 +5,7 @@ namespace esphome::ade7953_base { -static const char *const TAG = "ade7953"; +ESPHOME_LOG_TAG(TAG, "ade7953"); constexpr uint16_t CONFIG_DEFAULT = 0x8004u; constexpr uint16_t CONFIG_LOCK_BIT = 0x8000u; diff --git a/esphome/components/ade7953_i2c/__init__.py b/esphome/components/ade7953_i2c/__init__.py index d3078a0b679..691ceb02749 100644 --- a/esphome/components/ade7953_i2c/__init__.py +++ b/esphome/components/ade7953_i2c/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@angelnu"] +DOMAIN = "ade7953_i2c" diff --git a/esphome/components/ade7953_i2c/ade7953_i2c.cpp b/esphome/components/ade7953_i2c/ade7953_i2c.cpp index 252e55ee5c1..cc0fb7e80d8 100644 --- a/esphome/components/ade7953_i2c/ade7953_i2c.cpp +++ b/esphome/components/ade7953_i2c/ade7953_i2c.cpp @@ -4,7 +4,7 @@ namespace esphome::ade7953_i2c { -static const char *const TAG = "ade7953"; +ESPHOME_LOG_TAG(TAG, "ade7953"); void AdE7953I2c::dump_config() { ESP_LOGCONFIG(TAG, "ADE7953_i2c:"); diff --git a/esphome/components/ade7953_spi/__init__.py b/esphome/components/ade7953_spi/__init__.py index d3078a0b679..71ad260db2a 100644 --- a/esphome/components/ade7953_spi/__init__.py +++ b/esphome/components/ade7953_spi/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@angelnu"] +DOMAIN = "ade7953_spi" diff --git a/esphome/components/ade7953_spi/ade7953_spi.cpp b/esphome/components/ade7953_spi/ade7953_spi.cpp index c2d85231d6b..e7c7bac97fc 100644 --- a/esphome/components/ade7953_spi/ade7953_spi.cpp +++ b/esphome/components/ade7953_spi/ade7953_spi.cpp @@ -4,7 +4,7 @@ namespace esphome::ade7953_spi { -static const char *const TAG = "ade7953"; +ESPHOME_LOG_TAG(TAG, "ade7953"); // Datasheet requires at least 1.2µs after clearing CONFIG LOCK_BIT before raising CS constexpr uint8_t CONFIG_LOCK_SETTLE_US = 2; diff --git a/esphome/components/ads1115/__init__.py b/esphome/components/ads1115/__init__.py index b42ee918c57..1b8405322fa 100644 --- a/esphome/components/ads1115/__init__.py +++ b/esphome/components/ads1115/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_ID from esphome.types import ConfigType DEPENDENCIES = ["i2c"] +DOMAIN = "ads1115" MULTI_CONF = True ads1115_ns = cg.esphome_ns.namespace("ads1115") diff --git a/esphome/components/ads1115/ads1115.cpp b/esphome/components/ads1115/ads1115.cpp index 58704bf7c68..b75e1b73046 100644 --- a/esphome/components/ads1115/ads1115.cpp +++ b/esphome/components/ads1115/ads1115.cpp @@ -4,7 +4,7 @@ namespace esphome::ads1115 { -static const char *const TAG = "ads1115"; +ESPHOME_LOG_TAG(TAG, "ads1115"); static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00; static const uint8_t ADS1115_REGISTER_CONFIG = 0x01; diff --git a/esphome/components/ads1115/sensor/ads1115_sensor.cpp b/esphome/components/ads1115/sensor/ads1115_sensor.cpp index 8086d972310..776346171b7 100644 --- a/esphome/components/ads1115/sensor/ads1115_sensor.cpp +++ b/esphome/components/ads1115/sensor/ads1115_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::ads1115 { -static const char *const TAG = "ads1115.sensor"; +ESPHOME_LOG_TAG(TAG, "ads1115.sensor"); float ADS1115Sensor::sample() { return this->parent_->request_measurement(this->multiplexer_, this->gain_, this->resolution_, this->samplerate_); diff --git a/esphome/components/ads1118/__init__.py b/esphome/components/ads1118/__init__.py index 956b9a0c1fb..98129ecbea3 100644 --- a/esphome/components/ads1118/__init__.py +++ b/esphome/components/ads1118/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@solomondg1"] DEPENDENCIES = ["spi"] +DOMAIN = "ads1118" MULTI_CONF = True CONF_ADS1118_ID = "ads1118_id" diff --git a/esphome/components/ads1118/ads1118.cpp b/esphome/components/ads1118/ads1118.cpp index 0a07193bfe0..c21dc2d3f06 100644 --- a/esphome/components/ads1118/ads1118.cpp +++ b/esphome/components/ads1118/ads1118.cpp @@ -4,7 +4,7 @@ namespace esphome::ads1118 { -static const char *const TAG = "ads1118"; +ESPHOME_LOG_TAG(TAG, "ads1118"); static const uint8_t ADS1118_DATA_RATE_860_SPS = 0b111; void ADS1118::setup() { diff --git a/esphome/components/ads1118/sensor/ads1118_sensor.cpp b/esphome/components/ads1118/sensor/ads1118_sensor.cpp index 383a3d25fcb..2b3d68d1f40 100644 --- a/esphome/components/ads1118/sensor/ads1118_sensor.cpp +++ b/esphome/components/ads1118/sensor/ads1118_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::ads1118 { -static const char *const TAG = "ads1118.sensor"; +ESPHOME_LOG_TAG(TAG, "ads1118.sensor"); void ADS1118Sensor::dump_config() { LOG_SENSOR(" ", "ADS1118 Sensor", this); diff --git a/esphome/components/ags10/__init__.py b/esphome/components/ags10/__init__.py index 37f34d06dff..8d2bbd6fb4b 100644 --- a/esphome/components/ags10/__init__.py +++ b/esphome/components/ags10/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@mak-42"] +DOMAIN = "ags10" diff --git a/esphome/components/ags10/ags10.cpp b/esphome/components/ags10/ags10.cpp index 377677ccdb8..2db39a206d5 100644 --- a/esphome/components/ags10/ags10.cpp +++ b/esphome/components/ags10/ags10.cpp @@ -4,7 +4,7 @@ #include namespace esphome::ags10 { -static const char *const TAG = "ags10"; +ESPHOME_LOG_TAG(TAG, "ags10"); // Data acquisition. static const uint8_t REG_TVOC = 0x00; diff --git a/esphome/components/aht10/__init__.py b/esphome/components/aht10/__init__.py index e69de29bb2d..888d442fdf1 100644 --- a/esphome/components/aht10/__init__.py +++ b/esphome/components/aht10/__init__.py @@ -0,0 +1 @@ +DOMAIN = "aht10" diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp index cc90abfc3a6..83ab55e6576 100644 --- a/esphome/components/aht10/aht10.cpp +++ b/esphome/components/aht10/aht10.cpp @@ -19,7 +19,7 @@ namespace esphome::aht10 { -static const char *const TAG = "aht10"; +ESPHOME_LOG_TAG(TAG, "aht10"); static const uint8_t AHT10_INITIALIZE_CMD[] = {0xE1, 0x08, 0x00}; static const uint8_t AHT20_INITIALIZE_CMD[] = {0xBE, 0x08, 0x00}; static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00}; diff --git a/esphome/components/aic3204/__init__.py b/esphome/components/aic3204/__init__.py index e69de29bb2d..c7f7f00960b 100644 --- a/esphome/components/aic3204/__init__.py +++ b/esphome/components/aic3204/__init__.py @@ -0,0 +1 @@ +DOMAIN = "aic3204" diff --git a/esphome/components/aic3204/aic3204.cpp b/esphome/components/aic3204/aic3204.cpp index 0ba960fd702..892a6c56ac0 100644 --- a/esphome/components/aic3204/aic3204.cpp +++ b/esphome/components/aic3204/aic3204.cpp @@ -6,7 +6,7 @@ namespace esphome::aic3204 { -static const char *const TAG = "aic3204"; +ESPHOME_LOG_TAG(TAG, "aic3204"); #define ERROR_CHECK(err, msg) \ if (!(err)) { \ diff --git a/esphome/components/airthings_ble/__init__.py b/esphome/components/airthings_ble/__init__.py index 44534b80e94..48cebad5fbc 100644 --- a/esphome/components/airthings_ble/__init__.py +++ b/esphome/components/airthings_ble/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["ble_device_base"] CODEOWNERS = ["@jeromelaban"] +DOMAIN = "airthings_ble" airthings_ble_ns = cg.esphome_ns.namespace("airthings_ble") AirthingsListener = airthings_ble_ns.class_( diff --git a/esphome/components/airthings_ble/airthings_listener.cpp b/esphome/components/airthings_ble/airthings_listener.cpp index f2625a78325..5f0e2eb3ecf 100644 --- a/esphome/components/airthings_ble/airthings_listener.cpp +++ b/esphome/components/airthings_ble/airthings_listener.cpp @@ -4,7 +4,7 @@ namespace esphome::airthings_ble { -static const char *const TAG = "airthings_ble"; +ESPHOME_LOG_TAG(TAG, "airthings_ble"); bool AirthingsListener::parse_device(const ble_device_base::ESPBTDevice &device) { for (auto &it : device.get_manufacturer_datas()) { diff --git a/esphome/components/airthings_wave_base/__init__.py b/esphome/components/airthings_wave_base/__init__.py index 2cec58934d6..035142193a8 100644 --- a/esphome/components/airthings_wave_base/__init__.py +++ b/esphome/components/airthings_wave_base/__init__.py @@ -24,6 +24,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@ncareau", "@jeromelaban"] +DOMAIN = "airthings_wave_base" DEPENDENCIES = ["ble_client"] diff --git a/esphome/components/airthings_wave_base/airthings_wave_base.cpp b/esphome/components/airthings_wave_base/airthings_wave_base.cpp index 5fa59f22fd5..f4dfaaee326 100644 --- a/esphome/components/airthings_wave_base/airthings_wave_base.cpp +++ b/esphome/components/airthings_wave_base/airthings_wave_base.cpp @@ -8,7 +8,10 @@ namespace esphome::airthings_wave_base { -static const char *const TAG = "airthings_wave_base"; +ESPHOME_LOG_TAG(TAG, "airthings_wave_base"); + +static constexpr uint32_t BATTERY_READ_TIMEOUT_ID = 0; +static constexpr uint32_t RESPONSE_TIMEOUT_ID = 1; void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { @@ -181,7 +184,7 @@ void AirthingsWaveBase::read_battery_(uint8_t *raw_value, uint16_t value_len) { // read the battery again at the configured update interval if (this->battery_update_interval_ != this->update_interval_) { this->read_battery_next_update_ = false; - this->set_timeout("battery", this->battery_update_interval_, + this->set_timeout(BATTERY_READ_TIMEOUT_ID, this->battery_update_interval_, [this]() { this->read_battery_next_update_ = true; }); } } @@ -204,7 +207,7 @@ void AirthingsWaveBase::response_received_() { } void AirthingsWaveBase::set_response_timeout_() { - this->set_timeout("response_timeout", 30 * 1000, [this]() { + this->set_timeout(RESPONSE_TIMEOUT_ID, 30 * 1000, [this]() { this->responses_pending_ = 1; this->response_received_(); }); diff --git a/esphome/components/airthings_wave_mini/__init__.py b/esphome/components/airthings_wave_mini/__init__.py index 022f35b4cf6..64513a448d6 100644 --- a/esphome/components/airthings_wave_mini/__init__.py +++ b/esphome/components/airthings_wave_mini/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@ncareau"] +DOMAIN = "airthings_wave_mini" diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp index f487e9dbc08..54dbc21a6dc 100644 --- a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp @@ -4,7 +4,7 @@ namespace esphome::airthings_wave_mini { -static const char *const TAG = "airthings_wave_mini"; +ESPHOME_LOG_TAG(TAG, "airthings_wave_mini"); void AirthingsWaveMini::read_sensors(uint8_t *raw_value, uint16_t value_len) { auto *value = (WaveMiniReadings *) raw_value; diff --git a/esphome/components/airthings_wave_plus/__init__.py b/esphome/components/airthings_wave_plus/__init__.py index e26bfd471bb..904aa922ba7 100644 --- a/esphome/components/airthings_wave_plus/__init__.py +++ b/esphome/components/airthings_wave_plus/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@jeromelaban", "@precurse"] +DOMAIN = "airthings_wave_plus" diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index 80fe081b572..4b23cb685ca 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -4,7 +4,7 @@ namespace esphome::airthings_wave_plus { -static const char *const TAG = "airthings_wave_plus"; +ESPHOME_LOG_TAG(TAG, "airthings_wave_plus"); void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) { auto *value = (WavePlusReadings *) raw_value; diff --git a/esphome/components/alarm_control_panel/__init__.py b/esphome/components/alarm_control_panel/__init__.py index 78206ff3f8a..ac12835433d 100644 --- a/esphome/components/alarm_control_panel/__init__.py +++ b/esphome/components/alarm_control_panel/__init__.py @@ -21,6 +21,7 @@ from esphome.core.entity_helpers import ( from esphome.cpp_generator import MockObjClass CODEOWNERS = ["@grahambrown11", "@hwstar"] +DOMAIN = "alarm_control_panel" IS_PLATFORM_COMPONENT = True CONF_ON_TRIGGERED = "on_triggered" diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.cpp b/esphome/components/alarm_control_panel/alarm_control_panel.cpp index fc72c13ce30..6f23b1aca89 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel.cpp @@ -10,7 +10,7 @@ namespace esphome::alarm_control_panel { -static const char *const TAG = "alarm_control_panel"; +ESPHOME_LOG_TAG(TAG, "alarm_control_panel"); AlarmControlPanelCall AlarmControlPanel::make_call() { return AlarmControlPanelCall(this); } diff --git a/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp b/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp index 0c43cd555a3..667cae42a07 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel_call.cpp @@ -6,7 +6,7 @@ namespace esphome::alarm_control_panel { -static const char *const TAG = "alarm_control_panel"; +ESPHOME_LOG_TAG(TAG, "alarm_control_panel"); AlarmControlPanelCall::AlarmControlPanelCall(AlarmControlPanel *parent) : parent_(parent) {} diff --git a/esphome/components/alpha3/__init__.py b/esphome/components/alpha3/__init__.py index 7cd320c80fd..551edbeb7f1 100644 --- a/esphome/components/alpha3/__init__.py +++ b/esphome/components/alpha3/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@jan-hofmeier"] +DOMAIN = "alpha3" diff --git a/esphome/components/alpha3/alpha3.cpp b/esphome/components/alpha3/alpha3.cpp index 92b00d87cb8..ce81ca4025f 100644 --- a/esphome/components/alpha3/alpha3.cpp +++ b/esphome/components/alpha3/alpha3.cpp @@ -7,7 +7,7 @@ namespace esphome::alpha3 { -static const char *const TAG = "alpha3"; +ESPHOME_LOG_TAG(TAG, "alpha3"); void Alpha3::dump_config() { ESP_LOGCONFIG(TAG, "ALPHA3"); diff --git a/esphome/components/am2315c/__init__.py b/esphome/components/am2315c/__init__.py index 2398e4fa236..a0ffa96f5eb 100644 --- a/esphome/components/am2315c/__init__.py +++ b/esphome/components/am2315c/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@swoboda1337"] +DOMAIN = "am2315c" diff --git a/esphome/components/am2315c/am2315c.cpp b/esphome/components/am2315c/am2315c.cpp index 8980a8dfc3a..d34061889be 100644 --- a/esphome/components/am2315c/am2315c.cpp +++ b/esphome/components/am2315c/am2315c.cpp @@ -26,7 +26,7 @@ namespace esphome::am2315c { -static const char *const TAG = "am2315c"; +ESPHOME_LOG_TAG(TAG, "am2315c"); bool AM2315C::reset_register_(uint8_t reg) { // code based on demo code sent by www.aosong.com diff --git a/esphome/components/am2320/__init__.py b/esphome/components/am2320/__init__.py index e69de29bb2d..34e57d220cd 100644 --- a/esphome/components/am2320/__init__.py +++ b/esphome/components/am2320/__init__.py @@ -0,0 +1 @@ +DOMAIN = "am2320" diff --git a/esphome/components/am2320/am2320.cpp b/esphome/components/am2320/am2320.cpp index 5445ab38981..94053ab5dc8 100644 --- a/esphome/components/am2320/am2320.cpp +++ b/esphome/components/am2320/am2320.cpp @@ -10,7 +10,7 @@ namespace esphome::am2320 { -static const char *const TAG = "am2320"; +ESPHOME_LOG_TAG(TAG, "am2320"); void AM2320Component::update() { uint8_t data[8]; diff --git a/esphome/components/am43/__init__.py b/esphome/components/am43/__init__.py index f21a15ce0a2..fa3caae506a 100644 --- a/esphome/components/am43/__init__.py +++ b/esphome/components/am43/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@buxtronix"] +DOMAIN = "am43" diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp index 4b096983a44..6fb8c0c7eba 100644 --- a/esphome/components/am43/cover/am43_cover.cpp +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -5,7 +5,7 @@ namespace esphome::am43 { -static const char *const TAG = "am43_cover"; +ESPHOME_LOG_TAG(TAG, "am43_cover"); using namespace esphome::cover; diff --git a/esphome/components/am43/sensor/am43_sensor.cpp b/esphome/components/am43/sensor/am43_sensor.cpp index ddc3eadae9a..1a30af03aef 100644 --- a/esphome/components/am43/sensor/am43_sensor.cpp +++ b/esphome/components/am43/sensor/am43_sensor.cpp @@ -6,7 +6,7 @@ namespace esphome::am43 { -static const char *const TAG = "am43"; +ESPHOME_LOG_TAG(TAG, "am43"); void Am43::dump_config() { ESP_LOGCONFIG(TAG, "AM43"); diff --git a/esphome/components/analog_threshold/__init__.py b/esphome/components/analog_threshold/__init__.py index 9ae2df986db..c01f3b40e5f 100644 --- a/esphome/components/analog_threshold/__init__.py +++ b/esphome/components/analog_threshold/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@ianchi"] +DOMAIN = "analog_threshold" diff --git a/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp b/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp index d25c10021c0..c5061365a21 100644 --- a/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp +++ b/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::analog_threshold { -static const char *const TAG = "analog_threshold.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "analog_threshold.binary_sensor"); void AnalogThresholdBinarySensor::setup() { float sensor_value = this->sensor_->get_state(); diff --git a/esphome/components/anova/__init__.py b/esphome/components/anova/__init__.py index e69de29bb2d..4fdd8de72cc 100644 --- a/esphome/components/anova/__init__.py +++ b/esphome/components/anova/__init__.py @@ -0,0 +1 @@ +DOMAIN = "anova" diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index b0769bb622e..28e2da93e93 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -5,7 +5,7 @@ namespace esphome::anova { -static const char *const TAG = "anova"; +ESPHOME_LOG_TAG(TAG, "anova"); using namespace esphome::climate; diff --git a/esphome/components/apds9306/__init__.py b/esphome/components/apds9306/__init__.py index 3dc8fcf5ff5..b22aecf6d20 100644 --- a/esphome/components/apds9306/__init__.py +++ b/esphome/components/apds9306/__init__.py @@ -2,3 +2,4 @@ # https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf CODEOWNERS = ["@aodrenah"] +DOMAIN = "apds9306" diff --git a/esphome/components/apds9306/apds9306.cpp b/esphome/components/apds9306/apds9306.cpp index 57a502ca420..daf6bb6b338 100644 --- a/esphome/components/apds9306/apds9306.cpp +++ b/esphome/components/apds9306/apds9306.cpp @@ -7,7 +7,7 @@ namespace esphome::apds9306 { -static const char *const TAG = "apds9306"; +ESPHOME_LOG_TAG(TAG, "apds9306"); enum { // APDS9306 registers APDS9306_MAIN_CTRL = 0x00, diff --git a/esphome/components/apds9960/__init__.py b/esphome/components/apds9960/__init__.py index 7ac1e5eb326..8c19eec5ca1 100644 --- a/esphome/components/apds9960/__init__.py +++ b/esphome/components/apds9960/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_ID from esphome.types import ConfigType DEPENDENCIES = ["i2c"] +DOMAIN = "apds9960" MULTI_CONF = True CONF_APDS9960_ID = "apds9960_id" diff --git a/esphome/components/apds9960/apds9960.cpp b/esphome/components/apds9960/apds9960.cpp index da8029b4ee0..ef5d43e2cba 100644 --- a/esphome/components/apds9960/apds9960.cpp +++ b/esphome/components/apds9960/apds9960.cpp @@ -4,7 +4,7 @@ namespace esphome::apds9960 { -static const char *const TAG = "apds9960"; +ESPHOME_LOG_TAG(TAG, "apds9960"); #define APDS9960_ERROR_CHECK(func) \ if (!(func)) { \ diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 25ebc934586..f538c0b5e50 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -1,3 +1,4 @@ +from ipaddress import IPv4Address, IPv6Address import logging import re from typing import Any @@ -5,8 +6,9 @@ from typing import Any from esphome import automation from esphome.automation import Condition import esphome.codegen as cg -from esphome.components.const import CONF_DESCRIPTION +from esphome.components.const import CONF_DESCRIPTION, CONF_HOST from esphome.components.logger import request_log_listener +from esphome.components.network import DOMAIN as NETWORK_DOMAIN # ENCRYPTION_SCHEMA and validate_encryption_key are re-exported for external # components and downstream consumers that import them from api @@ -26,6 +28,8 @@ from esphome.const import ( CONF_CAPTURE_RESPONSE, CONF_DATA, CONF_DATA_TEMPLATE, + CONF_DELAY, + CONF_ENABLE_IPV6, CONF_ENCRYPTION, CONF_EVENT, CONF_ID, @@ -48,10 +52,14 @@ from esphome.const import ( CONF_VARIABLES, ) from esphome.core import CORE, ID, CoroPriority, EsphomeError, coroutine_with_priority -from esphome.cpp_generator import MockObj, TemplateArgsType -from esphome.helpers import fnv1_hash +from esphome.cpp_generator import Expression, MockObj, TemplateArgsType +import esphome.final_validate as fv +from esphome.helpers import cpp_string_escape, fnv1_hash +from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome.types import ConfigFragmentType, ConfigType +from . import wizard + # Compat alias: downstream consumers (e.g. device-builder) referenced the # schema by its old private name before it moved to the noise component _encryption_schema = encryption_schema @@ -86,6 +94,7 @@ APIServer = api_ns.class_("APIServer", cg.Component) HomeAssistantServiceCallAction = api_ns.class_( "HomeAssistantServiceCallAction", automation.Action ) +HomeAssistantField = api_ns.struct("HomeAssistantField") ActionResponse = api_ns.class_("ActionResponse") HomeAssistantActionResponseTrigger = api_ns.class_( "HomeAssistantActionResponseTrigger", automation.Trigger @@ -135,6 +144,7 @@ CONF_HOMEASSISTANT_SERVICES = "homeassistant_services" CONF_HOMEASSISTANT_STATES = "homeassistant_states" CONF_LISTEN_BACKLOG = "listen_backlog" CONF_MAX_SEND_QUEUE = "max_send_queue" +CONF_OUTGOING_CONNECTION = "outgoing_connection" CONF_STATE_SUBSCRIPTION_ONLY = "state_subscription_only" # Schema defaults that also match the C++ initializers in api_server.h; codegen @@ -142,6 +152,7 @@ CONF_STATE_SUBSCRIPTION_ONLY = "state_subscription_only" DEFAULT_PORT = 6053 DEFAULT_REBOOT_TIMEOUT = "15min" DEFAULT_BATCH_DELAY = "100ms" +DEFAULT_LISTEN_BACKLOG = 4 def _register_provisioning_source(config: ConfigType) -> ConfigType: @@ -292,9 +303,72 @@ def _consume_api_sockets(config: ConfigType) -> ConfigType: # (not max_connections, which is the upper limit rarely reached) socket.consume_sockets(3, "api")(config) socket.consume_sockets(1, "api", socket.SocketType.TCP_LISTEN)(config) + if CONF_OUTGOING_CONNECTION in config: + socket.consume_sockets(1, "api_outgoing_connection")(config) return config +def _validate_outgoing_connection(config: ConfigType) -> ConfigType: + if (outgoing := config.get(CONF_OUTGOING_CONNECTION)) is None: + return config + if CONF_ENCRYPTION not in config: + raise cv.Invalid( + "outgoing_connection requires 'encryption' so the peer is verified by key", + path=[CONF_OUTGOING_CONNECTION], + ) + # A device with no client reboots once reboot_timeout passes, so a delay that + # reaches it would reboot the device before it ever dials + reboot_timeout = config[CONF_REBOOT_TIMEOUT] + delay = outgoing[CONF_DELAY] + if reboot_timeout.total_milliseconds and delay >= reboot_timeout: + raise cv.Invalid( + f"delay must be shorter than reboot_timeout ({reboot_timeout}), " + "otherwise the device reboots before it dials", + path=[CONF_OUTGOING_CONNECTION, CONF_DELAY], + ) + return config + + +def _validate_outgoing_host(value: str) -> IPv4Address | IPv6Address: + """Only accept an address the device itself can parse. + + Python accepts a scope id, which neither `inet_pton` nor lwIP's `inet6_aton` + takes, and a v4-mapped address is dialed as plain IPv4, needing no IPv6 build. + """ + address = cv.ipaddress(value) + if isinstance(address, IPv6Address): + if address.scope_id is not None: + raise cv.Invalid( + f"{value} carries a scope id, which the device cannot parse; " + "give the address without the '%' part" + ) + if (mapped := address.ipv4_mapped) is not None: + return mapped + return address + + +_OUTGOING_CONNECTION_SCHEMA = cv.Schema( + { + cv.Optional(CONF_HOST): _validate_outgoing_host, + cv.Optional(CONF_PORT, default=6054): cv.port, + # Bounded against reboot_timeout in _validate_outgoing_connection + cv.Optional(CONF_DELAY, default="60s"): cv.positive_time_period_milliseconds, + } +) + + +@schema_extractor("schema") +def _outgoing_connection_schema(config: ConfigType | None) -> ConfigType: + # A bare `outgoing_connection:` block is valid; without a host the device + # dials the remembered last dial-back client + if config is SCHEMA_EXTRACT: + # Let the language-schema dumper walk host, port and delay + return _OUTGOING_CONNECTION_SCHEMA + if config is None: + config = {} + return _OUTGOING_CONNECTION_SCHEMA(config) + + CONFIG_SCHEMA = cv.All( cv.Schema( { @@ -319,6 +393,8 @@ CONFIG_SCHEMA = cv.All( ): ACTIONS_SCHEMA, cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA, cv.Optional(CONF_ENCRYPTION): encryption_schema, + cv.Optional(wizard.CONF_WIZARD): wizard.WIZARD_SCHEMA, + cv.Optional(CONF_OUTGOING_CONNECTION): _outgoing_connection_schema, cv.Optional(CONF_BATCH_DELAY, default=DEFAULT_BATCH_DELAY): cv.All( cv.positive_time_period_milliseconds, cv.Range(max=cv.TimePeriod(milliseconds=65535)), @@ -374,6 +450,7 @@ CONFIG_SCHEMA = cv.All( } ).extend(cv.COMPONENT_SCHEMA), cv.rename_key(CONF_SERVICES, CONF_ACTIONS), + _validate_outgoing_connection, _consume_api_sockets, _register_provisioning_source, ) @@ -430,7 +507,34 @@ def _validate_esp8266_action_strings(config: ConfigType) -> ConfigType: return config -FINAL_VALIDATE_SCHEMA = _validate_esp8266_action_strings +def _validate_outgoing_host_ipv6(config: ConfigType) -> ConfigType: + """An IPv6 host can never be parsed, so never dialed, without IPv6.""" + if ( + (outgoing := config.get(CONF_OUTGOING_CONNECTION)) is None + or (host := outgoing.get(CONF_HOST)) is None + or host.version != 6 + ): + return config + network_conf = fv.full_config.get().get(NETWORK_DOMAIN) or {} + if not network_conf.get(CONF_ENABLE_IPV6): + raise cv.Invalid( + "outgoing_connection host is an IPv6 address but IPv6 is not " + "enabled; set 'network: enable_ipv6: true'", + path=[CONF_OUTGOING_CONNECTION, CONF_HOST], + ) + return config + + +def _validate_wizard(config: ConfigType) -> ConfigType: + wizard.final_validate(config) + return config + + +FINAL_VALIDATE_SCHEMA = cv.All( + _validate_esp8266_action_strings, + _validate_outgoing_host_ipv6, + _validate_wizard, +) def _add_action_strings( @@ -477,8 +581,10 @@ async def to_code(config: ConfigType) -> None: cg.add(var.set_reboot_timeout(reboot_timeout)) if (batch_delay := config[CONF_BATCH_DELAY]) != cv.time_period(DEFAULT_BATCH_DELAY): cg.add(var.set_batch_delay(batch_delay)) - if CONF_LISTEN_BACKLOG in config: - cg.add(var.set_listen_backlog(config[CONF_LISTEN_BACKLOG])) + if ( + listen_backlog := config.get(CONF_LISTEN_BACKLOG) + ) is not None and listen_backlog != DEFAULT_LISTEN_BACKLOG: + cg.add(var.set_listen_backlog(listen_backlog)) cg.add_define("MAX_API_CONNECTIONS", config[CONF_MAX_CONNECTIONS]) cg.add_define("API_MAX_SEND_QUEUE", config[CONF_MAX_SEND_QUEUE]) @@ -583,6 +689,9 @@ async def to_code(config: ConfigType) -> None: # Stack buffer that list-entities copies PROGMEM strings into, sized for the largest action cg.add_define("API_USER_ACTION_STRINGS_SCRATCH_SIZE", max(scratch_size, 1)) + if (wizard_config := config.get(wizard.CONF_WIZARD)) is not None: + await wizard.to_code(wizard_config) + if CONF_ON_CLIENT_CONNECTED in config: cg.add_define("USE_API_CLIENT_CONNECTED_TRIGGER") await automation.build_automation( @@ -618,6 +727,13 @@ async def to_code(config: ConfigType) -> None: else: cg.add_define("USE_API_PLAINTEXT") + if (outgoing := config.get(CONF_OUTGOING_CONNECTION)) is not None: + cg.add_define("USE_API_OUTGOING_CONNECTION") + if (host := outgoing.get(CONF_HOST)) is not None: + cg.add_define("API_OUTGOING_CONNECTION_HOST", str(host)) + cg.add_define("API_OUTGOING_CONNECTION_PORT", outgoing[CONF_PORT]) + cg.add_define("API_OUTGOING_CONNECTION_DELAY", outgoing[CONF_DELAY]) + cg.add_define("USE_API") cg.add_global(api_ns.using) @@ -658,6 +774,11 @@ VARIABLES_SCHEMA = cv.Schema( {cv.string: cv.All(_coerce_implicit_lambda, cv.templatable(cv.string_strict))} ) +# The action stores each map's entry count in a uint8_t +_FIELD_MAP_MAX = 255 +DATA_FIELDS_SCHEMA = cv.All(KEY_VALUE_SCHEMA, cv.Length(max=_FIELD_MAP_MAX)) +VARIABLES_FIELDS_SCHEMA = cv.All(VARIABLES_SCHEMA, cv.Length(max=_FIELD_MAP_MAX)) + def _validate_response_config(config: ConfigType) -> ConfigType: # Validate dependencies: @@ -692,9 +813,9 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All( cv.Exclusive(CONF_ACTION, group_of_exclusion=CONF_ACTION): cv.templatable( cv.string ), - cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, - cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, - cv.Optional(CONF_VARIABLES, default={}): VARIABLES_SCHEMA, + cv.Optional(CONF_DATA, default={}): DATA_FIELDS_SCHEMA, + cv.Optional(CONF_DATA_TEMPLATE, default={}): DATA_FIELDS_SCHEMA, + cv.Optional(CONF_VARIABLES, default={}): VARIABLES_FIELDS_SCHEMA, cv.Optional(CONF_RESPONSE_TEMPLATE): cv.templatable(cv.string), cv.Optional(CONF_CAPTURE_RESPONSE, default=False): cv.boolean, cv.Optional(CONF_ON_SUCCESS): automation.validate_automation(single=True), @@ -707,6 +828,62 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All( ) +def _field_string(value: str) -> Expression: + # ESP8266 can only keep a string in flash as its own PROGMEM array + literal = cg.RawExpression(cpp_string_escape(value)) + if CORE.is_esp8266: + return cg.shared_progmem_array("ha_field_str", cg.char, literal) + return literal + + +async def _new_service_call_action( + server_id: ID, + action_id: ID, + template_arg: cg.TemplateArguments, + args: TemplateArgsType, + is_event: bool, + service: Any, + data: dict[str, Any], + data_template: dict[str, Any], + variables: dict[str, Any], +) -> MockObj: + """Create the action with its name and fields in one shared flash table.""" + cg.add_define("USE_API_HOMEASSISTANT_SERVICES") + serv = await cg.get_variable(server_id) + field_type = HomeAssistantField.template(template_arg) + groups = [data, data_template, variables] + # A lambda may keep static state, so a table with lambdas is never shared + has_lambda = cg.is_template(service) or any( + cg.is_template(value) for group in groups for value in group.values() + ) + + async def field(key: str | None, value: Any, output_type: Any = None) -> Expression: + key_exp = cg.nullptr if key is None else _field_string(key) + if cg.is_template(value): + # output_type=None lets lambdas return numbers or char pointers; C++ converts them + lam = await cg.process_lambda(value, args, return_type=output_type) + return cg.RawExpression(f"{field_type}::from_lambda({key_exp}, {lam})") + return cg.ArrayInitializer(key_exp, _field_string(value), cg.nullptr) + + entries = [await field(None, service, cg.std_string)] + for group in groups: + for key, value in group.items(): + entries.append(await field(key, value)) + table = cg.shared_progmem_array( + "ha_action_fields", + field_type, + cg.ArrayInitializer(*entries, multiline=True), + share=not has_lambda, + ) + return cg.new_Pvariable( + action_id, template_arg, serv, is_event, table, *(len(g) for g in groups) + ) + + +def _service_call_fields(config: ConfigType) -> tuple[dict[str, Any], ...]: + return config[CONF_DATA], config[CONF_DATA_TEMPLATE], config[CONF_VARIABLES] + + # synchronous=False: when on_success/on_error is configured, play() stores the # trigger args until the HomeassistantActionResponse arrives, so non-owning args # (StringRef into the API receive buffer) must not be used. @@ -728,36 +905,15 @@ async def homeassistant_service_to_code( template_arg: cg.TemplateArguments, args: TemplateArgsType, ) -> MockObj: - cg.add_define("USE_API_HOMEASSISTANT_SERVICES") - serv = await cg.get_variable(config[CONF_ID]) - var = cg.new_Pvariable(action_id, template_arg, serv, False) - templ = await cg.templatable(config[CONF_ACTION], args, cg.std_string) - cg.add(var.set_service(templ)) - - # Initialize FixedVectors with exact sizes from config - cg.add(var.init_data(len(config[CONF_DATA]))) - for key, value in config[CONF_DATA].items(): - # output_type=None because lambdas can return non-string types (int, - # float, char*) that TemplatableStringValue converts via to_string. - # Static strings are manually wrapped for PROGMEM on ESP8266. - templ = await cg.templatable(value, args, None) - if isinstance(templ, str): - templ = cg.FlashStringLiteral(templ) - cg.add(var.add_data(cg.FlashStringLiteral(key), templ)) - - cg.add(var.init_data_template(len(config[CONF_DATA_TEMPLATE]))) - for key, value in config[CONF_DATA_TEMPLATE].items(): - templ = await cg.templatable(value, args, None) - if isinstance(templ, str): - templ = cg.FlashStringLiteral(templ) - cg.add(var.add_data_template(cg.FlashStringLiteral(key), templ)) - - cg.add(var.init_variables(len(config[CONF_VARIABLES]))) - for key, value in config[CONF_VARIABLES].items(): - templ = await cg.templatable(value, args, None) - if isinstance(templ, str): - templ = cg.FlashStringLiteral(templ) - cg.add(var.add_variable(cg.FlashStringLiteral(key), templ)) + var = await _new_service_call_action( + config[CONF_ID], + action_id, + template_arg, + args, + False, + config[CONF_ACTION], + *_service_call_fields(config), + ) if on_error := config.get(CONF_ON_ERROR): cg.add_define("USE_API_HOMEASSISTANT_ACTION_RESPONSES") @@ -809,9 +965,9 @@ HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema( { cv.GenerateID(): cv.use_id(APIServer), cv.Required(CONF_EVENT): validate_homeassistant_event, - cv.Optional(CONF_DATA, default={}): KEY_VALUE_SCHEMA, - cv.Optional(CONF_DATA_TEMPLATE, default={}): KEY_VALUE_SCHEMA, - cv.Optional(CONF_VARIABLES, default={}): VARIABLES_SCHEMA, + cv.Optional(CONF_DATA, default={}): DATA_FIELDS_SCHEMA, + cv.Optional(CONF_DATA_TEMPLATE, default={}): DATA_FIELDS_SCHEMA, + cv.Optional(CONF_VARIABLES, default={}): VARIABLES_FIELDS_SCHEMA, } ) @@ -830,38 +986,15 @@ async def homeassistant_event_to_code( template_arg: cg.TemplateArguments, args: TemplateArgsType, ) -> MockObj: - cg.add_define("USE_API_HOMEASSISTANT_SERVICES") - serv = await cg.get_variable(config[CONF_ID]) - var = cg.new_Pvariable(action_id, template_arg, serv, True) - templ = await cg.templatable(config[CONF_EVENT], args, cg.std_string) - cg.add(var.set_service(templ)) - - # Initialize FixedVectors with exact sizes from config - cg.add(var.init_data(len(config[CONF_DATA]))) - for key, value in config[CONF_DATA].items(): - # output_type=None because lambdas can return non-string types (int, - # float, char*) that TemplatableStringValue converts via to_string. - # Static strings are manually wrapped for PROGMEM on ESP8266. - templ = await cg.templatable(value, args, None) - if isinstance(templ, str): - templ = cg.FlashStringLiteral(templ) - cg.add(var.add_data(cg.FlashStringLiteral(key), templ)) - - cg.add(var.init_data_template(len(config[CONF_DATA_TEMPLATE]))) - for key, value in config[CONF_DATA_TEMPLATE].items(): - templ = await cg.templatable(value, args, None) - if isinstance(templ, str): - templ = cg.FlashStringLiteral(templ) - cg.add(var.add_data_template(cg.FlashStringLiteral(key), templ)) - - cg.add(var.init_variables(len(config[CONF_VARIABLES]))) - for key, value in config[CONF_VARIABLES].items(): - templ = await cg.templatable(value, args, None) - if isinstance(templ, str): - templ = cg.FlashStringLiteral(templ) - cg.add(var.add_variable(cg.FlashStringLiteral(key), templ)) - - return var + return await _new_service_call_action( + config[CONF_ID], + action_id, + template_arg, + args, + True, + config[CONF_EVENT], + *_service_call_fields(config), + ) HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value( @@ -885,15 +1018,17 @@ async def homeassistant_tag_scanned_to_code( template_arg: cg.TemplateArguments, args: TemplateArgsType, ) -> MockObj: - cg.add_define("USE_API_HOMEASSISTANT_SERVICES") - serv = await cg.get_variable(config[CONF_ID]) - var = cg.new_Pvariable(action_id, template_arg, serv, True) - cg.add(var.set_service(cg.FlashStringLiteral("esphome.tag_scanned"))) - # Initialize FixedVector with exact size (1 data field) - cg.add(var.init_data(1)) - templ = await cg.templatable(config[CONF_TAG], args, cg.std_string) - cg.add(var.add_data(cg.FlashStringLiteral("tag_id"), templ)) - return var + return await _new_service_call_action( + config[CONF_ID], + action_id, + template_arg, + args, + True, + "esphome.tag_scanned", + {"tag_id": config[CONF_TAG]}, + {}, + {}, + ) CONF_SUCCESS = "success" @@ -1004,6 +1139,8 @@ _define_filter = filter_source_files_from_defines( "user_services.cpp": "USE_API_USER_DEFINED_ACTIONS", "api_frame_helper_noise.cpp": "USE_API_NOISE", "api_frame_helper_plaintext.cpp": "USE_API_PLAINTEXT", + "api_wizard.cpp": "USE_API_WIZARD", + "api_outgoing_connection.cpp": "USE_API_OUTGOING_CONNECTION", } ) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 687dc1ca951..d4ae82fd9f1 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -20,6 +20,8 @@ service APIConnection { option (needs_authentication) = false; } rpc device_capabilities (DeviceCapabilitiesRequest) returns (DeviceCapabilitiesResponse) {} + rpc device_wizard (DeviceWizardRequest) returns (DeviceWizardResponse) {} + rpc wizard_input_set (WizardInputSetRequest) returns (void) {} rpc list_entities (ListEntitiesRequest) returns (void) {} rpc subscribe_states (SubscribeStatesRequest) returns (void) {} rpc subscribe_logs (SubscribeLogsRequest) returns (void) {} @@ -76,8 +78,11 @@ service APIConnection { rpc serial_proxy_write(SerialProxyWriteRequest) returns (void) {} rpc serial_proxy_set_modem_pins(SerialProxySetModemPinsRequest) returns (void) {} rpc serial_proxy_get_modem_pins(SerialProxyGetModemPinsRequest) returns (void) {} + rpc subscribe_serial_proxy_identity(SubscribeSerialProxyIdentityRequest) returns (void) {} rpc serial_proxy_request(SerialProxyRequest) returns (void) {} rpc serial_proxy_set_mode(SerialProxySetModeRequest) returns (void) {} + + rpc sendspin_pairing_token(SendspinPairingTokenRequest) returns (SendspinPairingTokenResponse) {} } @@ -113,6 +118,11 @@ message HelloRequest { string client_info = 1; uint32 api_version_major = 2; uint32 api_version_minor = 3; + + // Set by clients that can accept connections the device opens to them + // (see api: outgoing_connection:). The device remembers this client's + // address as the target to dial when no such client is connected. + bool outgoing_connection_target = 4 [(field_ifdef) = "USE_API_OUTGOING_CONNECTION"]; } // Confirmation of successful connection request. @@ -228,6 +238,7 @@ enum SerialProxyPortType { SERIAL_PROXY_PORT_TYPE_TTL = 0; SERIAL_PROXY_PORT_TYPE_RS232 = 1; SERIAL_PROXY_PORT_TYPE_RS485 = 2; + SERIAL_PROXY_PORT_TYPE_USB_SERIAL = 3; // since API 1.18 } message SerialProxyInfo { @@ -332,6 +343,10 @@ message DeviceInfoResponse { // all-zeros PSK, so the api encryption key can be provisioned without being // sent in plaintext (protects against passive sniffing, not active MITM) bool api_encryption_provisionable = 26 [(field_ifdef) = "USE_API_NOISE"]; + + // Device is built with the api outgoing_connection option and can open + // the TCP connection to a dial-back target itself + bool api_outgoing_connection_supported = 27 [(field_ifdef) = "USE_API_OUTGOING_CONNECTION"]; } // ==================== DEVICE CAPABILITIES ==================== @@ -380,6 +395,16 @@ message ZWaveProxyCapabilities { uint32 home_id = 2; } +message WizardCapabilities { + // True when the device has a wizard configured, so DeviceWizardRequest will be answered + bool configured = 1; +} + +message SendspinCapabilities { + // Bitmask of the Sendspin features this device supports + uint32 feature_flags = 1; +} + message DeviceCapabilitiesResponse { option (id) = 150; option (source) = SOURCE_SERVER; @@ -389,6 +414,89 @@ message DeviceCapabilitiesResponse { ZWaveProxyCapabilities zwave_proxy = 3 [(field_ifdef) = "USE_ZWAVE_PROXY"]; repeated SerialProxyInfo serial_proxies = 4 [(field_ifdef) = "USE_SERIAL_PROXY", (fixed_array_size_define) = "SERIAL_PROXY_COUNT"]; + WizardCapabilities wizard = 5 [(field_ifdef) = "USE_API_WIZARD"]; + SendspinCapabilities sendspin = 6 [(field_ifdef) = "USE_SENDSPIN"]; +} + +// ==================== DEVICE WIZARD ==================== + +// Asks the device for the onboarding wizard that Home Assistant presents when the +// device is added. +// +// The wizard is read once, when the device is added, whereas capabilities are read +// on every connect, so it is a separate message rather than part of +// DeviceCapabilitiesResponse. It is only served on an authenticated connection. +// +// A device without a wizard does not have this message at all and never answers it, +// as a device ignores messages it does not know. Clients therefore check +// DeviceCapabilitiesResponse.wizard.configured before sending it. +message DeviceWizardRequest { + option (id) = 156; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_API_WIZARD"; + // Empty +} + +// The wizard, as UTF-8 JSON compressed into a single zstd frame. The device builds it +// when it is compiled and sends it unchanged, so its size varies with the wizard and +// its format can grow without new fields here. Clients decompress it, then read: +// +// {"version": 1, +// "pages": [{"title": "...", "description": "...", +// "entities": [{"key": 123, "device_id": 456, "description": "..."}], +// "inputs": [{"key": 789, "description": "...", +// "entity_filters": [{"integration": "...", "domain": ["..."], +// "device_class": ["..."], "supported_features": ["..."]}]}]}]} +// +// Values that are empty or unset are left out, and so are empty lists. Strings may be +// Home Assistant translation placeholders such as "[%key:component::domain::name%]", +// passed through verbatim. The pages are shown in the order listed. A client that does +// not know the version must not use the wizard. +// +// An entity is an entity of this device that the page shows to the user (for example to +// turn it on or off). Its key is the key ListEntitiesResponse sends for it. Its +// device_id is the device_id ListEntitiesResponse sends, and is left out when that is 0. +// +// An input is filled in by the user with the id of a Home Assistant entity. It either +// stands for one entity of the device's homeassistant platform (sensor, binary_sensor, +// text_sensor, number, switch, text, select or button) that sets no entity_id of its own, +// as Home Assistant supplies it, or it is a standalone input that only stores the entity +// id for the device's own automations. Its key is the FNV-1 hash +// of the input's ESPHome id, not an entity key. The client sends the chosen entity id to +// the device with a WizardInputSetRequest. entity_filters says which Home Assistant +// entities the user may choose, and mirrors Home Assistant's EntityFilterSelectorConfig: +// an entity matches a filter when it satisfies every field that is set, and an entity +// that matches any one of the filters is accepted. +// +// The device keeps the entity id of an input in RAM only and does not store it across +// restarts. The client therefore stores the choice itself and, after every connection +// and before it sends SubscribeHomeAssistantStatesRequest, sends a WizardInputSetRequest +// for each input. It sends one again whenever the user changes the choice. +message DeviceWizardResponse { + option (id) = 157; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_API_WIZARD"; + + // The data is in flash, and the device sends it from there + bytes data = 1 [(pointer_to_buffer) = true, (progmem) = true]; +} + +// Sets the entity id of a wizard input (see WizardInputField.key). Clients send +// this only when DeviceCapabilitiesResponse.wizard.configured is set. There is no +// reply. The device ignores a request whose key matches no input or whose entity_id +// is empty, longer than 255 bytes or has no '.'. Otherwise it keeps the id in RAM, +// not across restarts, and when the input belongs to a homeassistant platform entity +// it subscribes to that entity's state. Clients send this for every input right +// after connecting, before SubscribeHomeAssistantStatesRequest. A request that +// arrives later still works: the device then sends the affected state subscriptions +// again. +message WizardInputSetRequest { + option (id) = 158; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_API_WIZARD_INPUTS"; + + fixed32 key = 1; + string entity_id = 2 [(max_data_length) = 255]; } message ListEntitiesRequest { @@ -652,7 +760,7 @@ message ListEntitiesLightResponse { bool legacy_supports_color_temperature = 8 [deprecated=true]; float min_mireds = 9; float max_mireds = 10; - repeated string effects = 11 [(container_pointer_no_template) = "FixedVector"]; + repeated string effects = 11 [(container_pointer_no_template) = "light::LightEffectNames"]; bool disabled_by_default = 13; string icon = 14 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; EntityCategory entity_category = 15; @@ -1439,7 +1547,7 @@ message ListEntitiesSelectResponse { reserved 4; // Deprecated: was string unique_id string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63]; - repeated string options = 6 [(container_pointer_no_template) = "FixedVector"]; + repeated string options = 6 [(container_pointer_no_template) = "std::span"]; bool disabled_by_default = 7; EntityCategory entity_category = 8; uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"]; @@ -2683,7 +2791,7 @@ message ListEntitiesInfraredResponse { message InfraredRFTransmitRawTimingsRequest { option (id) = 136; option (source) = SOURCE_CLIENT; - option (ifdef) = "USE_IR_RF || USE_RADIO_FREQUENCY"; + option (ifdef) = "USE_IR_RF"; uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"]; fixed32 key = 2 [(force) = true]; // Key identifying the transmitter instance @@ -2697,7 +2805,7 @@ message InfraredRFTransmitRawTimingsRequest { message InfraredRFReceiveEvent { option (id) = 137; option (source) = SOURCE_SERVER; - option (ifdef) = "USE_IR_RF || USE_RADIO_FREQUENCY"; + option (ifdef) = "USE_IR_RF"; option (no_delay) = true; option (speed_optimized) = true; @@ -2706,6 +2814,28 @@ message InfraredRFReceiveEvent { repeated sint32 timings = 3 [packed = true, (container_pointer_no_template) = "std::vector"]; // Raw timings in microseconds (zigzag-encoded): alternating mark/space periods } +// Sent only to the client that issued an InfraredRFTransmitRawTimingsRequest, once the +// transmitter reports that the transmission (all repeats) has finished, or immediately with +// success=false if it was refused before reaching the transmitter (unknown key, no transmitter, +// no or invalid timings) or the transmitter could not send it (not set up, hardware error). +// success=false also answers a request superseded by a newer request on the same entity, from +// any client, and a transmit that reported nothing within 30 s past its expected air time, so a +// client should not retry on it blindly. The device serializes transmits per transmitter and +// several entities may share one, so a client should keep at most one transmit outstanding per +// device, not per entity. A reply for an unknown key or a refused request is sent once and can be lost +// when the device's send buffer is full, so a client should also stop waiting on its own after the +// expected air time plus a margin. Lets clients pace requests instead of estimating durations (since API 1.18) +message InfraredRFTransmitCompleteResponse { + option (id) = 153; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_IR_RF"; + option (no_delay) = true; + + uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"]; + fixed32 key = 2 [(force) = true]; // Key of the transmitter entity from the request + bool success = 3; // false if the request was refused, not sent, superseded, or never reported +} + // ==================== RADIO FREQUENCY ==================== // Lists available radio frequency entity instances @@ -2876,6 +3006,56 @@ message SerialProxySetModeRequest { SerialProxyMode mode = 2; } +// Subscribe to the identity of every serial proxy port. The device answers with one +// SerialProxyIdentity per port, then sends another whenever a port's identity changes, +// for the life of the connection (since API 1.18). +message SubscribeSerialProxyIdentityRequest { + option (id) = 154; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_SERIAL_PROXY"; +} + +// Where a port's identity comes from +enum SerialProxyIdentitySource { + SERIAL_PROXY_IDENTITY_SOURCE_NONE = 0; // The port carries no identity + SERIAL_PROXY_IDENTITY_SOURCE_CONFIGURED = 1; // Reserved for identities stated in the device configuration; not sent yet + SERIAL_PROXY_IDENTITY_SOURCE_USB = 2; // Read from the descriptors of the USB device behind the port; + // changes when a device is attached or removed +} + +enum SerialProxyIdentityFlag { + SERIAL_PROXY_IDENTITY_FLAG_NONE = 0; + SERIAL_PROXY_IDENTITY_FLAG_CONNECTED = 1; // The backend believes the device is present and usable on this port + SERIAL_PROXY_IDENTITY_FLAG_ERROR = 2; // The USB host stack refused the descriptor query; the strings and + // IDs below are empty +} + +// The descriptor fields of a USB device as seen by the host stack +message UsbDeviceDescriptor { + uint32 vendor_id = 1; + uint32 product_id = 2; + uint32 bcd_device = 3; + uint32 interface_number = 4; // bInterfaceNumber the host driver binds to +} + +// The identity of the device behind a port. Identifies one physical device among others of +// the same kind, so a client matches on manufacturer and product regardless of source. The +// strings are empty for source NONE, and for source USB while nothing is connected +// (since API 1.18). +message SerialProxyIdentity { + option (id) = 155; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_SERIAL_PROXY"; + + uint32 instance = 1; + SerialProxyIdentitySource source = 2; + uint32 flags = 3; // Bitmask of SerialProxyIdentityFlag + string manufacturer = 4; + string product = 5; + string serial_number = 6; + UsbDeviceDescriptor usb = 7; // Only sent for source USB +} + // ==================== BLUETOOTH CONNECTION PARAMS ==================== message BluetoothSetConnectionParamsRequest { option (id) = 145; @@ -2897,3 +3077,34 @@ message BluetoothSetConnectionParamsResponse { uint64 address = 1; int32 error = 2; } + +// ==================== SENDSPIN ==================== +enum SendspinPairingTokenStatus { + // The Sendspin client has not started yet, so the token does not exist yet. Ask again later. + SENDSPIN_PAIRING_TOKEN_STATUS_NOT_READY = 0; + SENDSPIN_PAIRING_TOKEN_STATUS_OK = 1; + // The device has no API encryption key, so it will not send the token. Ask again once a key is set. + SENDSPIN_PAIRING_TOKEN_STATUS_ENCRYPTION_REQUIRED = 2; + // Sendspin is turned off and has not started since boot, so the token does not exist yet. + SENDSPIN_PAIRING_TOKEN_STATUS_DISABLED = 3; + // The Sendspin client failed to start, and will not start until the device reboots. + SENDSPIN_PAIRING_TOKEN_STATUS_FAILED = 4; +} + +// Requests the Sendspin pairing token, which a Sendspin server uses to pair with this device +message SendspinPairingTokenRequest { + option (id) = 159; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_SENDSPIN"; + // Empty +} + +message SendspinPairingTokenResponse { + option (id) = 160; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_SENDSPIN"; + + SendspinPairingTokenStatus status = 1; + // The pairing token (Sendspin pairing.md "Pairing Token"). Set only when status is OK. + string token = 2 [(max_data_length) = 127]; +} diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 0207bc14b9f..fd51aae1be8 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -48,6 +48,9 @@ #ifdef USE_ZWAVE_PROXY #include "esphome/components/zwave_proxy/zwave_proxy.h" #endif +#if defined(USE_SENDSPIN) && defined(USE_ESP_IDF) +#include "esphome/components/sendspin/sendspin_hub.h" +#endif #ifdef USE_WATER_HEATER #include "esphome/components/water_heater/water_heater.h" #endif @@ -88,7 +91,7 @@ static_assert(sizeof(ESPHOME_VERSION) - 1 <= 32, "Update max_data_length for esp static_assert(ESPHOME_DEVICE_NAME_MAX_LEN <= 31, "Update max_data_length for name in api.proto"); static_assert(ESPHOME_FRIENDLY_NAME_MAX_LEN <= 120, "Update max_data_length for friendly_name in api.proto"); -static const char *const TAG = "api.connection"; +ESPHOME_LOG_TAG(TAG, "api.connection"); #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN void log_dropped_message(const char *tag, int line, const LogString *what) { @@ -198,6 +201,17 @@ APIConnection::~APIConnection() { proxy->serial_proxy_request(this, enums::SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE); } } +#endif + // entities holding a transmit reply for this client must not answer into a freed connection +#ifdef USE_INFRARED + for (auto *infrared : App.get_infrareds()) { + infrared->on_api_connection_closed(this); + } +#endif +#ifdef USE_RADIO_FREQUENCY + for (auto *radio_frequency : App.get_radio_frequencies()) { + radio_frequency->on_api_connection_closed(this); + } #endif } @@ -630,16 +644,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c msg.min_mireds = traits.get_min_mireds(); msg.max_mireds = traits.get_max_mireds(); } - FixedVector effects_list; - if (light->supports_effects()) { - auto &light_effects = light->get_effects(); - effects_list.init(light_effects.size() + 1); - effects_list.push_back("None"); - for (auto *effect : light_effects) { - // c_str() is safe as effect names are null-terminated strings from codegen - effects_list.push_back(effect->get_name().c_str()); - } - } + light::LightEffectNames effects_list(light->get_effects()); msg.effects = &effects_list; return fill_and_encode_entity_info(light, msg, conn, remaining_size); } @@ -990,7 +995,9 @@ uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) { auto *select = static_cast(entity); ListEntitiesSelectResponse msg; - msg.options = &select->traits.get_options(); + const auto &opts = select->traits.get_options(); + const std::span options(opts.data(), opts.size()); + msg.options = &options; return fill_and_encode_entity_info(select, msg, conn, remaining_size); } void APIConnection::on_select_command_request(const SelectCommandRequest &msg) { @@ -1389,6 +1396,40 @@ void APIConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { } #endif +#if defined(USE_SENDSPIN) && defined(USE_ESP_IDF) +bool APIConnection::send_sendspin_pairing_token_response_() { + SendspinPairingTokenResponse resp; +#ifdef USE_API_NOISE + // has_psk() stands in for "this session proved the key": with a key active, plaintext and zero-key sessions + // never reach hello, and activating a key closes every session opened before it. + if (!this->parent_->get_noise_ctx().has_psk()) { + resp.status = enums::SENDSPIN_PAIRING_TOKEN_STATUS_ENCRYPTION_REQUIRED; + return this->send_message(resp); + } + auto token = sendspin_::global_sendspin_hub->get_pairing_token(); + if (token.has_value()) { + resp.status = enums::SENDSPIN_PAIRING_TOKEN_STATUS_OK; + resp.token = StringRef(*token); + } else if (sendspin_::global_sendspin_hub->status_has_error()) { + resp.status = enums::SENDSPIN_PAIRING_TOKEN_STATUS_FAILED; + } else if (sendspin_::global_sendspin_hub->is_disabled()) { + resp.status = enums::SENDSPIN_PAIRING_TOKEN_STATUS_DISABLED; + } else { + resp.status = enums::SENDSPIN_PAIRING_TOKEN_STATUS_NOT_READY; + } +#else + resp.status = enums::SENDSPIN_PAIRING_TOKEN_STATUS_ENCRYPTION_REQUIRED; +#endif + return this->send_message(resp); +} + +void APIConnection::on_sendspin_pairing_token_request() { + if (!this->send_sendspin_pairing_token_response_()) { + this->on_fatal_error(); + } +} +#endif + #ifdef USE_ALARM_CONTROL_PANEL bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { return this->send_message_smart_(a_alarm_control_panel, AlarmControlPanelStateResponse::MESSAGE_TYPE, @@ -1515,8 +1556,10 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c } #endif -#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY) +#ifdef USE_IR_RF void APIConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) { + // Clients on API 1.18+ are told when the frame has left the transmitter; the entity owns that reply + const bool want_reply = this->client_supports_api_version(1, 18); // Dispatch by key: infrared entities are checked first, then radio frequency entities. // The key is unique across all entity instances on a device, so at most one lookup will succeed. #ifdef USE_INFRARED @@ -1526,6 +1569,7 @@ void APIConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRF call.set_carrier_frequency(msg.carrier_frequency); call.set_raw_timings_packed(msg.timings_data_, msg.timings_length_, msg.timings_count_); call.set_repeat_count(msg.repeat_count); + call.set_api_connection(want_reply ? this : nullptr); call.perform(); return; } @@ -1538,13 +1582,38 @@ void APIConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRF call.set_modulation(static_cast(msg.modulation)); call.set_repeat_count(msg.repeat_count); call.set_raw_timings_packed(msg.timings_data_, msg.timings_length_, msg.timings_count_); + call.set_api_connection(want_reply ? this : nullptr); call.perform(); + return; } #endif + ESP_LOGW(TAG, "IR/RF transmit for unknown key %" PRIu32, msg.key); + if (want_reply) { + // nothing will ever report for an unknown key, so answer as not started right away +#ifdef USE_DEVICES + const uint32_t device_id = msg.device_id; +#else + const uint32_t device_id = 0; +#endif + if (!this->send_infrared_rf_transmit_complete(device_id, msg.key, false)) { + API_LOG_MSG_DROPPED(TAG, "IR/RF reply"); + } + } +} + +bool APIConnection::send_infrared_rf_transmit_complete([[maybe_unused]] uint32_t device_id, uint32_t key, + bool success) { + InfraredRFTransmitCompleteResponse resp{}; +#ifdef USE_DEVICES + resp.device_id = device_id; +#endif + resp.key = key; + resp.success = success; + return this->send_message(resp); } #endif -#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY) +#ifdef USE_IR_RF void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg) { if (!this->send_message(msg)) { // V: fires per decoded frame with no subscription gate, so a warning @@ -1552,6 +1621,7 @@ void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent ESP_LOGV(TAG, "IR/RF event dropped, TCP buffer full"); } } + #endif #ifdef USE_SERIAL_PROXY @@ -1643,6 +1713,22 @@ void APIConnection::on_serial_proxy_get_modem_pins_request(const SerialProxyGetM } } +void APIConnection::on_subscribe_serial_proxy_identity_request() { +#ifdef USE_SERIAL_PROXY_USB_IDENTITY + // Only USB ports change identity after this snapshot + this->flags_.serial_proxy_identity_subscription = true; +#endif + for (auto *proxy : App.get_serial_proxies()) { + proxy->send_identity(this); + } +} + +void APIConnection::send_serial_proxy_identity(const SerialProxyIdentity &msg) { + if (!this->send_message(msg)) { + API_LOG_MSG_DROPPED(TAG, "Serial proxy identity"); + } +} + void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) { auto &proxies = App.get_serial_proxies(); if (msg.instance >= proxies.size()) { @@ -1814,7 +1900,7 @@ bool APIConnection::send_hello_response_(const HelloRequest &msg) { HelloResponse resp; resp.api_version_major = 1; - resp.api_version_minor = 17; + resp.api_version_minor = 19; // 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()); @@ -1837,6 +1923,19 @@ bool APIConnection::send_hello_response_(const HelloRequest &msg) { // Auto-authenticate - password auth was removed in ESPHome 2026.1.0 this->complete_authentication_(); +#ifdef USE_API_OUTGOING_CONNECTION + // With a PSK set only key-verified transports reach hello: plaintext and + // zero-PSK are rejected, and pre-activation sessions are force-closed + if (msg.outgoing_connection_target && !this->flags_.outgoing_connection_target) { + if (this->parent_->get_noise_ctx().has_psk()) { + this->flags_.outgoing_connection_target = true; + this->parent_->on_outgoing_target_client(this); + } else { + this->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("Dial-back target refused; no key active")); + } + } +#endif + return this->send_message(resp); } @@ -1959,6 +2058,9 @@ bool APIConnection::send_device_info_response_() { // one) so this advertisement survives the plaintext removal in 2027.2.0. resp.api_encryption_provisionable = !this->parent_->get_noise_ctx().has_psk(); #endif +#ifdef USE_API_OUTGOING_CONNECTION + resp.api_outgoing_connection_supported = true; +#endif #endif #ifdef USE_DEVICES size_t device_index = 0; @@ -2011,6 +2113,12 @@ bool APIConnection::send_device_capabilities_response_() { info.port_type = proxy->get_port_type(); info.configured_line_states = proxy->get_configured_modem_pins(); } +#endif +#ifdef USE_API_WIZARD + resp.wizard.configured = true; +#endif +#if defined(USE_SENDSPIN) && defined(USE_ESP_IDF) + resp.sendspin.feature_flags = sendspin_::global_sendspin_hub->get_feature_flags(); #endif return this->send_message(resp); } @@ -2225,7 +2333,13 @@ void APIConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSet } #endif #ifdef USE_API_HOMEASSISTANT_STATES -void APIConnection::on_subscribe_home_assistant_states_request() { state_subs_at_ = 0; } +void APIConnection::on_subscribe_home_assistant_states_request() { +#ifdef USE_API_WIZARD_LINKED_INPUTS + // Remember it, as a client that subscribed also gets the subscriptions again when a wizard input is set + this->flags_.home_assistant_states = true; +#endif + state_subs_at_ = 0; +} #endif bool APIConnection::try_to_clear_buffer_slow_(bool log_out_of_space) { delay(0); @@ -2269,8 +2383,7 @@ bool APIConnection::send_message_(uint32_t payload_size, uint16_t message_type, #endif // Capacity reserved above, cannot fail (void) shared_buf.resize(write_start + payload_size); - ProtoWriteBuffer buffer{&shared_buf, write_start}; - uint8_t *end = encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf)); + uint8_t *end = encode_fn(msg, shared_buf.data() + write_start PROTO_ENCODE_DEBUG_INIT(&shared_buf)); #ifdef ESPHOME_DEBUG_API proto_check_encode_end(end, shared_buf.data() + shared_buf.size()); #else @@ -2668,6 +2781,13 @@ void APIConnection::process_state_subscriptions_() { } const auto &it = subs[this->state_subs_at_]; +#ifdef USE_API_WIZARD_LINKED_INPUTS + // An entity id that is not set yet (a wizard input) has nothing to subscribe to; it is sent once it is set + if (it.entity_id[0] == '\0') { + this->state_subs_at_++; + return; + } +#endif SubscribeHomeAssistantStateResponse resp; resp.entity_id = StringRef(it.entity_id); diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 0b8a1dc2b29..9f784185b83 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -223,6 +223,10 @@ class APIConnection final : public APIServerConnectionBase { void on_z_wave_proxy_request(const ZWaveProxyRequest &msg); #endif +#if defined(USE_SENDSPIN) && defined(USE_ESP_IDF) + void on_sendspin_pairing_token_request(); +#endif + #ifdef USE_ALARM_CONTROL_PANEL bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel); void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg); @@ -233,9 +237,12 @@ class APIConnection final : public APIServerConnectionBase { void on_water_heater_command_request(const WaterHeaterCommandRequest &msg); #endif -#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY) +#ifdef USE_IR_RF void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg); void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg); + // Reply to an InfraredRFTransmitRawTimingsRequest (API 1.18+); false when the TCP buffer is + // full, the entity that owns the reply retries it then + [[nodiscard]] bool send_infrared_rf_transmit_complete(uint32_t device_id, uint32_t key, bool success); #endif #ifdef USE_SERIAL_PROXY @@ -243,6 +250,9 @@ class APIConnection final : public APIServerConnectionBase { void on_serial_proxy_write_request(const SerialProxyWriteRequest &msg); void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg); void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg); + void on_subscribe_serial_proxy_identity_request(); + /// Send a port identity to this client + void send_serial_proxy_identity(const SerialProxyIdentity &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); @@ -273,6 +283,12 @@ class APIConnection final : public APIServerConnectionBase { void on_ping_request(); void on_device_info_request(); void on_device_capabilities_request(); +#ifdef USE_API_WIZARD + void on_device_wizard_request(); +#endif +#ifdef USE_API_WIZARD_INPUTS + void on_wizard_input_set_request(const WizardInputSetRequest &msg); +#endif void on_list_entities_request() { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); } void on_subscribe_states_request() { this->flags_.state_subscription = true; @@ -302,6 +318,10 @@ class APIConnection final : public APIServerConnectionBase { #endif #ifdef USE_API_HOMEASSISTANT_STATES void on_subscribe_home_assistant_states_request(); +#ifdef USE_API_WIZARD_LINKED_INPUTS + /// Tell this client about the subscriptions whose entity id is stored in the given buffer, as the buffer changed + void resend_state_subscriptions(const char *entity_id); +#endif #endif #ifdef USE_API_USER_DEFINED_ACTIONS void on_execute_service_request(const ExecuteServiceRequest &msg); @@ -345,7 +365,7 @@ class APIConnection final : public APIServerConnectionBase { void on_no_setup_connection(); // Function pointer type for type-erased message encoding - using MessageEncodeFn = uint8_t *(*) (const void *, ProtoWriteBuffer &PROTO_ENCODE_DEBUG_PARAM); + using MessageEncodeFn = ProtoEncodeFn; // Function pointer type for type-erased size calculation using CalculateSizeFn = uint32_t (*)(const void *); @@ -378,6 +398,23 @@ class APIConnection final : public APIServerConnectionBase { return this->helper_->get_peername_to(buf); } +#ifdef USE_API_OUTGOING_CONNECTION + /// Get the peer address itself, for remembering a dial-back target + int getpeername(struct sockaddr *addr, socklen_t *addrlen) const { return this->helper_->getpeername(addr, addrlen); } + /// Outgoing connection: send our server hello immediately so the peer can + /// pick the matching key. Outgoing connections are only dialed when a PSK + /// is set, so the helper is always the noise helper. Call after start(). + void mark_outgoing() { + if (this->flags_.remove) { + return; // start() failed; the connection is already being torn down + } + APIError err = static_cast(this->helper_.get())->send_server_hello_first(); + if (err != APIError::OK) { + this->fatal_error_with_log_(LOG_STR("Server hello failed"), err); + } + } +#endif + protected: bool try_to_clear_buffer_slow_(bool log_out_of_space); @@ -399,6 +436,9 @@ class APIConnection final : public APIServerConnectionBase { #ifdef USE_VOICE_ASSISTANT bool send_voice_assistant_get_configuration_response_(const VoiceAssistantConfigurationRequest &msg); #endif +#if defined(USE_SENDSPIN) && defined(USE_ESP_IDF) + bool send_sendspin_pairing_token_response_(); +#endif #ifdef USE_CAMERA void try_send_camera_image_(); @@ -734,12 +774,21 @@ class APIConnection final : public APIServerConnectionBase { uint8_t batch_first_message : 1; // For batch buffer allocation uint8_t should_try_send_immediately : 1; // True after initial states are sent uint8_t may_have_remaining_data : 1; // Read loop hit limit, retry without ready check +#ifdef USE_API_WIZARD_LINKED_INPUTS + uint8_t home_assistant_states : 1; // Client subscribed to Home Assistant states +#endif +#ifdef USE_API_OUTGOING_CONNECTION + uint8_t outgoing_connection_target : 1; // Client declared itself a dial-back target in its hello +#endif #ifdef HAS_PROTO_MESSAGE_DUMP uint8_t log_only_mode : 1; #endif - } flags_{}; // 2 bytes total +#ifdef USE_SERIAL_PROXY_USB_IDENTITY + uint8_t serial_proxy_identity_subscription : 1; +#endif + } flags_{}; // 2 bytes; 3 with HAS_PROTO_MESSAGE_DUMP + USE_API_OUTGOING_CONNECTION + USE_SERIAL_PROXY_USB_IDENTITY - // 2-byte type immediately after flags_ (no padding between them) + // 2-byte type immediately after flags_ (one padding byte when flags_ is 3 bytes) uint16_t batch_message_type_{0}; // Current message type during batch encoding // 1-byte types to fill remaining space before next 4-byte boundary // Client API versions are clamped to 255 on receive (see send_hello_response_) diff --git a/esphome/components/api/api_connection_buffer.h b/esphome/components/api/api_connection_buffer.h index 5fbe171b523..9b05da76e5d 100644 --- a/esphome/components/api/api_connection_buffer.h +++ b/esphome/components/api/api_connection_buffer.h @@ -45,8 +45,8 @@ inline uint16_t ESPHOME_ALWAYS_INLINE APIConnection::encode_to_buffer(uint32_t c conn->fatal_out_of_memory_(); return 0; } - ProtoWriteBuffer buffer{&shared_buf, shared_buf.size() - calculated_size}; - uint8_t *end = encode_fn(msg, buffer PROTO_ENCODE_DEBUG_INIT(&shared_buf)); + uint8_t *end = + encode_fn(msg, shared_buf.data() + shared_buf.size() - calculated_size PROTO_ENCODE_DEBUG_INIT(&shared_buf)); #ifdef ESPHOME_DEBUG_API // A body that writes fewer bytes than calculate_size() promised would ship stale buffer bytes proto_check_encode_end(end, shared_buf.data() + shared_buf.size()); diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 41d1230aaa6..de50eb109fa 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -10,7 +10,7 @@ namespace esphome::api { -static const char *const TAG = "api.frame_helper"; +ESPHOME_LOG_TAG(TAG, "api.frame_helper"); // Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512) static constexpr size_t API_MAX_LOG_BYTES = 168; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index a68a0ad0d87..96c302745c8 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -285,7 +285,8 @@ class APIFrameHelper { DATA = 5, CLOSED = 6, FAILED = 7, - EXPLICIT_REJECT = 8, // Noise only + EXPLICIT_REJECT = 8, // Noise only + CLIENT_HELLO_OUTGOING = 9, // Noise only: like CLIENT_HELLO but the server hello already went out (outgoing conn) }; // Fast inline state check for read_packet/write_protobuf_messages hot path. diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index e726a7c9676..47772fe590a 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -22,7 +22,7 @@ using noise::noise_err_to_logstr; static_assert(MAX_HANDSHAKE_SIZE == noise::MAX_HANDSHAKE_SIZE, "api and noise component handshake size limits must match"); -static const char *const TAG = "api.noise"; +ESPHOME_LOG_TAG(TAG, "api.noise"); static constexpr char PROLOGUE_INIT[] PROGMEM = "NoiseAPIInit"; static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit") @@ -70,6 +70,13 @@ APIError APINoiseFrameHelper::init() { state_ = State::CLIENT_HELLO; return APIError::OK; } +#ifdef USE_API_OUTGOING_CONNECTION +APIError APINoiseFrameHelper::send_server_hello_first() { + // The peer needs our name and MAC to pick the key before its first message + this->state_ = State::CLIENT_HELLO_OUTGOING; + return this->send_server_hello_frame_(); +} +#endif #ifdef USE_API_PLAINTEXT APIError APINoiseFrameHelper::init_from_handoff(const uint8_t *header, uint8_t header_len) { APIError err = this->init(); @@ -242,6 +249,9 @@ APIError APINoiseFrameHelper::state_action_() { HELPER_LOG("Bad state for method: %d", (int) this->state_); return APIError::BAD_STATE; case State::CLIENT_HELLO: +#ifdef USE_API_OUTGOING_CONNECTION + case State::CLIENT_HELLO_OUTGOING: +#endif return this->state_action_client_hello_(); case State::SERVER_HELLO: return this->state_action_server_hello_(); @@ -274,11 +284,16 @@ APIError APINoiseFrameHelper::state_action_client_hello_() { std::memcpy(dst + 2, this->rx_buf_.data(), rx_size); } +#ifdef USE_API_OUTGOING_CONNECTION + if (this->state_ == State::CLIENT_HELLO_OUTGOING) { + // Server hello already went out at handoff + return this->start_handshake_(); + } +#endif state_ = State::SERVER_HELLO; return APIError::OK; } -APIError APINoiseFrameHelper::state_action_server_hello_() { - // send server hello +APIError APINoiseFrameHelper::send_server_hello_frame_() { const auto &name = App.get_name(); char mac[MAC_ADDRESS_BUFFER_SIZE]; get_mac_address_into_buffer(mac); @@ -302,15 +317,18 @@ APIError APINoiseFrameHelper::state_action_server_hello_() { // node mac, terminated by null byte std::memcpy(msg + mac_offset, mac, MAC_ADDRESS_BUFFER_SIZE); - APIError aerr = write_frame_(msg, total_size); + return write_frame_(msg, total_size); +} +APIError APINoiseFrameHelper::state_action_server_hello_() { + APIError aerr = this->send_server_hello_frame_(); if (aerr != APIError::OK) return aerr; - - // start handshake - aerr = init_handshake_(); + return this->start_handshake_(); +} +APIError APINoiseFrameHelper::start_handshake_() { + APIError aerr = init_handshake_(); if (aerr != APIError::OK) return aerr; - state_ = State::HANDSHAKE; return APIError::OK; } diff --git a/esphome/components/api/api_frame_helper_noise.h b/esphome/components/api/api_frame_helper_noise.h index 366751738e6..a807d6e5da3 100644 --- a/esphome/components/api/api_frame_helper_noise.h +++ b/esphome/components/api/api_frame_helper_noise.h @@ -28,6 +28,12 @@ class APINoiseFrameHelper final : public APIFrameHelper { // Seeds the already-read header bytes and pumps the handshake state machine // until it would block. APIError init_from_handoff(const uint8_t *header, uint8_t header_len); +#endif +#ifdef USE_API_OUTGOING_CONNECTION + // Send the server hello immediately so the peer can pick the key before + // its PSK-mixed message. Call after init(); the mode is tracked in state_ + // so the helper does not grow. + APIError send_server_hello_first(); #endif APIError loop() override; APIError read_packet(ReadPacketBuffer *buffer) override; @@ -39,6 +45,8 @@ class APINoiseFrameHelper final : public APIFrameHelper { APIError state_action_(); APIError state_action_client_hello_(); APIError state_action_server_hello_(); + APIError send_server_hello_frame_(); + APIError start_handshake_(); APIError state_action_handshake_(); APIError state_action_handshake_read_(); APIError state_action_handshake_write_(); diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index d4e3354fa00..dc00d8ec1e4 100644 --- a/esphome/components/api/api_frame_helper_plaintext.cpp +++ b/esphome/components/api/api_frame_helper_plaintext.cpp @@ -16,7 +16,7 @@ namespace esphome::api { -static const char *const TAG = "api.plaintext"; +ESPHOME_LOG_TAG(TAG, "api.plaintext"); // Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512) static constexpr size_t API_MAX_LOG_BYTES = 168; diff --git a/esphome/components/api/api_options.proto b/esphome/components/api/api_options.proto index 66295b3d535..8e8fa99833d 100644 --- a/esphome/components/api/api_options.proto +++ b/esphome/components/api/api_options.proto @@ -122,4 +122,9 @@ extend google.protobuf.FieldOptions { // to true when the field arrives, so an all-default submessage can be told apart // from an absent one (e.g. a UTC ParsedTimezone, which is all zeros). optional bool track_presence = 50020 [default=false]; + + // progmem: The pointer_to_buffer bytes field points into flash (PROGMEM). + // The generated encode and dump copy it with progmem_memcpy, as ESP8266 + // cannot read flash with ordinary loads. + optional bool progmem = 50021 [default=false]; } diff --git a/esphome/components/api/api_outgoing_connection.cpp b/esphome/components/api/api_outgoing_connection.cpp new file mode 100644 index 00000000000..b38976e8cd5 --- /dev/null +++ b/esphome/components/api/api_outgoing_connection.cpp @@ -0,0 +1,318 @@ +#include "api_outgoing_connection.h" +#if defined(USE_API) && defined(USE_API_OUTGOING_CONNECTION) + +#include "api_connection.h" +#include "api_server.h" +#include "esphome/components/network/util.h" +#include "esphome/core/application.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include +#include +#include +#include + +namespace esphome::api { + +ESPHOME_LOG_TAG(TAG, "api.outgoing"); + +#ifndef API_OUTGOING_CONNECTION_HOST +static constexpr uint32_t OUTGOING_TARGET_PREF_HASH = 629847102UL; +#endif + +#ifndef API_OUTGOING_CONNECTION_HOST +// Read the connection's peer address into target; false when unavailable or +// of a family this build cannot dial +static bool peer_to_target(APIConnection *conn, SavedOutgoingTarget &target) { + // Zeroed because the raw lwIP getpeername() leaves sin6_scope_id untouched + struct sockaddr_storage peer = {}; + socklen_t peer_len = sizeof(peer); + if (conn->getpeername((struct sockaddr *) &peer, &peer_len) != 0) { + return false; + } + const sa_family_t family = ((struct sockaddr *) &peer)->sa_family; +#if USE_NETWORK_IPV6 + if (family == AF_INET6) { + const auto *addr6 = reinterpret_cast(&peer); + const auto *bytes = reinterpret_cast(&addr6->sin6_addr); + uint32_t prefix[3]; + memcpy(prefix, bytes, sizeof(prefix)); + // A dual-stack listener reports an IPv4 peer as ::ffff:a.b.c.d + if (prefix[0] == 0 && prefix[1] == 0 && prefix[2] == htonl(0xFFFFUL)) { + target.family = AF_INET; + memcpy(target.addr, bytes + sizeof(prefix), sizeof(struct in_addr)); + return true; + } + // A link-local target is only reachable through the interface it came in + // on. Device platforms number interfaces from one; a host build can hand + // out an index too large to store, and a truncated one dials the wrong + // interface, so that target is not remembered at all. + if (addr6->sin6_scope_id > UINT8_MAX) { + return false; + } + target.family = AF_INET6; + memcpy(target.addr, bytes, sizeof(target.addr)); + target.scope_id = static_cast(addr6->sin6_scope_id); + return true; + } +#endif + if (family != AF_INET) { + return false; + } + const auto *addr4 = reinterpret_cast(&peer); + target.family = AF_INET; + memcpy(target.addr, &addr4->sin_addr, sizeof(addr4->sin_addr)); + return true; +} +#endif + +socklen_t OutgoingConnectionManager::target_sockaddr_(struct sockaddr_storage *addr) const { +#ifdef API_OUTGOING_CONNECTION_HOST + // Validation only lets through a literal both inet_pton and inet6_aton + // accept, so this cannot fail + return socket::set_sockaddr((struct sockaddr *) addr, sizeof(*addr), API_OUTGOING_CONNECTION_HOST, + API_OUTGOING_CONNECTION_PORT); +#else +#if USE_NETWORK_IPV6 + if (this->saved_.family == AF_INET6) { + auto *addr6 = reinterpret_cast(addr); + memset(addr6, 0, sizeof(*addr6)); + addr6->sin6_family = AF_INET6; + addr6->sin6_port = htons(API_OUTGOING_CONNECTION_PORT); + memcpy(&addr6->sin6_addr, this->saved_.addr, sizeof(this->saved_.addr)); + addr6->sin6_scope_id = this->saved_.scope_id; + return sizeof(*addr6); + } +#endif + if (this->saved_.family != AF_INET) { + return 0; + } + auto *addr4 = reinterpret_cast(addr); + memset(addr4, 0, sizeof(*addr4)); + addr4->sin_family = AF_INET; + addr4->sin_port = htons(API_OUTGOING_CONNECTION_PORT); + memcpy(&addr4->sin_addr, this->saved_.addr, sizeof(addr4->sin_addr)); + return sizeof(*addr4); +#endif +} + +#ifndef API_OUTGOING_CONNECTION_HOST +void OutgoingConnectionManager::format_target_(std::span buf) const { + struct sockaddr_storage addr; + socklen_t addr_len = this->target_sockaddr_(&addr); + if (addr_len == 0) { + buf[0] = '\0'; + return; + } + // Clears buf itself if it cannot format the address + socket::format_sockaddr_to((struct sockaddr *) &addr, addr_len, buf); +} +#endif + +void OutgoingConnectionManager::setup() { +#ifndef API_OUTGOING_CONNECTION_HOST + this->target_pref_ = global_preferences->make_preference(OUTGOING_TARGET_PREF_HASH, true); + struct sockaddr_storage addr; + // dump_config() prints whichever target this leaves in place + if (this->target_pref_.load(&this->saved_) && this->target_sockaddr_(&addr) != 0) { + this->host_persisted_ = true; + } else { + // Never saved, failed its size or CRC check, or holds an unknown family + this->saved_ = {}; + } +#endif +} + +void OutgoingConnectionManager::loop(APIServer *server) { + if (server->has_outgoing_target_client_()) { + return; // on_target_client() already reset the dial state + } + if (this->dialed_conn_ != nullptr) { + // A live dialed session (flagged or not, e.g. a host: peer) is the + // target; a silent one dies on the handshake timeout + return; + } + const uint32_t now = App.get_loop_component_start_time(); + switch (this->state_) { + case DialState::DIAL_STATE_IDLE: + // Target went away; give it the configured delay to reconnect first + this->schedule_wait_(now, IDLE_WAIT_MS); + break; + case DialState::DIAL_STATE_WAITING: + if (now - this->state_ts_ >= this->wait_) { + this->try_dial_(server, now); + } + break; + case DialState::DIAL_STATE_CONNECTING: + this->poll_connect_(server, now); + break; + } +} + +void OutgoingConnectionManager::try_dial_(APIServer *server, uint32_t now) { + if (!network::is_connected()) { + // Flips within seconds of boot; recheck fast so a deep sleep wake + // window is not spent waiting + this->schedule_wait_(now, NETWORK_RETRY_MS); + return; + } + struct sockaddr_storage addr; + socklen_t addr_len = this->target_sockaddr_(&addr); + const bool at_limit = server->at_client_limit_(); + // No target is the steady state until a dial-back client has ever connected + if (addr_len == 0 || at_limit || !server->noise_ctx_.has_psk()) { + // Repeats for as long as the reason holds, so keep it out of debug logs + ESP_LOGV(TAG, "Not dialing: %s", + addr_len == 0 ? LOG_STR_LITERAL("no target") + : (at_limit ? LOG_STR_LITERAL("max connections") : LOG_STR_LITERAL("no key"))); + // Not a dial failure; retry without escalating the backoff + this->schedule_wait_(now, PRECONDITION_RETRY_MS); + return; + } + this->dial_socket_ = socket::socket_loop_monitored(((struct sockaddr *) &addr)->sa_family, SOCK_STREAM, IPPROTO_TCP); + if (!this->dial_socket_ || this->dial_socket_->setblocking(false) != 0) { + ESP_LOGW(TAG, "Socket %s failed: errno %d", + this->dial_socket_ ? LOG_STR_LITERAL("setblocking") : LOG_STR_LITERAL("create"), errno); + this->schedule_retry_(now); + return; + } +#ifdef API_OUTGOING_CONNECTION_HOST + ESP_LOGD(TAG, "Dialing " API_OUTGOING_CONNECTION_HOST ":%u", API_OUTGOING_CONNECTION_PORT); +#else + char host[socket::SOCKADDR_STR_LEN]; + socket::format_sockaddr_to((struct sockaddr *) &addr, addr_len, host); + ESP_LOGD(TAG, "Dialing %s:%u", host, API_OUTGOING_CONNECTION_PORT); +#endif + int err = this->dial_socket_->connect((struct sockaddr *) &addr, addr_len); + if (err == 0) { + // Immediate success (possible for localhost) + this->handoff_(server, now); + return; + } + if (errno != EINPROGRESS) { + ESP_LOGW(TAG, "Connect failed: %d", errno); + this->schedule_retry_(now); + return; + } + this->state_ = DialState::DIAL_STATE_CONNECTING; + this->state_ts_ = now; + this->last_poll_ = now; +} + +void OutgoingConnectionManager::poll_connect_(APIServer *server, uint32_t now) { + if (now - this->state_ts_ >= CONNECT_TIMEOUT_MS) { + ESP_LOGW(TAG, "Connect timeout"); + this->schedule_retry_(now); + return; + } + if (now - this->last_poll_ < CONNECT_POLL_INTERVAL_MS) { + return; + } + this->last_poll_ = now; + int err = 0; + switch (socket::poll_connect(*this->dial_socket_, err)) { + case socket::ConnectPollResult::CONNECT_POLL_RESULT_PENDING: + break; + case socket::ConnectPollResult::CONNECT_POLL_RESULT_CONNECTED: + this->handoff_(server, now); + break; + case socket::ConnectPollResult::CONNECT_POLL_RESULT_ERROR: + ESP_LOGW(TAG, "Connect failed: %d", err); + this->schedule_retry_(now); + break; + } +} + +void OutgoingConnectionManager::handoff_(APIServer *server, uint32_t now) { + this->dialed_conn_ = server->add_outgoing_client_(std::move(this->dial_socket_)); + if (this->dialed_conn_ == nullptr) { + // Only preconditions (slot limit, key cleared) refuse the handoff; the + // peer is reachable, so do not escalate the backoff + this->schedule_wait_(now, PRECONDITION_RETRY_MS); + return; + } + // Connected; dialed_conn_ gates further dialing until the session settles + this->state_ = DialState::DIAL_STATE_IDLE; +} + +void OutgoingConnectionManager::schedule_wait_(uint32_t now, uint32_t wait) { + this->dial_socket_.reset(); // no-op when the socket was handed off + this->state_ = DialState::DIAL_STATE_WAITING; + this->state_ts_ = now; + this->wait_ = wait; +} + +void OutgoingConnectionManager::schedule_retry_(uint32_t now) { + // +/-20% jitter so a fleet of devices does not retry one server in lockstep + const uint32_t jitter_span = this->backoff_ / 5; + this->schedule_wait_(now, this->backoff_ - jitter_span + (random_uint32() % (2 * jitter_span + 1))); + this->backoff_ = std::min(this->backoff_ * 2, BACKOFF_MAX_MS); +} + +void OutgoingConnectionManager::on_client_removed(APIConnection *conn, bool was_authenticated) { + if (conn != this->dialed_conn_) { + return; + } + this->dialed_conn_ = nullptr; + if (was_authenticated) { + // A working peer (e.g. a host: target that never sends the flag) + // disconnected normally; state is IDLE, so loop() applies the delay + this->backoff_ = BACKOFF_MIN_MS; + } else { + this->schedule_retry_(App.get_loop_component_start_time()); + } +} + +void OutgoingConnectionManager::on_target_client(APIConnection *conn) { + // The target is connected; stop any dial in flight and reset the backoff. + // A dialed connection stays tracked unless it is this one: an inbound + // target must not orphan a still-open dial. + this->dial_socket_.reset(); + if (conn == this->dialed_conn_) { + this->dialed_conn_ = nullptr; + } + this->state_ = DialState::DIAL_STATE_IDLE; + this->backoff_ = BACKOFF_MIN_MS; +#ifndef API_OUTGOING_CONNECTION_HOST + SavedOutgoingTarget target{}; + if (!peer_to_target(conn, target)) { + ESP_LOGW(TAG, "Not remembering this target; its address cannot be dialed"); + return; + } + if (this->host_persisted_ && memcmp(&target, &this->saved_, sizeof(target)) == 0) { + return; // unchanged and already on flash; avoid flash wear + } + // Use the fresh address this boot even if the flash write fails; a failed + // write is retried on the next flagged hello via host_persisted_ + this->saved_ = target; + if (!this->persist_target_()) { + ESP_LOGW(TAG, "Failed to save target"); + return; + } + char host[socket::SOCKADDR_STR_LEN]; + this->format_target_(host); + ESP_LOGD(TAG, "Remembered %s as the dial target", host); +#endif +} + +void OutgoingConnectionManager::dump_config() const { + // The boot delay differs from delay: on deep sleep builds, so print the + // value that actually applies + ESP_LOGCONFIG(TAG, + " Outgoing connection port: %u\n" + " Outgoing connection boot delay: %" PRIu32 "ms", + API_OUTGOING_CONNECTION_PORT, BOOT_WAIT_MS); + // Both forms keep their text out of RAM on ESP8266: in the format string, + // or through LOG_STR_LITERAL +#ifdef API_OUTGOING_CONNECTION_HOST + ESP_LOGCONFIG(TAG, " Outgoing connection host: " API_OUTGOING_CONNECTION_HOST); +#else + char buf[socket::SOCKADDR_STR_LEN]; + this->format_target_(buf); + ESP_LOGCONFIG(TAG, " Outgoing connection host: %s", buf[0] == '\0' ? LOG_STR_LITERAL("none remembered yet") : buf); +#endif +} + +} // namespace esphome::api +#endif // USE_API && USE_API_OUTGOING_CONNECTION diff --git a/esphome/components/api/api_outgoing_connection.h b/esphome/components/api/api_outgoing_connection.h new file mode 100644 index 00000000000..16d50c34255 --- /dev/null +++ b/esphome/components/api/api_outgoing_connection.h @@ -0,0 +1,120 @@ +#pragma once + +#include "esphome/core/defines.h" +#if defined(USE_API) && defined(USE_API_OUTGOING_CONNECTION) + +#ifndef USE_API_NOISE +#error "api outgoing_connection needs noise encryption so the peer is verified by key" +#endif + +#include "esphome/components/socket/socket.h" +#include "esphome/core/preferences.h" + +#include + +namespace esphome::api { + +class APIServer; +class APIConnection; + +// Room for an IPv6 address in every build, so a remembered IPv4 target is +// still dialed after enable_ipv6 is turned on. A size that followed the build +// would also shift every preference registered after this one on ESP8266, +// where slots are positional. An IPv6 target on a build without IPv6 is +// dropped by target_sockaddr_() and relearned. +// Bytes in an IPv6 address +static constexpr size_t TARGET_ADDR_LEN = 16; + +struct SavedOutgoingTarget { + // 0 when none is remembered, else AF_INET or AF_INET6 + uint8_t family; + // Network order, IPv4 in the first four bytes and the rest zero + uint8_t addr[TARGET_ADDR_LEN]; + // Interface a link-local IPv6 target is reachable on, 0 when it needs none. + // Free in flash: the record still rounds up to the same five words. + uint8_t scope_id; +} PACKED; // NOLINT + +/// Dials out when no dial-back target client is connected. Only the TCP +/// direction flips: the device stays the Noise responder, so both sides +/// still verify by key. Targets the YAML host or the last remembered client. +class OutgoingConnectionManager { + public: + void setup(); + void loop(APIServer *server); + /// A key-verified client declared itself a dial-back target; last one wins + void on_target_client(APIConnection *conn); + /// Clears the dialed-connection gate; dying unauthenticated escalates the backoff + void on_client_removed(APIConnection *conn, bool was_authenticated); + void on_shutdown() { this->dial_socket_.reset(); } + void dump_config() const; + + protected: + enum class DialState : uint8_t { + DIAL_STATE_IDLE, + DIAL_STATE_WAITING, + DIAL_STATE_CONNECTING, + }; + + static constexpr uint32_t BACKOFF_MIN_MS = 5000; + static constexpr uint32_t BACKOFF_MAX_MS = 300000; + static constexpr uint32_t CONNECT_TIMEOUT_MS = 10000; + static constexpr uint32_t CONNECT_POLL_INTERVAL_MS = 250; + static constexpr uint32_t NETWORK_RETRY_MS = 500; + static constexpr uint32_t PRECONDITION_RETRY_MS = 5000; + // A deep sleep wake window is too short to spend on the delay, so those + // builds dial out as soon as the target is gone +#ifdef USE_DEEP_SLEEP + static constexpr uint32_t BOOT_WAIT_MS = 0; + static constexpr uint32_t IDLE_WAIT_MS = BACKOFF_MIN_MS; +#else + static constexpr uint32_t BOOT_WAIT_MS = API_OUTGOING_CONNECTION_DELAY; + static constexpr uint32_t IDLE_WAIT_MS = API_OUTGOING_CONNECTION_DELAY; +#endif + + void try_dial_(APIServer *server, uint32_t now); + void poll_connect_(APIServer *server, uint32_t now); + // Hand the connected socket to the server and gate on the new connection + void handoff_(APIServer *server, uint32_t now); + // Close any half-open dial and wait a jittered backoff before retrying + void schedule_retry_(uint32_t now); + // Wait without escalating the backoff (used for unmet preconditions) + void schedule_wait_(uint32_t now, uint32_t wait); + /// Fill addr with the target and return its length, or 0 when there is none + socklen_t target_sockaddr_(struct sockaddr_storage *addr) const; +#ifndef API_OUTGOING_CONNECTION_HOST + // Write saved_ to flash, tracking success in host_persisted_ + bool persist_target_() { + this->host_persisted_ = this->target_pref_.save(&this->saved_) && global_preferences->sync(); + return this->host_persisted_; + } + /// Format the remembered target for a log line; empty when there is none + void format_target_(std::span buf) const; +#endif + + // Pointers first (4 bytes each on 32-bit) + std::unique_ptr dial_socket_; + // Compared only, never dereferenced + APIConnection *dialed_conn_{nullptr}; +#ifndef API_OUTGOING_CONNECTION_HOST + ESPPreferenceObject target_pref_; +#endif + + // 4-byte types + uint32_t backoff_{BACKOFF_MIN_MS}; + uint32_t wait_{BOOT_WAIT_MS}; + uint32_t state_ts_{0}; + uint32_t last_poll_{0}; + + // Byte-aligned types last +#ifndef API_OUTGOING_CONNECTION_HOST + SavedOutgoingTarget saved_{}; + // False while saved_ holds a value the flash write failed for; retried on + // the next flagged hello + bool host_persisted_{false}; +#endif + DialState state_{DialState::DIAL_STATE_WAITING}; +}; + +} // namespace esphome::api +#endif // USE_API && USE_API_OUTGOING_CONNECTION diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 1d1f594aa73..8847249b173 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -20,11 +20,15 @@ void HelloRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, p case proto_tag(3, WIRE_TYPE_VARINT): msg.api_version_minor = value.as_varint(); break; +#ifdef USE_API_OUTGOING_CONNECTION + case proto_tag(4, WIRE_TYPE_VARINT): + msg.outgoing_connection_target = value.as_bool(); + break; +#endif } } -uint8_t *HelloResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *HelloResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.api_version_major); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.api_version_minor); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.server_info); @@ -49,9 +53,8 @@ void DisconnectRequest::decode_field(void *self, uint32_t tag, const uint8_t *da break; } } -uint8_t *DisconnectRequest::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *DisconnectRequest::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, static_cast(msg.reason)); return pos; } @@ -62,9 +65,8 @@ uint32_t DisconnectRequest::calc_size_msg(const void *self) { return size; } #ifdef USE_AREAS -uint8_t *AreaInfo::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *AreaInfo::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.area_id); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 18, msg.name); return pos; @@ -78,9 +80,8 @@ uint32_t AreaInfo::calc_size_msg(const void *self) { } #endif #ifdef USE_DEVICES -uint8_t *DeviceInfo::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *DeviceInfo::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.device_id); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 18, msg.name); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.area_id); @@ -96,9 +97,8 @@ uint32_t DeviceInfo::calc_size_msg(const void *self) { } #endif #ifdef USE_SERIAL_PROXY -uint8_t *SerialProxyInfo::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *SerialProxyInfo::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.name); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(msg.port_type)); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.configured_line_states); @@ -113,9 +113,8 @@ uint32_t SerialProxyInfo::calc_size_msg(const void *self) { return size; } #endif -uint8_t *DeviceInfoResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *DeviceInfoResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 18, msg.name); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.mac_address); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 34, msg.esphome_version); @@ -152,16 +151,16 @@ uint8_t *DeviceInfoResponse::encode_msg(const void *self, ProtoWriteBuffer &buff #endif #ifdef USE_DEVICES for (const auto &it : msg.devices) { - pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 20, it); + pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 20, it); } #endif #ifdef USE_AREAS for (const auto &it : msg.areas) { - pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 21, it); + pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 21, it); } #endif #ifdef USE_AREAS - pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 22, msg.area); + pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 22, msg.area); #endif #ifdef USE_ZWAVE_PROXY pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 23, msg.zwave_proxy_feature_flags); @@ -171,11 +170,14 @@ uint8_t *DeviceInfoResponse::encode_msg(const void *self, ProtoWriteBuffer &buff #endif #ifdef USE_SERIAL_PROXY for (const auto &it : msg.serial_proxies) { - pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 25, it); + pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 25, it); } #endif #ifdef USE_API_NOISE pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.api_encryption_provisionable); +#endif +#ifdef USE_API_OUTGOING_CONNECTION + pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 27, msg.api_outgoing_connection_supported); #endif return pos; } @@ -242,13 +244,15 @@ uint32_t DeviceInfoResponse::calc_size_msg(const void *self) { #endif #ifdef USE_API_NOISE size += ProtoSize::calc_bool(2, msg.api_encryption_provisionable); +#endif +#ifdef USE_API_OUTGOING_CONNECTION + size += ProtoSize::calc_bool(2, msg.api_outgoing_connection_supported); #endif return size; } #ifdef USE_BLUETOOTH_PROXY -uint8_t *BluetoothProxyCapabilities::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *BluetoothProxyCapabilities::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.feature_flags); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 18, msg.mac_address); return pos; @@ -262,9 +266,8 @@ uint32_t BluetoothProxyCapabilities::calc_size_msg(const void *self) { } #endif #ifdef USE_VOICE_ASSISTANT -uint8_t *VoiceAssistantCapabilities::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *VoiceAssistantCapabilities::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.feature_flags); return pos; } @@ -276,9 +279,8 @@ uint32_t VoiceAssistantCapabilities::calc_size_msg(const void *self) { } #endif #ifdef USE_ZWAVE_PROXY -uint8_t *ZWaveProxyCapabilities::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ZWaveProxyCapabilities::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.feature_flags); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.home_id); return pos; @@ -291,22 +293,53 @@ uint32_t ZWaveProxyCapabilities::calc_size_msg(const void *self) { return size; } #endif -uint8_t *DeviceCapabilitiesResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +#ifdef USE_API_WIZARD +uint8_t *WizardCapabilities::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { + const auto &msg = *static_cast(self); + pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.configured); + return pos; +} +uint32_t WizardCapabilities::calc_size_msg(const void *self) { + const auto &msg = *static_cast(self); + uint32_t size = 0; + size += ProtoSize::calc_bool(1, msg.configured); + return size; +} +#endif +#ifdef USE_SENDSPIN +uint8_t *SendspinCapabilities::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { + const auto &msg = *static_cast(self); + pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.feature_flags); + return pos; +} +uint32_t SendspinCapabilities::calc_size_msg(const void *self) { + const auto &msg = *static_cast(self); + uint32_t size = 0; + size += ProtoSize::calc_uint32(1, msg.feature_flags); + return size; +} +#endif +uint8_t *DeviceCapabilitiesResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); #ifdef USE_BLUETOOTH_PROXY - pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 1, msg.bluetooth_proxy); + pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.bluetooth_proxy); #endif #ifdef USE_VOICE_ASSISTANT - pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 2, msg.voice_assistant); + pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.voice_assistant); #endif #ifdef USE_ZWAVE_PROXY - pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 3, msg.zwave_proxy); + pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.zwave_proxy); #endif #ifdef USE_SERIAL_PROXY for (const auto &it : msg.serial_proxies) { - pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 4, it); + pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 4, it); } +#endif +#ifdef USE_API_WIZARD + pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 5, msg.wizard); +#endif +#ifdef USE_SENDSPIN + pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 6, msg.sendspin); #endif return pos; } @@ -326,14 +359,46 @@ uint32_t DeviceCapabilitiesResponse::calc_size_msg(const void *self) { for (const auto &it : msg.serial_proxies) { size += ProtoSize::calc_message_force(1, it.calculate_size()); } +#endif +#ifdef USE_API_WIZARD + size += ProtoSize::calc_message(1, msg.wizard.calculate_size()); +#endif +#ifdef USE_SENDSPIN + size += ProtoSize::calc_message(1, msg.sendspin.calculate_size()); #endif return size; } +#ifdef USE_API_WIZARD +uint8_t *DeviceWizardResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { + const auto &msg = *static_cast(self); + pos = ProtoEncode::encode_progmem_bytes(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.data, msg.data_len); + return pos; +} +uint32_t DeviceWizardResponse::calc_size_msg(const void *self) { + const auto &msg = *static_cast(self); + uint32_t size = 0; + size += ProtoSize::calc_length(1, msg.data_len); + return size; +} +#endif +#ifdef USE_API_WIZARD_INPUTS +void WizardInputSetRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { + auto &msg = *static_cast(self); + const ProtoFieldValue value(data, scalar); + switch (tag) { + case proto_tag(1, WIRE_TYPE_FIXED32): + msg.key = value.as_fixed32(); + break; + case proto_tag(2, WIRE_TYPE_LENGTH_DELIMITED): + msg.entity_id = StringRef(value.data(), value.size()); + break; + } +} +#endif #ifdef USE_BINARY_SENSOR uint8_t *ListEntitiesBinarySensorResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -367,9 +432,8 @@ uint32_t ListEntitiesBinarySensorResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *BinarySensorStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *BinarySensorStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.state); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.missing_state); @@ -391,9 +455,8 @@ uint32_t BinarySensorStateResponse::calc_size_msg(const void *self) { } #endif #ifdef USE_COVER -uint8_t *ListEntitiesCoverResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesCoverResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -433,9 +496,8 @@ uint32_t ListEntitiesCoverResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *CoverStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *CoverStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); if (uint32_t raw = float_to_raw(msg.position); raw != 0) [[likely]] { pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 29, raw); @@ -492,9 +554,8 @@ void CoverCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t * } #endif #ifdef USE_FAN -uint8_t *ListEntitiesFanResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesFanResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -540,9 +601,8 @@ uint32_t ListEntitiesFanResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *FanStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *FanStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.state); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.oscillating); @@ -614,9 +674,8 @@ void FanCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *da } #endif #ifdef USE_LIGHT -uint8_t *ListEntitiesLightResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesLightResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -629,8 +688,8 @@ uint8_t *ListEntitiesLightResponse::encode_msg(const void *self, ProtoWriteBuffe if (uint32_t raw = float_to_raw(msg.max_mireds); raw != 0) [[likely]] { pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 85, raw); } - for (const char *it : *msg.effects) { - pos = ProtoEncode::encode_string_force(pos PROTO_ENCODE_DEBUG_ARG, 11, it, strlen(it)); + for (const auto &it : *msg.effects) { + pos = ProtoEncode::encode_string_force(pos PROTO_ENCODE_DEBUG_ARG, 11, it); } pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.disabled_by_default); #ifdef USE_ENTITY_ICON @@ -654,8 +713,8 @@ uint32_t ListEntitiesLightResponse::calc_size_msg(const void *self) { size += ProtoSize::calc_float(1, msg.min_mireds); size += ProtoSize::calc_float(1, msg.max_mireds); if (!msg.effects->empty()) { - for (const char *it : *msg.effects) { - size += ProtoSize::calc_length_force(1, strlen(it)); + for (const auto &it : *msg.effects) { + size += ProtoSize::calc_length_force(1, it.size()); } } size += ProtoSize::calc_bool(1, msg.disabled_by_default); @@ -668,9 +727,8 @@ uint32_t ListEntitiesLightResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *LightStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *LightStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.state); if (uint32_t raw = float_to_raw(msg.brightness); raw != 0) [[likely]] { @@ -822,9 +880,8 @@ void LightCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t * } #endif #ifdef USE_SENSOR -uint8_t *ListEntitiesSensorResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesSensorResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -866,9 +923,8 @@ uint32_t ListEntitiesSensorResponse::calc_size_msg(const void *self) { } __attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes) uint8_t * -SensorStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +SensorStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); if (uint32_t raw = float_to_raw(msg.state); raw != 0) [[likely]] { pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, raw); @@ -894,9 +950,8 @@ SensorStateResponse::calc_size_msg(const void *self) { } #endif #ifdef USE_SWITCH -uint8_t *ListEntitiesSwitchResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesSwitchResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -930,9 +985,8 @@ uint32_t ListEntitiesSwitchResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *SwitchStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *SwitchStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.state); #ifdef USE_DEVICES @@ -972,9 +1026,8 @@ void SwitchCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t #endif #ifdef USE_TEXT_SENSOR uint8_t *ListEntitiesTextSensorResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -1006,9 +1059,8 @@ uint32_t ListEntitiesTextSensorResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *TextSensorStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *TextSensorStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.state); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.missing_state); @@ -1043,9 +1095,8 @@ void SubscribeLogsRequest::decode_field(void *self, uint32_t tag, const uint8_t } __attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes) uint8_t * -SubscribeLogsResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +SubscribeLogsResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32_force(pos PROTO_ENCODE_DEBUG_ARG, 1, static_cast(msg.level)); pos = ProtoEncode::write_raw_byte(pos PROTO_ENCODE_DEBUG_ARG, 26); pos = ProtoEncode::encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, msg.message_len_); @@ -1074,9 +1125,8 @@ void NoiseEncryptionSetKeyRequest::decode_field(void *self, uint32_t tag, const } } uint8_t *NoiseEncryptionSetKeyResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.success); return pos; } @@ -1088,9 +1138,8 @@ uint32_t NoiseEncryptionSetKeyResponse::calc_size_msg(const void *self) { } #endif #ifdef USE_API_HOMEASSISTANT_SERVICES -uint8_t *HomeassistantServiceMap::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *HomeassistantServiceMap::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.key); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.value); return pos; @@ -1102,18 +1151,17 @@ uint32_t HomeassistantServiceMap::calc_size_msg(const void *self) { size += ProtoSize::calc_length(1, msg.value.size()); return size; } -uint8_t *HomeassistantActionRequest::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *HomeassistantActionRequest::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.service); for (auto &it : msg.data) { - pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 2, it); + pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 2, it); } for (auto &it : msg.data_template) { - pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 3, it); + pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 3, it); } for (auto &it : msg.variables) { - pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 4, it); + pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 4, it); } pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 5, msg.is_event); #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES @@ -1185,9 +1233,8 @@ void HomeassistantActionResponse::decode_field(void *self, uint32_t tag, const u #endif #ifdef USE_API_HOMEASSISTANT_STATES uint8_t *SubscribeHomeAssistantStateResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.entity_id); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.attribute); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.once); @@ -1274,9 +1321,9 @@ void GetTimeResponse::decode_field(void *self, uint32_t tag, const uint8_t *data } } #ifdef USE_API_USER_DEFINED_ACTIONS -uint8_t *ListEntitiesServicesArgument::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesServicesArgument::encode_msg(const void *self, + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.name); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(msg.type)); #ifdef USE_API_USER_DEFINED_ACTION_METADATA @@ -1300,13 +1347,13 @@ uint32_t ListEntitiesServicesArgument::calc_size_msg(const void *self) { #endif return size; } -uint8_t *ListEntitiesServicesResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesServicesResponse::encode_msg(const void *self, + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.name); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); for (auto &it : msg.args) { - pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 3, it); + pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 3, it); } pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, static_cast(msg.supports_response)); #ifdef USE_API_USER_DEFINED_ACTION_METADATA @@ -1404,9 +1451,8 @@ void ExecuteServiceRequest::decode(const uint8_t *buffer, size_t length) { } #endif #ifdef USE_API_USER_DEFINED_ACTION_RESPONSES -uint8_t *ExecuteServiceResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ExecuteServiceResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.call_id); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.success); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.error_message); @@ -1428,9 +1474,8 @@ uint32_t ExecuteServiceResponse::calc_size_msg(const void *self) { } #endif #ifdef USE_CAMERA -uint8_t *ListEntitiesCameraResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesCameraResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -1460,9 +1505,8 @@ uint32_t ListEntitiesCameraResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *CameraImageResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *CameraImageResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.data_ptr_, msg.data_len_); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.done); @@ -1496,9 +1540,8 @@ void CameraImageRequest::decode_field(void *self, uint32_t tag, const uint8_t *d } #endif #ifdef USE_CLIMATE -uint8_t *ListEntitiesClimateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesClimateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -1600,9 +1643,8 @@ uint32_t ListEntitiesClimateResponse::calc_size_msg(const void *self) { size += msg.temperature_unit ? 3 : 0; return size; } -uint8_t *ClimateStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ClimateStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(msg.mode)); if (uint32_t raw = float_to_raw(msg.current_temperature); raw != 0) [[likely]] { @@ -1735,9 +1777,8 @@ void ClimateCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t #endif #ifdef USE_WATER_HEATER uint8_t *ListEntitiesWaterHeaterResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -1789,9 +1830,8 @@ uint32_t ListEntitiesWaterHeaterResponse::calc_size_msg(const void *self) { size += msg.temperature_unit ? 2 : 0; return size; } -uint8_t *WaterHeaterStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *WaterHeaterStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); if (uint32_t raw = float_to_raw(msg.current_temperature); raw != 0) [[likely]] { pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, raw); @@ -1864,9 +1904,8 @@ void WaterHeaterCommandRequest::decode_field(void *self, uint32_t tag, const uin } #endif #ifdef USE_NUMBER -uint8_t *ListEntitiesNumberResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesNumberResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -1914,9 +1953,8 @@ uint32_t ListEntitiesNumberResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *NumberStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *NumberStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); if (uint32_t raw = float_to_raw(msg.state); raw != 0) [[likely]] { pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, raw); @@ -1957,9 +1995,8 @@ void NumberCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t } #endif #ifdef USE_SELECT -uint8_t *ListEntitiesSelectResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesSelectResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -1997,9 +2034,8 @@ uint32_t ListEntitiesSelectResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *SelectStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *SelectStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.state); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.missing_state); @@ -2038,9 +2074,8 @@ void SelectCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t } #endif #ifdef USE_SIREN -uint8_t *ListEntitiesSirenResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesSirenResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -2082,9 +2117,8 @@ uint32_t ListEntitiesSirenResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *SirenStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *SirenStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.state); #ifdef USE_DEVICES @@ -2142,9 +2176,8 @@ void SirenCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t * } #endif #ifdef USE_LOCK -uint8_t *ListEntitiesLockResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesLockResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -2182,9 +2215,8 @@ uint32_t ListEntitiesLockResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *LockStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *LockStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(msg.state)); #ifdef USE_DEVICES @@ -2227,9 +2259,8 @@ void LockCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *d } #endif #ifdef USE_BUTTON -uint8_t *ListEntitiesButtonResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesButtonResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -2277,9 +2308,8 @@ void ButtonCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t } #endif #ifdef USE_MEDIA_PLAYER -uint8_t *MediaPlayerSupportedFormat::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *MediaPlayerSupportedFormat::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.format); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.sample_rate); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.num_channels); @@ -2298,9 +2328,8 @@ uint32_t MediaPlayerSupportedFormat::calc_size_msg(const void *self) { return size; } uint8_t *ListEntitiesMediaPlayerResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -2310,7 +2339,7 @@ uint8_t *ListEntitiesMediaPlayerResponse::encode_msg(const void *self, pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 6, msg.disabled_by_default); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, static_cast(msg.entity_category)); for (auto &it : msg.supported_formats) { - pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 9, it); + pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 9, it); } #ifdef USE_DEVICES pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.device_id); @@ -2340,9 +2369,8 @@ uint32_t ListEntitiesMediaPlayerResponse::calc_size_msg(const void *self) { size += ProtoSize::calc_uint32(1, msg.feature_flags); return size; } -uint8_t *MediaPlayerStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *MediaPlayerStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(msg.state)); if (uint32_t raw = float_to_raw(msg.volume); raw != 0) [[likely]] { @@ -2419,9 +2447,8 @@ void SubscribeBluetoothLEAdvertisementsRequest::decode_field(void *self, uint32_ } __attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes) uint8_t * -BluetoothLERawAdvertisementsResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +BluetoothLERawAdvertisementsResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); for (uint16_t i = 0; i < msg.advertisements_len; i++) { auto &sub_msg = msg.advertisements[i]; pos = ProtoEncode::write_raw_byte(pos PROTO_ENCODE_DEBUG_ARG, 10); @@ -2478,9 +2505,8 @@ void BluetoothDeviceRequest::decode_field(void *self, uint32_t tag, const uint8_ } } uint8_t *BluetoothDeviceConnectionResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.address); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.connected); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.mtu); @@ -2506,9 +2532,8 @@ void BluetoothGATTGetServicesRequest::decode_field(void *self, uint32_t tag, con break; } } -uint8_t *BluetoothGATTDescriptor::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *BluetoothGATTDescriptor::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); if (msg.uuid[0] != 0 || msg.uuid[1] != 0) { pos = ProtoEncode::encode_uint64_force(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.uuid[0]); pos = ProtoEncode::encode_uint64_force(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.uuid[1]); @@ -2528,9 +2553,8 @@ uint32_t BluetoothGATTDescriptor::calc_size_msg(const void *self) { size += ProtoSize::calc_uint32(1, msg.short_uuid); return size; } -uint8_t *BluetoothGATTCharacteristic::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *BluetoothGATTCharacteristic::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); if (msg.uuid[0] != 0 || msg.uuid[1] != 0) { pos = ProtoEncode::encode_uint64_force(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.uuid[0]); pos = ProtoEncode::encode_uint64_force(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.uuid[1]); @@ -2538,7 +2562,7 @@ uint8_t *BluetoothGATTCharacteristic::encode_msg(const void *self, ProtoWriteBuf pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.handle); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.properties); for (auto &it : msg.descriptors) { - pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 4, it); + pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 4, it); } pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 5, msg.short_uuid); return pos; @@ -2560,16 +2584,15 @@ uint32_t BluetoothGATTCharacteristic::calc_size_msg(const void *self) { size += ProtoSize::calc_uint32(1, msg.short_uuid); return size; } -uint8_t *BluetoothGATTService::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *BluetoothGATTService::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); if (msg.uuid[0] != 0 || msg.uuid[1] != 0) { pos = ProtoEncode::encode_uint64_force(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.uuid[0]); pos = ProtoEncode::encode_uint64_force(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.uuid[1]); } pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.handle); for (auto &it : msg.characteristics) { - pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 3, it); + pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 3, it); } pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, msg.short_uuid); return pos; @@ -2591,12 +2614,11 @@ uint32_t BluetoothGATTService::calc_size_msg(const void *self) { return size; } uint8_t *BluetoothGATTGetServicesResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.address); for (auto &it : msg.services) { - pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 2, it); + pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 2, it); } return pos; } @@ -2612,9 +2634,8 @@ uint32_t BluetoothGATTGetServicesResponse::calc_size_msg(const void *self) { return size; } uint8_t *BluetoothGATTGetServicesDoneResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.address); return pos; } @@ -2637,9 +2658,8 @@ void BluetoothGATTReadRequest::decode_field(void *self, uint32_t tag, const uint break; } } -uint8_t *BluetoothGATTReadResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *BluetoothGATTReadResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.address); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.handle); pos = ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.data_ptr_, msg.data_len_); @@ -2720,9 +2740,8 @@ void BluetoothGATTNotifyRequest::decode_field(void *self, uint32_t tag, const ui } } uint8_t *BluetoothGATTNotifyDataResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.address); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.handle); pos = ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.data_ptr_, msg.data_len_); @@ -2737,9 +2756,8 @@ uint32_t BluetoothGATTNotifyDataResponse::calc_size_msg(const void *self) { return size; } uint8_t *BluetoothConnectionsFreeResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.free); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.limit); for (const auto &it : msg.allocated) { @@ -2761,9 +2779,8 @@ uint32_t BluetoothConnectionsFreeResponse::calc_size_msg(const void *self) { } return size; } -uint8_t *BluetoothGATTErrorResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *BluetoothGATTErrorResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.address); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.handle); pos = ProtoEncode::encode_int32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.error); @@ -2777,9 +2794,8 @@ uint32_t BluetoothGATTErrorResponse::calc_size_msg(const void *self) { size += ProtoSize::calc_int32(1, msg.error); return size; } -uint8_t *BluetoothGATTWriteResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *BluetoothGATTWriteResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.address); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.handle); return pos; @@ -2791,9 +2807,8 @@ uint32_t BluetoothGATTWriteResponse::calc_size_msg(const void *self) { size += ProtoSize::calc_uint32(1, msg.handle); return size; } -uint8_t *BluetoothGATTNotifyResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *BluetoothGATTNotifyResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.address); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.handle); return pos; @@ -2806,9 +2821,8 @@ uint32_t BluetoothGATTNotifyResponse::calc_size_msg(const void *self) { return size; } uint8_t *BluetoothDevicePairingResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.address); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.paired); pos = ProtoEncode::encode_int32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.error); @@ -2823,9 +2837,8 @@ uint32_t BluetoothDevicePairingResponse::calc_size_msg(const void *self) { return size; } uint8_t *BluetoothDeviceUnpairingResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.address); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.success); pos = ProtoEncode::encode_int32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.error); @@ -2840,9 +2853,8 @@ uint32_t BluetoothDeviceUnpairingResponse::calc_size_msg(const void *self) { return size; } uint8_t *BluetoothDeviceClearCacheResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.address); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.success); pos = ProtoEncode::encode_int32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.error); @@ -2859,9 +2871,8 @@ uint32_t BluetoothDeviceClearCacheResponse::calc_size_msg(const void *self) { #endif #ifdef USE_BLUETOOTH_PROXY uint8_t *BluetoothScannerStateResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, static_cast(msg.state)); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(msg.mode)); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, static_cast(msg.configured_mode)); @@ -2900,9 +2911,8 @@ void SubscribeVoiceAssistantRequest::decode_field(void *self, uint32_t tag, cons break; } } -uint8_t *VoiceAssistantAudioSettings::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *VoiceAssistantAudioSettings::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.noise_suppression_level); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.auto_gain); if (uint32_t raw = float_to_raw(msg.volume_multiplier); raw != 0) [[likely]] { @@ -2918,13 +2928,12 @@ uint32_t VoiceAssistantAudioSettings::calc_size_msg(const void *self) { size += ProtoSize::calc_float(1, msg.volume_multiplier); return size; } -uint8_t *VoiceAssistantRequest::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *VoiceAssistantRequest::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.start); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.conversation_id); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.flags); - pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 4, msg.audio_settings); + pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 4, msg.audio_settings); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, msg.wake_word_phrase); return pos; } @@ -2993,9 +3002,8 @@ void VoiceAssistantAudio::decode_field(void *self, uint32_t tag, const uint8_t * break; } } -uint8_t *VoiceAssistantAudio::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *VoiceAssistantAudio::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.data, msg.data_len); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.end); pos = ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.data2, msg.data2_len); @@ -3054,9 +3062,8 @@ void VoiceAssistantAnnounceRequest::decode_field(void *self, uint32_t tag, const } } uint8_t *VoiceAssistantAnnounceFinished::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.success); return pos; } @@ -3066,9 +3073,8 @@ uint32_t VoiceAssistantAnnounceFinished::calc_size_msg(const void *self) { size += ProtoSize::calc_bool(1, msg.success); return size; } -uint8_t *VoiceAssistantWakeWord::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *VoiceAssistantWakeWord::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.id); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.wake_word); for (auto &it : msg.trained_languages) { @@ -3128,11 +3134,10 @@ void VoiceAssistantConfigurationRequest::decode_field(void *self, uint32_t tag, } } uint8_t *VoiceAssistantConfigurationResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); for (auto &it : msg.available_wake_words) { - pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, buffer, 1, it); + pos = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 1, it); } for (const auto &it : *msg.active_wake_words) { pos = ProtoEncode::encode_string_force(pos PROTO_ENCODE_DEBUG_ARG, 2, it); @@ -3169,9 +3174,8 @@ void VoiceAssistantSetConfiguration::decode_field(void *self, uint32_t tag, cons #endif #ifdef USE_ALARM_CONTROL_PANEL uint8_t *ListEntitiesAlarmControlPanelResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -3208,9 +3212,8 @@ uint32_t ListEntitiesAlarmControlPanelResponse::calc_size_msg(const void *self) return size; } uint8_t *AlarmControlPanelStateResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(msg.state)); #ifdef USE_DEVICES @@ -3251,9 +3254,8 @@ void AlarmControlPanelCommandRequest::decode_field(void *self, uint32_t tag, con } #endif #ifdef USE_TEXT -uint8_t *ListEntitiesTextResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesTextResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -3291,9 +3293,8 @@ uint32_t ListEntitiesTextResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *TextStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *TextStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.state); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.missing_state); @@ -3332,9 +3333,8 @@ void TextCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *d } #endif #ifdef USE_DATETIME_DATE -uint8_t *ListEntitiesDateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesDateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -3364,9 +3364,8 @@ uint32_t ListEntitiesDateResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *DateStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *DateStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.missing_state); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.year); @@ -3415,9 +3414,8 @@ void DateCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *d } #endif #ifdef USE_DATETIME_TIME -uint8_t *ListEntitiesTimeResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesTimeResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -3447,9 +3445,8 @@ uint32_t ListEntitiesTimeResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *TimeStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *TimeStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.missing_state); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.hour); @@ -3498,9 +3495,8 @@ void TimeCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t *d } #endif #ifdef USE_EVENT -uint8_t *ListEntitiesEventResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesEventResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -3540,9 +3536,8 @@ uint32_t ListEntitiesEventResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *EventResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *EventResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.event_type); #ifdef USE_DEVICES @@ -3562,9 +3557,8 @@ uint32_t EventResponse::calc_size_msg(const void *self) { } #endif #ifdef USE_VALVE -uint8_t *ListEntitiesValveResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesValveResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -3602,9 +3596,8 @@ uint32_t ListEntitiesValveResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *ValveStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ValveStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); if (uint32_t raw = float_to_raw(msg.position); raw != 0) [[likely]] { pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, raw); @@ -3651,9 +3644,9 @@ void ValveCommandRequest::decode_field(void *self, uint32_t tag, const uint8_t * } #endif #ifdef USE_DATETIME_DATETIME -uint8_t *ListEntitiesDateTimeResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesDateTimeResponse::encode_msg(const void *self, + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -3683,9 +3676,8 @@ uint32_t ListEntitiesDateTimeResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *DateTimeStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *DateTimeStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.missing_state); if (uint32_t raw = msg.epoch_seconds; raw != 0) [[likely]] { @@ -3726,9 +3718,8 @@ void DateTimeCommandRequest::decode_field(void *self, uint32_t tag, const uint8_ } #endif #ifdef USE_UPDATE -uint8_t *ListEntitiesUpdateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesUpdateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -3760,9 +3751,8 @@ uint32_t ListEntitiesUpdateResponse::calc_size_msg(const void *self) { #endif return size; } -uint8_t *UpdateStateResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *UpdateStateResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 13, msg.key); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.missing_state); pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.in_progress); @@ -3829,9 +3819,8 @@ void ZWaveProxyFrame::decode_field(void *self, uint32_t tag, const uint8_t *data } __attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes) uint8_t * -ZWaveProxyFrame::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +ZWaveProxyFrame::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.data, msg.data_len); return pos; } @@ -3856,9 +3845,8 @@ void ZWaveProxyRequest::decode_field(void *self, uint32_t tag, const uint8_t *da break; } } -uint8_t *ZWaveProxyRequest::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ZWaveProxyRequest::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, static_cast(msg.type)); pos = ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.data, msg.data_len); return pos; @@ -3870,9 +3858,8 @@ uint32_t ZWaveProxyRequest::calc_size_msg(const void *self) { size += ProtoSize::calc_length(1, msg.data_len); return size; } -uint8_t *ZWaveProxyRequestResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ZWaveProxyRequestResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, static_cast(msg.type)); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(msg.status)); return pos; @@ -3886,9 +3873,9 @@ uint32_t ZWaveProxyRequestResponse::calc_size_msg(const void *self) { } #endif #ifdef USE_INFRARED -uint8_t *ListEntitiesInfraredResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *ListEntitiesInfraredResponse::encode_msg(const void *self, + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -3923,7 +3910,7 @@ uint32_t ListEntitiesInfraredResponse::calc_size_msg(const void *self) { return size; } #endif -#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY) +#ifdef USE_IR_RF void InfraredRFTransmitRawTimingsRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar) { auto &msg = *static_cast(self); @@ -3955,9 +3942,8 @@ void InfraredRFTransmitRawTimingsRequest::decode_field(void *self, uint32_t tag, } __attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes) uint8_t * -InfraredRFReceiveEvent::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +InfraredRFReceiveEvent::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); #ifdef USE_DEVICES pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.device_id); #endif @@ -3983,12 +3969,31 @@ InfraredRFReceiveEvent::calc_size_msg(const void *self) { } return size; } +uint8_t *InfraredRFTransmitCompleteResponse::encode_msg(const void *self, + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { + const auto &msg = *static_cast(self); +#ifdef USE_DEVICES + pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.device_id); +#endif + pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); + pos = ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.success); + return pos; +} +uint32_t InfraredRFTransmitCompleteResponse::calc_size_msg(const void *self) { + const auto &msg = *static_cast(self); + uint32_t size = 0; +#ifdef USE_DEVICES + size += ProtoSize::calc_uint32(1, msg.device_id); +#endif + size += 5; + size += ProtoSize::calc_bool(1, msg.success); + return size; +} #endif #ifdef USE_RADIO_FREQUENCY uint8_t *ListEntitiesRadioFrequencyResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, msg.object_id); pos = ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, msg.key); pos = ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, msg.name); @@ -4055,9 +4060,8 @@ void SerialProxyConfigureRequest::decode_field(void *self, uint32_t tag, const u } __attribute__((optimize("O2"))) // NOLINT(clang-diagnostic-unknown-attributes) uint8_t * -SerialProxyDataReceived::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +SerialProxyDataReceived::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.instance); pos = ProtoEncode::encode_bytes(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.data_ptr_, msg.data_len_); return pos; @@ -4108,9 +4112,8 @@ void SerialProxyGetModemPinsRequest::decode_field(void *self, uint32_t tag, cons } } uint8_t *SerialProxyGetModemPinsResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.instance); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.line_states); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, static_cast(msg.status)); @@ -4136,9 +4139,8 @@ void SerialProxyRequest::decode_field(void *self, uint32_t tag, const uint8_t *d break; } } -uint8_t *SerialProxyRequestResponse::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { +uint8_t *SerialProxyRequestResponse::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.instance); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(msg.type)); pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, static_cast(msg.status)); @@ -4167,6 +4169,46 @@ void SerialProxySetModeRequest::decode_field(void *self, uint32_t tag, const uin break; } } +uint8_t *UsbDeviceDescriptor::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { + const auto &msg = *static_cast(self); + pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.vendor_id); + pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.product_id); + pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.bcd_device); + pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, msg.interface_number); + return pos; +} +uint32_t UsbDeviceDescriptor::calc_size_msg(const void *self) { + const auto &msg = *static_cast(self); + uint32_t size = 0; + size += ProtoSize::calc_uint32(1, msg.vendor_id); + size += ProtoSize::calc_uint32(1, msg.product_id); + size += ProtoSize::calc_uint32(1, msg.bcd_device); + size += ProtoSize::calc_uint32(1, msg.interface_number); + return size; +} +uint8_t *SerialProxyIdentity::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { + const auto &msg = *static_cast(self); + pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.instance); + pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 2, static_cast(msg.source)); + pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 3, msg.flags); + pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 4, msg.manufacturer); + pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 5, msg.product); + pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 6, msg.serial_number); + pos = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, 7, msg.usb); + return pos; +} +uint32_t SerialProxyIdentity::calc_size_msg(const void *self) { + const auto &msg = *static_cast(self); + uint32_t size = 0; + size += ProtoSize::calc_uint32(1, msg.instance); + size += msg.source ? 2 : 0; + size += ProtoSize::calc_uint32(1, msg.flags); + size += ProtoSize::calc_length(1, msg.manufacturer.size()); + size += ProtoSize::calc_length(1, msg.product.size()); + size += ProtoSize::calc_length(1, msg.serial_number.size()); + size += ProtoSize::calc_message(1, msg.usb.calculate_size()); + return size; +} #endif #ifdef USE_BLUETOOTH_PROXY_CONNECTIONS void BluetoothSetConnectionParamsRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, @@ -4192,9 +4234,8 @@ void BluetoothSetConnectionParamsRequest::decode_field(void *self, uint32_t tag, } } uint8_t *BluetoothSetConnectionParamsResponse::encode_msg(const void *self, - ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { const auto &msg = *static_cast(self); - uint8_t *__restrict__ pos = buffer.get_pos(); pos = ProtoEncode::encode_uint64(pos PROTO_ENCODE_DEBUG_ARG, 1, msg.address); pos = ProtoEncode::encode_int32(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.error); return pos; @@ -4207,9 +4248,28 @@ uint32_t BluetoothSetConnectionParamsResponse::calc_size_msg(const void *self) { return size; } #endif +#ifdef USE_SENDSPIN +uint8_t *SendspinPairingTokenResponse::encode_msg(const void *self, + uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) { + const auto &msg = *static_cast(self); + pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 1, static_cast(msg.status)); + pos = ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 2, msg.token); + return pos; +} +uint32_t SendspinPairingTokenResponse::calc_size_msg(const void *self) { + const auto &msg = *static_cast(self); + uint32_t size = 0; + size += msg.status ? 2 : 0; + size += !msg.token.empty() ? 2 + msg.token.size() : 0; + return size; +} +#endif #ifndef HAS_PROTO_MESSAGE_DUMP static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#ifdef USE_API_WIZARD_INPUTS +static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); +#endif #ifdef USE_COVER static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); #endif @@ -4318,7 +4378,7 @@ static_assert(!std::is_polymorphic_v, "decodable messages static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); #endif -#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY) +#ifdef USE_IR_RF static_assert(!std::is_polymorphic_v, "decodable messages carry no vtable"); #endif #ifdef USE_SERIAL_PROXY diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index dbf0fb49a1e..17436d33aab 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -23,6 +23,7 @@ enum SerialProxyPortType : uint32_t { SERIAL_PROXY_PORT_TYPE_TTL = 0, SERIAL_PROXY_PORT_TYPE_RS232 = 1, SERIAL_PROXY_PORT_TYPE_RS485 = 2, + SERIAL_PROXY_PORT_TYPE_USB_SERIAL = 3, }; enum EntityCategory : uint32_t { ENTITY_CATEGORY_NONE = 0, @@ -371,6 +372,25 @@ enum SerialProxyMode : uint32_t { SERIAL_PROXY_MODE_RAW = 0, SERIAL_PROXY_MODE_PROTOCOL = 1, }; +enum SerialProxyIdentitySource : uint32_t { + SERIAL_PROXY_IDENTITY_SOURCE_NONE = 0, + SERIAL_PROXY_IDENTITY_SOURCE_CONFIGURED = 1, + SERIAL_PROXY_IDENTITY_SOURCE_USB = 2, +}; +#endif +enum SerialProxyIdentityFlag : uint32_t { + SERIAL_PROXY_IDENTITY_FLAG_NONE = 0, + SERIAL_PROXY_IDENTITY_FLAG_CONNECTED = 1, + SERIAL_PROXY_IDENTITY_FLAG_ERROR = 2, +}; +#ifdef USE_SENDSPIN +enum SendspinPairingTokenStatus : uint32_t { + SENDSPIN_PAIRING_TOKEN_STATUS_NOT_READY = 0, + SENDSPIN_PAIRING_TOKEN_STATUS_OK = 1, + SENDSPIN_PAIRING_TOKEN_STATUS_ENCRYPTION_REQUIRED = 2, + SENDSPIN_PAIRING_TOKEN_STATUS_DISABLED = 3, + SENDSPIN_PAIRING_TOKEN_STATUS_FAILED = 4, +}; #endif } // namespace enums @@ -417,13 +437,16 @@ class CommandProtoMessage : public ProtoDecodableMessage { class HelloRequest final : public ProtoDecodableMessage { public: static constexpr uint16_t MESSAGE_TYPE = 1; - static constexpr uint8_t ESTIMATED_SIZE = 17; + static constexpr uint8_t ESTIMATED_SIZE = 19; #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("hello_request"); } #endif StringRef client_info{}; uint32_t api_version_major{0}; uint32_t api_version_minor{0}; +#ifdef USE_API_OUTGOING_CONNECTION + bool outgoing_connection_target{false}; +#endif void decode(const uint8_t *buffer, size_t length) { ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); } @@ -445,9 +468,9 @@ class HelloResponse final : public ProtoMessage { uint32_t api_version_minor{0}; StringRef server_info{}; StringRef name{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -468,9 +491,9 @@ class DisconnectRequest final : public ProtoDecodableMessage { void decode(const uint8_t *buffer, size_t length) { ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); } - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -525,9 +548,9 @@ class AreaInfo final : public ProtoMessage { public: uint32_t area_id{0}; StringRef name{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -544,9 +567,9 @@ class DeviceInfo final : public ProtoMessage { uint32_t device_id{0}; StringRef name{}; uint32_t area_id{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -563,9 +586,9 @@ class SerialProxyInfo final : public ProtoMessage { StringRef name{nullptr, 0}; // null until set, encode only enums::SerialProxyPortType port_type{}; uint32_t configured_line_states{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -579,7 +602,7 @@ class SerialProxyInfo final : public ProtoMessage { class DeviceInfoResponse final : public ProtoMessage { public: static constexpr uint16_t MESSAGE_TYPE = 10; - static constexpr uint16_t ESTIMATED_SIZE = 312; + static constexpr uint16_t ESTIMATED_SIZE = 315; #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("device_info_response"); } #endif @@ -638,9 +661,12 @@ class DeviceInfoResponse final : public ProtoMessage { #ifdef USE_API_NOISE bool api_encryption_provisionable{false}; #endif - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); +#ifdef USE_API_OUTGOING_CONNECTION + bool api_outgoing_connection_supported{false}; +#endif + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -655,9 +681,9 @@ class BluetoothProxyCapabilities final : public ProtoMessage { public: uint32_t feature_flags{0}; StringRef mac_address{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -672,9 +698,9 @@ class BluetoothProxyCapabilities final : public ProtoMessage { class VoiceAssistantCapabilities final : public ProtoMessage { public: uint32_t feature_flags{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -690,9 +716,43 @@ class ZWaveProxyCapabilities final : public ProtoMessage { public: uint32_t feature_flags{0}; uint32_t home_id{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); + } + static uint32_t calc_size_msg(const void *self); + uint32_t calculate_size() const { return calc_size_msg(this); } +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *dump_to(DumpBuffer &out) const override; +#endif + + protected: +}; +#endif +#ifdef USE_API_WIZARD +class WizardCapabilities final : public ProtoMessage { + public: + bool configured{false}; + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); + } + static uint32_t calc_size_msg(const void *self); + uint32_t calculate_size() const { return calc_size_msg(this); } +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *dump_to(DumpBuffer &out) const override; +#endif + + protected: +}; +#endif +#ifdef USE_SENDSPIN +class SendspinCapabilities final : public ProtoMessage { + public: + uint32_t feature_flags{0}; + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -706,7 +766,7 @@ class ZWaveProxyCapabilities final : public ProtoMessage { class DeviceCapabilitiesResponse final : public ProtoMessage { public: static constexpr uint16_t MESSAGE_TYPE = 150; - static constexpr uint8_t ESTIMATED_SIZE = 102; + static constexpr uint8_t ESTIMATED_SIZE = 136; #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("device_capabilities_response"); } #endif @@ -722,9 +782,15 @@ class DeviceCapabilitiesResponse final : public ProtoMessage { #ifdef USE_SERIAL_PROXY std::array serial_proxies{}; #endif - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); +#ifdef USE_API_WIZARD + WizardCapabilities wizard{}; +#endif +#ifdef USE_SENDSPIN + SendspinCapabilities sendspin{}; +#endif + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -734,6 +800,50 @@ class DeviceCapabilitiesResponse final : public ProtoMessage { protected: }; +#ifdef USE_API_WIZARD +class DeviceWizardResponse final : public ProtoMessage { + public: + static constexpr uint16_t MESSAGE_TYPE = 157; + static constexpr uint8_t ESTIMATED_SIZE = 19; +#ifdef HAS_PROTO_MESSAGE_DUMP + const LogString *message_name() const override { return LOG_STR("device_wizard_response"); } +#endif + const uint8_t *data{nullptr}; + uint16_t data_len{0}; + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); + } + static uint32_t calc_size_msg(const void *self); + uint32_t calculate_size() const { return calc_size_msg(this); } +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *dump_to(DumpBuffer &out) const override; +#endif + + protected: +}; +#endif +#ifdef USE_API_WIZARD_INPUTS +class WizardInputSetRequest final : public ProtoDecodableMessage { + public: + static constexpr uint16_t MESSAGE_TYPE = 158; + static constexpr uint8_t ESTIMATED_SIZE = 14; +#ifdef HAS_PROTO_MESSAGE_DUMP + const LogString *message_name() const override { return LOG_STR("wizard_input_set_request"); } +#endif + uint32_t key{0}; + StringRef entity_id{}; + void decode(const uint8_t *buffer, size_t length) { + ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); + } +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *dump_to(DumpBuffer &out) const override; +#endif + + protected: + static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); +}; +#endif class ListEntitiesDoneResponse final : public ProtoMessage { public: static constexpr uint16_t MESSAGE_TYPE = 19; @@ -757,9 +867,9 @@ class ListEntitiesBinarySensorResponse final : public InfoResponseProtoMessage { #endif StringRef device_class{nullptr, 0}; // null until set, encode only bool is_status_binary_sensor{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -778,9 +888,9 @@ class BinarySensorStateResponse final : public StateResponseProtoMessage { #endif bool state{false}; bool missing_state{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -804,9 +914,9 @@ class ListEntitiesCoverResponse final : public InfoResponseProtoMessage { bool supports_tilt{false}; StringRef device_class{nullptr, 0}; // null until set, encode only bool supports_stop{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -826,9 +936,9 @@ class CoverStateResponse final : public StateResponseProtoMessage { float position{0.0f}; float tilt{0.0f}; enums::CoverOperation current_operation{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -874,9 +984,9 @@ class ListEntitiesFanResponse final : public InfoResponseProtoMessage { bool supports_direction{false}; int32_t supported_speed_count{0}; const std::vector *supported_preset_modes{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -898,9 +1008,9 @@ class FanStateResponse final : public StateResponseProtoMessage { enums::FanDirection direction{}; int32_t speed_level{0}; StringRef preset_mode{nullptr, 0}; // null until set, encode only - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -949,10 +1059,10 @@ class ListEntitiesLightResponse final : public InfoResponseProtoMessage { const light::ColorModeMask *supported_color_modes{}; float min_mireds{0.0f}; float max_mireds{0.0f}; - const FixedVector *effects{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + const light::LightEffectNames *effects{}; + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -981,9 +1091,9 @@ class LightStateResponse final : public StateResponseProtoMessage { float cold_white{0.0f}; float warm_white{0.0f}; StringRef effect{nullptr, 0}; // null until set, encode only - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1050,9 +1160,9 @@ class ListEntitiesSensorResponse final : public InfoResponseProtoMessage { bool force_update{false}; StringRef device_class{nullptr, 0}; // null until set, encode only enums::SensorStateClass state_class{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1071,9 +1181,9 @@ class SensorStateResponse final : public StateResponseProtoMessage { #endif float state{0.0f}; bool missing_state{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1094,9 +1204,9 @@ class ListEntitiesSwitchResponse final : public InfoResponseProtoMessage { #endif bool assumed_state{false}; StringRef device_class{nullptr, 0}; // null until set, encode only - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1115,9 +1225,9 @@ class SwitchStateResponse final : public StateResponseProtoMessage { #endif bool state{false}; bool missing_state{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1155,9 +1265,9 @@ class ListEntitiesTextSensorResponse final : public InfoResponseProtoMessage { const LogString *message_name() const override { return LOG_STR("list_entities_text_sensor_response"); } #endif StringRef device_class{nullptr, 0}; // null until set, encode only - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1176,9 +1286,9 @@ class TextSensorStateResponse final : public StateResponseProtoMessage { #endif StringRef state{nullptr, 0}; // null until set, encode only bool missing_state{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1222,9 +1332,9 @@ class SubscribeLogsResponse final : public ProtoMessage { this->message_ptr_ = data; this->message_len_ = len; } - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1262,9 +1372,9 @@ class NoiseEncryptionSetKeyResponse final : public ProtoMessage { const LogString *message_name() const override { return LOG_STR("noise_encryption_set_key_response"); } #endif bool success{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1280,9 +1390,9 @@ class HomeassistantServiceMap final : public ProtoMessage { public: StringRef key{nullptr, 0}; // null until set, encode only StringRef value{nullptr, 0}; // null until set, encode only - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1313,9 +1423,9 @@ class HomeassistantActionRequest final : public ProtoMessage { #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON StringRef response_template{nullptr, 0}; // null until set, encode only #endif - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1363,9 +1473,9 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage { StringRef entity_id{nullptr, 0}; // null until set, encode only StringRef attribute{nullptr, 0}; // null until set, encode only bool once{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1474,9 +1584,9 @@ class ListEntitiesServicesArgument final : public ProtoMessage { #ifdef USE_API_USER_DEFINED_ACTION_METADATA StringRef example{nullptr, 0}; // null until set, encode only #endif - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1500,9 +1610,9 @@ class ListEntitiesServicesResponse final : public ProtoMessage { #ifdef USE_API_USER_DEFINED_ACTION_METADATA StringRef description{nullptr, 0}; // null until set, encode only #endif - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1570,9 +1680,9 @@ class ExecuteServiceResponse final : public ProtoMessage { const uint8_t *response_data{nullptr}; uint16_t response_data_len{0}; #endif - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1591,9 +1701,9 @@ class ListEntitiesCameraResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("list_entities_camera_response"); } #endif - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1617,9 +1727,9 @@ class CameraImageResponse final : public StateResponseProtoMessage { this->data_len_ = len; } bool done{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1676,9 +1786,9 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage { float visual_max_humidity{0.0f}; uint32_t feature_flags{0}; enums::TemperatureUnit temperature_unit{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1709,9 +1819,9 @@ class ClimateStateResponse final : public StateResponseProtoMessage { float current_humidity{0.0f}; float target_humidity{0.0f}; bool missing_state{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1773,9 +1883,9 @@ class ListEntitiesWaterHeaterResponse final : public InfoResponseProtoMessage { const water_heater::WaterHeaterModeMask *supported_modes{}; uint32_t supported_features{0}; enums::TemperatureUnit temperature_unit{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1799,9 +1909,9 @@ class WaterHeaterStateResponse final : public StateResponseProtoMessage { float target_temperature_low{0.0f}; float target_temperature_high{0.0f}; bool missing_state{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1849,9 +1959,9 @@ class ListEntitiesNumberResponse final : public InfoResponseProtoMessage { StringRef unit_of_measurement{nullptr, 0}; // null until set, encode only enums::NumberMode mode{}; StringRef device_class{nullptr, 0}; // null until set, encode only - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1870,9 +1980,9 @@ class NumberStateResponse final : public StateResponseProtoMessage { #endif float state{0.0f}; bool missing_state{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1909,10 +2019,10 @@ class ListEntitiesSelectResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("list_entities_select_response"); } #endif - const FixedVector *options{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + const std::span *options{}; + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1931,9 +2041,9 @@ class SelectStateResponse final : public StateResponseProtoMessage { #endif StringRef state{nullptr, 0}; // null until set, encode only bool missing_state{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1973,9 +2083,9 @@ class ListEntitiesSirenResponse final : public InfoResponseProtoMessage { const FixedVector *tones{}; bool supports_duration{false}; bool supports_volume{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -1993,9 +2103,9 @@ class SirenStateResponse final : public StateResponseProtoMessage { const LogString *message_name() const override { return LOG_STR("siren_state_response"); } #endif bool state{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2043,9 +2153,9 @@ class ListEntitiesLockResponse final : public InfoResponseProtoMessage { bool supports_open{false}; bool requires_code{false}; StringRef code_format{nullptr, 0}; // null until set, encode only - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2063,9 +2173,9 @@ class LockStateResponse final : public StateResponseProtoMessage { const LogString *message_name() const override { return LOG_STR("lock_state_response"); } #endif enums::LockState state{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2105,9 +2215,9 @@ class ListEntitiesButtonResponse final : public InfoResponseProtoMessage { const LogString *message_name() const override { return LOG_STR("list_entities_button_response"); } #endif StringRef device_class{nullptr, 0}; // null until set, encode only - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2143,9 +2253,9 @@ class MediaPlayerSupportedFormat final : public ProtoMessage { uint32_t num_channels{0}; enums::MediaPlayerFormatPurpose purpose{}; uint32_t sample_bytes{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2164,9 +2274,9 @@ class ListEntitiesMediaPlayerResponse final : public InfoResponseProtoMessage { #endif std::vector supported_formats{}; uint32_t feature_flags{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2186,9 +2296,9 @@ class MediaPlayerStateResponse final : public StateResponseProtoMessage { enums::MediaPlayerState state{}; float volume{0.0f}; bool muted{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2265,9 +2375,9 @@ class BluetoothLERawAdvertisementsResponse final : public ProtoMessage { #endif std::array advertisements{}; uint16_t advertisements_len{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2311,9 +2421,9 @@ class BluetoothDeviceConnectionResponse final : public ProtoMessage { bool connected{false}; uint32_t mtu{0}; int32_t error{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2346,9 +2456,9 @@ class BluetoothGATTDescriptor final : public ProtoMessage { std::array uuid{}; uint32_t handle{0}; uint32_t short_uuid{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2365,9 +2475,9 @@ class BluetoothGATTCharacteristic final : public ProtoMessage { uint32_t properties{0}; FixedVector descriptors{}; uint32_t short_uuid{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2383,9 +2493,9 @@ class BluetoothGATTService final : public ProtoMessage { uint32_t handle{0}; FixedVector characteristics{}; uint32_t short_uuid{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2404,9 +2514,9 @@ class BluetoothGATTGetServicesResponse final : public ProtoMessage { #endif uint64_t address{0}; std::vector services{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2424,9 +2534,9 @@ class BluetoothGATTGetServicesDoneResponse final : public ProtoMessage { const LogString *message_name() const override { return LOG_STR("bluetooth_gatt_get_services_done_response"); } #endif uint64_t address{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2470,9 +2580,9 @@ class BluetoothGATTReadResponse final : public ProtoMessage { this->data_ptr_ = data; this->data_len_ = len; } - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2579,9 +2689,9 @@ class BluetoothGATTNotifyDataResponse final : public ProtoMessage { this->data_ptr_ = data; this->data_len_ = len; } - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2601,9 +2711,9 @@ class BluetoothConnectionsFreeResponse final : public ProtoMessage { uint32_t free{0}; uint32_t limit{0}; std::array allocated{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2623,9 +2733,9 @@ class BluetoothGATTErrorResponse final : public ProtoMessage { uint64_t address{0}; uint32_t handle{0}; int32_t error{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2644,9 +2754,9 @@ class BluetoothGATTWriteResponse final : public ProtoMessage { #endif uint64_t address{0}; uint32_t handle{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2665,9 +2775,9 @@ class BluetoothGATTNotifyResponse final : public ProtoMessage { #endif uint64_t address{0}; uint32_t handle{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2687,9 +2797,9 @@ class BluetoothDevicePairingResponse final : public ProtoMessage { uint64_t address{0}; bool paired{false}; int32_t error{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2709,9 +2819,9 @@ class BluetoothDeviceUnpairingResponse final : public ProtoMessage { uint64_t address{0}; bool success{false}; int32_t error{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2731,9 +2841,9 @@ class BluetoothDeviceClearCacheResponse final : public ProtoMessage { uint64_t address{0}; bool success{false}; int32_t error{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2755,9 +2865,9 @@ class BluetoothScannerStateResponse final : public ProtoMessage { enums::BluetoothScannerState state{}; enums::BluetoothScannerMode mode{}; enums::BluetoothScannerMode configured_mode{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2811,9 +2921,9 @@ class VoiceAssistantAudioSettings final : public ProtoMessage { uint32_t noise_suppression_level{0}; uint32_t auto_gain{0}; float volume_multiplier{0.0f}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2835,9 +2945,9 @@ class VoiceAssistantRequest final : public ProtoMessage { uint32_t flags{0}; VoiceAssistantAudioSettings audio_settings{}; StringRef wake_word_phrase{nullptr, 0}; // null until set, encode only - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2914,9 +3024,9 @@ class VoiceAssistantAudio final : public ProtoDecodableMessage { void decode(const uint8_t *buffer, size_t length) { ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); } - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2979,9 +3089,9 @@ class VoiceAssistantAnnounceFinished final : public ProtoMessage { const LogString *message_name() const override { return LOG_STR("voice_assistant_announce_finished"); } #endif bool success{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -2996,9 +3106,9 @@ class VoiceAssistantWakeWord final : public ProtoMessage { StringRef id{nullptr, 0}; // null until set, encode only StringRef wake_word{nullptr, 0}; // null until set, encode only std::vector trained_languages{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3055,9 +3165,9 @@ class VoiceAssistantConfigurationResponse final : public ProtoMessage { std::vector available_wake_words{}; const std::vector *active_wake_words{}; uint32_t max_active_wake_words{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3097,9 +3207,9 @@ class ListEntitiesAlarmControlPanelResponse final : public InfoResponseProtoMess uint32_t supported_features{0}; bool requires_code{false}; bool requires_code_to_arm{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3117,9 +3227,9 @@ class AlarmControlPanelStateResponse final : public StateResponseProtoMessage { const LogString *message_name() const override { return LOG_STR("alarm_control_panel_state_response"); } #endif enums::AlarmControlPanelState state{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3161,9 +3271,9 @@ class ListEntitiesTextResponse final : public InfoResponseProtoMessage { uint32_t max_length{0}; StringRef pattern{nullptr, 0}; // null until set, encode only enums::TextMode mode{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3182,9 +3292,9 @@ class TextStateResponse final : public StateResponseProtoMessage { #endif StringRef state{nullptr, 0}; // null until set, encode only bool missing_state{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3221,9 +3331,9 @@ class ListEntitiesDateResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("list_entities_date_response"); } #endif - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3244,9 +3354,9 @@ class DateStateResponse final : public StateResponseProtoMessage { uint32_t year{0}; uint32_t month{0}; uint32_t day{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3285,9 +3395,9 @@ class ListEntitiesTimeResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("list_entities_time_response"); } #endif - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3308,9 +3418,9 @@ class TimeStateResponse final : public StateResponseProtoMessage { uint32_t hour{0}; uint32_t minute{0}; uint32_t second{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3351,9 +3461,9 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage { #endif StringRef device_class{nullptr, 0}; // null until set, encode only const FixedVector *event_types{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3371,9 +3481,9 @@ class EventResponse final : public StateResponseProtoMessage { const LogString *message_name() const override { return LOG_STR("event_response"); } #endif StringRef event_type{nullptr, 0}; // null until set, encode only - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3396,9 +3506,9 @@ class ListEntitiesValveResponse final : public InfoResponseProtoMessage { bool assumed_state{false}; bool supports_position{false}; bool supports_stop{false}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3417,9 +3527,9 @@ class ValveStateResponse final : public StateResponseProtoMessage { #endif float position{0.0f}; enums::ValveOperation current_operation{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3458,9 +3568,9 @@ class ListEntitiesDateTimeResponse final : public InfoResponseProtoMessage { #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("list_entities_date_time_response"); } #endif - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3479,9 +3589,9 @@ class DateTimeStateResponse final : public StateResponseProtoMessage { #endif bool missing_state{false}; uint32_t epoch_seconds{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3519,9 +3629,9 @@ class ListEntitiesUpdateResponse final : public InfoResponseProtoMessage { const LogString *message_name() const override { return LOG_STR("list_entities_update_response"); } #endif StringRef device_class{nullptr, 0}; // null until set, encode only - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3547,9 +3657,9 @@ class UpdateStateResponse final : public StateResponseProtoMessage { StringRef title{nullptr, 0}; // null until set, encode only StringRef release_summary{nullptr, 0}; // null until set, encode only StringRef release_url{nullptr, 0}; // null until set, encode only - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3591,9 +3701,9 @@ class ZWaveProxyFrame final : public ProtoDecodableMessage { void decode(const uint8_t *buffer, size_t length) { ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); } - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3617,9 +3727,9 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage { void decode(const uint8_t *buffer, size_t length) { ProtoDecodableMessage::decode_fields(this, buffer, length, &decode_field); } - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3639,9 +3749,9 @@ class ZWaveProxyRequestResponse final : public ProtoMessage { #endif enums::ZWaveProxyRequestType type{}; enums::ZWaveProxyStatus status{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3662,9 +3772,9 @@ class ListEntitiesInfraredResponse final : public InfoResponseProtoMessage { #endif uint32_t capabilities{0}; uint32_t receiver_frequency{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3675,7 +3785,7 @@ class ListEntitiesInfraredResponse final : public InfoResponseProtoMessage { protected: }; #endif -#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY) +#ifdef USE_IR_RF class InfraredRFTransmitRawTimingsRequest final : public ProtoDecodableMessage { public: static constexpr uint16_t MESSAGE_TYPE = 136; @@ -3715,9 +3825,33 @@ class InfraredRFReceiveEvent final : public ProtoMessage { #endif uint32_t key{0}; const std::vector *timings{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); + } + static uint32_t calc_size_msg(const void *self); + uint32_t calculate_size() const { return calc_size_msg(this); } +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *dump_to(DumpBuffer &out) const override; +#endif + + protected: +}; +class InfraredRFTransmitCompleteResponse final : public ProtoMessage { + public: + static constexpr uint16_t MESSAGE_TYPE = 153; + static constexpr uint8_t ESTIMATED_SIZE = 11; +#ifdef HAS_PROTO_MESSAGE_DUMP + const LogString *message_name() const override { return LOG_STR("infrared_rf_transmit_complete_response"); } +#endif +#ifdef USE_DEVICES + uint32_t device_id{0}; +#endif + uint32_t key{0}; + bool success{false}; + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3740,9 +3874,9 @@ class ListEntitiesRadioFrequencyResponse final : public InfoResponseProtoMessage uint32_t frequency_min{0}; uint32_t frequency_max{0}; uint32_t supported_modulations{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3791,9 +3925,9 @@ class SerialProxyDataReceived final : public ProtoMessage { this->data_ptr_ = data; this->data_len_ = len; } - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3870,9 +4004,9 @@ class SerialProxyGetModemPinsResponse final : public ProtoMessage { uint32_t instance{0}; uint32_t line_states{0}; enums::SerialProxyStatus status{}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3912,9 +4046,9 @@ class SerialProxyRequestResponse final : public ProtoMessage { enums::SerialProxyRequestType type{}; enums::SerialProxyStatus status{}; StringRef error_message{nullptr, 0}; // null until set, encode only - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } @@ -3943,6 +4077,50 @@ class SerialProxySetModeRequest final : public ProtoDecodableMessage { protected: static void decode_field(void *self, uint32_t tag, const uint8_t *data, proto_varint_value_t scalar); }; +class UsbDeviceDescriptor final : public ProtoMessage { + public: + uint32_t vendor_id{0}; + uint32_t product_id{0}; + uint32_t bcd_device{0}; + uint32_t interface_number{0}; + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); + } + static uint32_t calc_size_msg(const void *self); + uint32_t calculate_size() const { return calc_size_msg(this); } +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *dump_to(DumpBuffer &out) const override; +#endif + + protected: +}; +class SerialProxyIdentity final : public ProtoMessage { + public: + static constexpr uint16_t MESSAGE_TYPE = 155; + static constexpr uint8_t ESTIMATED_SIZE = 54; +#ifdef HAS_PROTO_MESSAGE_DUMP + const LogString *message_name() const override { return LOG_STR("serial_proxy_identity"); } +#endif + uint32_t instance{0}; + enums::SerialProxyIdentitySource source{}; + uint32_t flags{0}; + StringRef manufacturer{nullptr, 0}; // null until set, encode only + StringRef product{nullptr, 0}; // null until set, encode only + StringRef serial_number{nullptr, 0}; // null until set, encode only + UsbDeviceDescriptor usb{}; + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); + } + static uint32_t calc_size_msg(const void *self); + uint32_t calculate_size() const { return calc_size_msg(this); } +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *dump_to(DumpBuffer &out) const override; +#endif + + protected: +}; #endif #ifdef USE_BLUETOOTH_PROXY_CONNECTIONS class BluetoothSetConnectionParamsRequest final : public ProtoDecodableMessage { @@ -3976,9 +4154,32 @@ class BluetoothSetConnectionParamsResponse final : public ProtoMessage { #endif uint64_t address{0}; int32_t error{0}; - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM); + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { - return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG); + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); + } + static uint32_t calc_size_msg(const void *self); + uint32_t calculate_size() const { return calc_size_msg(this); } +#ifdef HAS_PROTO_MESSAGE_DUMP + const char *dump_to(DumpBuffer &out) const override; +#endif + + protected: +}; +#endif +#ifdef USE_SENDSPIN +class SendspinPairingTokenResponse final : public ProtoMessage { + public: + static constexpr uint16_t MESSAGE_TYPE = 160; + static constexpr uint8_t ESTIMATED_SIZE = 11; +#ifdef HAS_PROTO_MESSAGE_DUMP + const LogString *message_name() const override { return LOG_STR("sendspin_pairing_token_response"); } +#endif + enums::SendspinPairingTokenStatus status{}; + StringRef token{nullptr, 0}; // null until set, encode only + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM); + uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { + return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG); } static uint32_t calc_size_msg(const void *self); uint32_t calculate_size() const { return calc_size_msg(this); } diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index d854a0731be..3c6f5302b55 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -114,15 +114,26 @@ template static void dump_field(DumpBuffer &out, const char *field_n out.append("\n"); } +// Bytes shown by a bytes field dump: 160 bytes is 480 chars with separators, to fit a typical log buffer +static constexpr size_t DUMP_BYTES_MAX = 160; + // Helper for bytes fields - uses stack buffer to avoid heap allocation -// Buffer sized for 160 bytes of data (480 chars with separators) to fit typical log buffer // field_name is a PROGMEM pointer (flash on ESP8266, regular pointer on other platforms) static void dump_bytes_field(DumpBuffer &out, const char *field_name, const uint8_t *data, size_t len, int indent = 2) { - char hex_buf[format_hex_pretty_size(160)]; + char hex_buf[format_hex_pretty_size(DUMP_BYTES_MAX)]; append_field_prefix(out, field_name, indent); format_hex_pretty_to(hex_buf, data, len); out.append(hex_buf).append("\n"); } + +// Helper for bytes fields in flash: copies the shown bytes out with progmem_memcpy first +static void dump_progmem_bytes_field(DumpBuffer &out, const char *field_name, const uint8_t *data, size_t len, + int indent = 2) { + uint8_t data_buf[DUMP_BYTES_MAX]; + len = std::min(len, sizeof(data_buf)); + progmem_memcpy(data_buf, data, len); + dump_bytes_field(out, field_name, data_buf, len, indent); +} #pragma GCC diagnostic pop template<> const char *proto_enum_to_string(enums::DisconnectReason value) { @@ -143,6 +154,8 @@ template<> const char *proto_enum_to_string(enums::S return ESPHOME_PSTR("SERIAL_PROXY_PORT_TYPE_RS232"); case enums::SERIAL_PROXY_PORT_TYPE_RS485: return ESPHOME_PSTR("SERIAL_PROXY_PORT_TYPE_RS485"); + case enums::SERIAL_PROXY_PORT_TYPE_USB_SERIAL: + return ESPHOME_PSTR("SERIAL_PROXY_PORT_TYPE_USB_SERIAL"); default: return ESPHOME_PSTR("UNKNOWN"); } @@ -890,6 +903,49 @@ template<> const char *proto_enum_to_string(enums::Seria return ESPHOME_PSTR("UNKNOWN"); } } +template<> const char *proto_enum_to_string(enums::SerialProxyIdentitySource value) { + switch (value) { + case enums::SERIAL_PROXY_IDENTITY_SOURCE_NONE: + return ESPHOME_PSTR("SERIAL_PROXY_IDENTITY_SOURCE_NONE"); + case enums::SERIAL_PROXY_IDENTITY_SOURCE_CONFIGURED: + return ESPHOME_PSTR("SERIAL_PROXY_IDENTITY_SOURCE_CONFIGURED"); + case enums::SERIAL_PROXY_IDENTITY_SOURCE_USB: + return ESPHOME_PSTR("SERIAL_PROXY_IDENTITY_SOURCE_USB"); + default: + return ESPHOME_PSTR("UNKNOWN"); + } +} +#endif +template<> const char *proto_enum_to_string(enums::SerialProxyIdentityFlag value) { + switch (value) { + case enums::SERIAL_PROXY_IDENTITY_FLAG_NONE: + return ESPHOME_PSTR("SERIAL_PROXY_IDENTITY_FLAG_NONE"); + case enums::SERIAL_PROXY_IDENTITY_FLAG_CONNECTED: + return ESPHOME_PSTR("SERIAL_PROXY_IDENTITY_FLAG_CONNECTED"); + case enums::SERIAL_PROXY_IDENTITY_FLAG_ERROR: + return ESPHOME_PSTR("SERIAL_PROXY_IDENTITY_FLAG_ERROR"); + default: + return ESPHOME_PSTR("UNKNOWN"); + } +} +#ifdef USE_SENDSPIN +template<> +const char *proto_enum_to_string(enums::SendspinPairingTokenStatus value) { + switch (value) { + case enums::SENDSPIN_PAIRING_TOKEN_STATUS_NOT_READY: + return ESPHOME_PSTR("SENDSPIN_PAIRING_TOKEN_STATUS_NOT_READY"); + case enums::SENDSPIN_PAIRING_TOKEN_STATUS_OK: + return ESPHOME_PSTR("SENDSPIN_PAIRING_TOKEN_STATUS_OK"); + case enums::SENDSPIN_PAIRING_TOKEN_STATUS_ENCRYPTION_REQUIRED: + return ESPHOME_PSTR("SENDSPIN_PAIRING_TOKEN_STATUS_ENCRYPTION_REQUIRED"); + case enums::SENDSPIN_PAIRING_TOKEN_STATUS_DISABLED: + return ESPHOME_PSTR("SENDSPIN_PAIRING_TOKEN_STATUS_DISABLED"); + case enums::SENDSPIN_PAIRING_TOKEN_STATUS_FAILED: + return ESPHOME_PSTR("SENDSPIN_PAIRING_TOKEN_STATUS_FAILED"); + default: + return ESPHOME_PSTR("UNKNOWN"); + } +} #endif const char *HelloRequest::dump_to(DumpBuffer &out) const { @@ -897,6 +953,9 @@ const char *HelloRequest::dump_to(DumpBuffer &out) const { dump_field(out, ESPHOME_PSTR("client_info"), this->client_info); dump_field(out, ESPHOME_PSTR("api_version_major"), this->api_version_major); dump_field(out, ESPHOME_PSTR("api_version_minor"), this->api_version_minor); +#ifdef USE_API_OUTGOING_CONNECTION + dump_field(out, ESPHOME_PSTR("outgoing_connection_target"), this->outgoing_connection_target); +#endif return out.c_str(); } const char *HelloResponse::dump_to(DumpBuffer &out) const { @@ -1020,6 +1079,9 @@ const char *DeviceInfoResponse::dump_to(DumpBuffer &out) const { #endif #ifdef USE_API_NOISE dump_field(out, ESPHOME_PSTR("api_encryption_provisionable"), this->api_encryption_provisionable); +#endif +#ifdef USE_API_OUTGOING_CONNECTION + dump_field(out, ESPHOME_PSTR("api_outgoing_connection_supported"), this->api_outgoing_connection_supported); #endif return out.c_str(); } @@ -1046,6 +1108,20 @@ const char *ZWaveProxyCapabilities::dump_to(DumpBuffer &out) const { return out.c_str(); } #endif +#ifdef USE_API_WIZARD +const char *WizardCapabilities::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, ESPHOME_PSTR("WizardCapabilities")); + dump_field(out, ESPHOME_PSTR("configured"), this->configured); + return out.c_str(); +} +#endif +#ifdef USE_SENDSPIN +const char *SendspinCapabilities::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, ESPHOME_PSTR("SendspinCapabilities")); + dump_field(out, ESPHOME_PSTR("feature_flags"), this->feature_flags); + return out.c_str(); +} +#endif const char *DeviceCapabilitiesResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, ESPHOME_PSTR("DeviceCapabilitiesResponse")); #ifdef USE_BLUETOOTH_PROXY @@ -1069,9 +1145,34 @@ const char *DeviceCapabilitiesResponse::dump_to(DumpBuffer &out) const { it.dump_to(out); out.append("\n"); } +#endif +#ifdef USE_API_WIZARD + out.append(2, ' ').append_p(ESPHOME_PSTR("wizard")).append(": "); + this->wizard.dump_to(out); + out.append("\n"); +#endif +#ifdef USE_SENDSPIN + out.append(2, ' ').append_p(ESPHOME_PSTR("sendspin")).append(": "); + this->sendspin.dump_to(out); + out.append("\n"); #endif return out.c_str(); } +#ifdef USE_API_WIZARD +const char *DeviceWizardResponse::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, ESPHOME_PSTR("DeviceWizardResponse")); + dump_progmem_bytes_field(out, ESPHOME_PSTR("data"), this->data, this->data_len); + return out.c_str(); +} +#endif +#ifdef USE_API_WIZARD_INPUTS +const char *WizardInputSetRequest::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, ESPHOME_PSTR("WizardInputSetRequest")); + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("entity_id"), this->entity_id); + return out.c_str(); +} +#endif const char *ListEntitiesDoneResponse::dump_to(DumpBuffer &out) const { out.append_p(ESPHOME_PSTR("ListEntitiesDoneResponse {}")); return out.c_str(); @@ -2714,7 +2815,7 @@ const char *ListEntitiesInfraredResponse::dump_to(DumpBuffer &out) const { return out.c_str(); } #endif -#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY) +#ifdef USE_IR_RF const char *InfraredRFTransmitRawTimingsRequest::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, ESPHOME_PSTR("InfraredRFTransmitRawTimingsRequest")); #ifdef USE_DEVICES @@ -2743,6 +2844,15 @@ const char *InfraredRFReceiveEvent::dump_to(DumpBuffer &out) const { } return out.c_str(); } +const char *InfraredRFTransmitCompleteResponse::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, ESPHOME_PSTR("InfraredRFTransmitCompleteResponse")); +#ifdef USE_DEVICES + dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); +#endif + dump_field(out, ESPHOME_PSTR("key"), this->key); + dump_field(out, ESPHOME_PSTR("success"), this->success); + return out.c_str(); +} #endif #ifdef USE_RADIO_FREQUENCY const char *ListEntitiesRadioFrequencyResponse::dump_to(DumpBuffer &out) const { @@ -2826,6 +2936,27 @@ const char *SerialProxySetModeRequest::dump_to(DumpBuffer &out) const { dump_field(out, ESPHOME_PSTR("mode"), static_cast(this->mode)); return out.c_str(); } +const char *UsbDeviceDescriptor::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, ESPHOME_PSTR("UsbDeviceDescriptor")); + dump_field(out, ESPHOME_PSTR("vendor_id"), this->vendor_id); + dump_field(out, ESPHOME_PSTR("product_id"), this->product_id); + dump_field(out, ESPHOME_PSTR("bcd_device"), this->bcd_device); + dump_field(out, ESPHOME_PSTR("interface_number"), this->interface_number); + return out.c_str(); +} +const char *SerialProxyIdentity::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, ESPHOME_PSTR("SerialProxyIdentity")); + dump_field(out, ESPHOME_PSTR("instance"), this->instance); + dump_field(out, ESPHOME_PSTR("source"), static_cast(this->source)); + dump_field(out, ESPHOME_PSTR("flags"), this->flags); + dump_field(out, ESPHOME_PSTR("manufacturer"), this->manufacturer); + dump_field(out, ESPHOME_PSTR("product"), this->product); + dump_field(out, ESPHOME_PSTR("serial_number"), this->serial_number); + out.append(2, ' ').append_p(ESPHOME_PSTR("usb")).append(": "); + this->usb.dump_to(out); + out.append("\n"); + return out.c_str(); +} #endif #ifdef USE_BLUETOOTH_PROXY_CONNECTIONS const char *BluetoothSetConnectionParamsRequest::dump_to(DumpBuffer &out) const { @@ -2844,6 +2975,14 @@ const char *BluetoothSetConnectionParamsResponse::dump_to(DumpBuffer &out) const return out.c_str(); } #endif +#ifdef USE_SENDSPIN +const char *SendspinPairingTokenResponse::dump_to(DumpBuffer &out) const { + MessageDumpHelper helper(out, ESPHOME_PSTR("SendspinPairingTokenResponse")); + dump_field(out, ESPHOME_PSTR("status"), static_cast(this->status)); + dump_field(out, ESPHOME_PSTR("token"), this->token); + return out.c_str(); +} +#endif } // namespace esphome::api diff --git a/esphome/components/api/api_pb2_includes.h b/esphome/components/api/api_pb2_includes.h index 70ba579fcc3..e076f98fe3d 100644 --- a/esphome/components/api/api_pb2_includes.h +++ b/esphome/components/api/api_pb2_includes.h @@ -15,6 +15,7 @@ #endif #ifdef USE_LIGHT +#include "esphome/components/light/light_effect_names.h" #include "esphome/components/light/light_traits.h" #endif @@ -28,6 +29,7 @@ // Standard library includes that might be needed #include +#include #include #include diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 172062be636..683bb84c302 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -6,7 +6,7 @@ namespace esphome::api { -static const char *const TAG = "api.service"; +ESPHOME_LOG_TAG(TAG, "api.service"); #ifdef HAS_PROTO_MESSAGE_DUMP void APIServerConnectionBase::log_send_message_(const LogString *name, const char *dump) { @@ -628,7 +628,7 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui break; } #endif -#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY) +#ifdef USE_IR_RF case InfraredRFTransmitRawTimingsRequest::MESSAGE_TYPE: { InfraredRFTransmitRawTimingsRequest msg; msg.decode(msg_data, msg_size); @@ -722,6 +722,44 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui this->on_serial_proxy_set_mode_request(msg); break; } +#endif +#ifdef USE_SERIAL_PROXY + case 154 /* SubscribeSerialProxyIdentityRequest is empty */: { +#ifdef HAS_PROTO_MESSAGE_DUMP + this->log_receive_message_(LOG_STR("on_subscribe_serial_proxy_identity_request")); +#endif + this->on_subscribe_serial_proxy_identity_request(); + break; + } +#endif +#ifdef USE_API_WIZARD + case 156 /* DeviceWizardRequest is empty */: { +#ifdef HAS_PROTO_MESSAGE_DUMP + this->log_receive_message_(LOG_STR("on_device_wizard_request")); +#endif + this->on_device_wizard_request(); + break; + } +#endif +#ifdef USE_API_WIZARD_INPUTS + case WizardInputSetRequest::MESSAGE_TYPE: { + WizardInputSetRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + this->log_receive_message_(LOG_STR("on_wizard_input_set_request"), msg); +#endif + this->on_wizard_input_set_request(msg); + break; + } +#endif +#ifdef USE_SENDSPIN + case 159 /* SendspinPairingTokenRequest is empty */: { +#ifdef HAS_PROTO_MESSAGE_DUMP + this->log_receive_message_(LOG_STR("on_sendspin_pairing_token_request")); +#endif + this->on_sendspin_pairing_token_request(); + break; + } #endif default: break; diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index a4dfd6a3663..7f33e5668c7 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -29,6 +29,13 @@ class APIServerConnectionBase { void on_device_capabilities_request(){}; +#ifdef USE_API_WIZARD + void on_device_wizard_request(){}; +#endif + +#ifdef USE_API_WIZARD_INPUTS + void on_wizard_input_set_request(const WizardInputSetRequest &value){}; +#endif void on_list_entities_request(){}; void on_subscribe_states_request(){}; @@ -213,7 +220,7 @@ class APIServerConnectionBase { void on_z_wave_proxy_request(const ZWaveProxyRequest &value){}; #endif -#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY) +#ifdef USE_IR_RF void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){}; #endif @@ -238,9 +245,17 @@ class APIServerConnectionBase { #ifdef USE_SERIAL_PROXY void on_serial_proxy_set_mode_request(const SerialProxySetModeRequest &value){}; #endif +#ifdef USE_SERIAL_PROXY + void on_subscribe_serial_proxy_identity_request(){}; +#endif + #ifdef USE_BLUETOOTH_PROXY_CONNECTIONS void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &value){}; #endif + +#ifdef USE_SENDSPIN + void on_sendspin_pairing_token_request(){}; +#endif }; } // namespace esphome::api diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index da9ca8bc799..7dad4e390e0 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -23,7 +23,7 @@ namespace esphome::api { -static const char *const TAG = "api"; +ESPHOME_LOG_TAG(TAG, "api"); // APIServer APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -56,7 +56,12 @@ APIServer::APIServer() { global_api_server = this; } void APIServer::socket_failed_(const LogString *msg) { ESP_LOGW(TAG, "Socket %s: errno %d", LOG_STR_ARG(msg), errno); this->destroy_socket_(); +#ifdef USE_API_OUTGOING_CONNECTION + // Dial-out needs no listener; degrade instead of stopping the component + this->status_set_error(LOG_STR("listen socket failed")); +#else this->mark_failed(); +#endif } void APIServer::setup() { @@ -72,43 +77,6 @@ void APIServer::setup() { #endif #endif - this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0).release(); // monitored for incoming connections - if (this->socket_ == nullptr) { - this->socket_failed_(LOG_STR("creation")); - return; - } - int enable = 1; - int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); - if (err != 0) { - ESP_LOGW(TAG, "Socket reuseaddr: errno %d", errno); - // we can still continue - } - err = this->socket_->setblocking(false); - if (err != 0) { - this->socket_failed_(LOG_STR("nonblocking")); - return; - } - - struct sockaddr_storage server; - - socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_); - if (sl == 0) { - this->socket_failed_(LOG_STR("set sockaddr")); - return; - } - - err = this->socket_->bind((struct sockaddr *) &server, sl); - if (err != 0) { - this->socket_failed_(LOG_STR("bind")); - return; - } - - err = this->socket_->listen(this->listen_backlog_); - if (err != 0) { - this->socket_failed_(LOG_STR("listen")); - return; - } - #ifdef USE_LOGGER if (logger::global_logger != nullptr) { logger::global_logger->add_log_callback( @@ -154,6 +122,47 @@ void APIServer::setup() { if (this->reboot_timeout_ != 0 && !this->provisioning_pending_()) { this->status_set_warning(LOG_STR("waiting for client connection")); } +#ifdef USE_API_OUTGOING_CONNECTION + this->outgoing_conn_.setup(); +#endif + + // Listener last: on failure socket_failed_() returns early, and an + // outgoing_connection build keeps dialing out without one + this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0).release(); // monitored for incoming connections + if (this->socket_ == nullptr) { + this->socket_failed_(LOG_STR("creation")); + return; + } + int enable = 1; + int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + if (err != 0) { + ESP_LOGW(TAG, "Socket reuseaddr: errno %d", errno); + // we can still continue + } + err = this->socket_->setblocking(false); + if (err != 0) { + this->socket_failed_(LOG_STR("nonblocking")); + return; + } + + struct sockaddr_storage server; + + socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_); + if (sl == 0) { + this->socket_failed_(LOG_STR("set sockaddr")); + return; + } + + err = this->socket_->bind((struct sockaddr *) &server, sl); + if (err != 0) { + this->socket_failed_(LOG_STR("bind")); + return; + } + + err = this->socket_->listen(this->listen_backlog_); + if (err != 0) { + this->socket_failed_(LOG_STR("listen")); + } } void APIServer::loop() { @@ -169,6 +178,12 @@ void APIServer::loop() { } #endif +#ifdef USE_API_OUTGOING_CONNECTION + if (!this->shutting_down_) { + this->outgoing_conn_.loop(this); + } +#endif + if (this->api_connection_count_ == 0) { // Check reboot timeout - done in loop to avoid scheduler heap churn // (cancelled scheduler items sit in heap memory until their scheduled time). @@ -241,6 +256,15 @@ void APIServer::remove_client_(uint8_t client_index) { std::string client_peername(client->get_peername_to(peername_buf)); #endif + // Read before the swap-and-reset below destroys the connection + const bool was_authenticated = client->is_authenticated(); +#ifdef USE_API_OUTGOING_CONNECTION + if (client->flags_.outgoing_connection_target) { + this->outgoing_target_count_--; + } + this->outgoing_conn_.on_client_removed(client.get(), was_authenticated); +#endif + // Close socket now (was deferred from on_fatal_error to allow getpeername) client->helper_->close(); @@ -259,9 +283,15 @@ void APIServer::remove_client_(uint8_t client_index) { // Last client disconnected - set warning and start tracking for reboot timeout // (suppressed while provisioning is pending - see loop()). + // Refresh on every authenticated removal, not just the last one, so an + // unauthenticated straggler removed later (e.g. a port scan, or a dial to + // a host that accepts TCP but never speaks the API) cannot discard a + // healthy session's timestamp and trigger a spurious reboot + if (was_authenticated) { + this->last_connected_ = App.get_loop_component_start_time(); + } if (this->api_connection_count_ == 0 && this->reboot_timeout_ != 0 && !this->provisioning_pending_()) { this->status_set_warning(LOG_STR("waiting for client connection")); - this->last_connected_ = App.get_loop_component_start_time(); } #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER @@ -283,7 +313,7 @@ void __attribute__((flatten)) APIServer::accept_new_connections_() { sock->getpeername_to(peername); // Check if we're at the connection limit - if (this->api_connection_count_ >= MAX_API_CONNECTIONS) { + if (this->at_client_limit_()) { ESP_LOGW(TAG, "Max connections (%d), rejecting %s", MAX_API_CONNECTIONS, peername); // Immediately close - socket destructor will handle cleanup sock.reset(); @@ -292,18 +322,47 @@ void __attribute__((flatten)) APIServer::accept_new_connections_() { ESP_LOGD(TAG, "Accept %s", peername); - auto *conn = new APIConnection(std::move(sock), this); - this->clients_[this->api_connection_count_++].reset(conn); - conn->start(); - - // First client connected - clear warning and update timestamp - if (this->api_connection_count_ == 1 && this->reboot_timeout_ != 0 && !this->provisioning_pending_()) { - this->status_clear_warning(); - this->last_connected_ = App.get_loop_component_start_time(); - } + this->add_client_(std::move(sock)); } } +APIConnection *APIServer::add_client_(std::unique_ptr sock) { + auto *conn = new APIConnection(std::move(sock), this); // NOLINT(cppcoreguidelines-owning-memory) + this->clients_[this->api_connection_count_++].reset(conn); + conn->start(); + + // First client connected - clear warning. The reboot watchdog timestamp is + // refreshed when an authenticated client is removed (see remove_client_), + // never on bare TCP connects. + if (this->api_connection_count_ == 1 && this->reboot_timeout_ != 0 && !this->provisioning_pending_()) { + this->status_clear_warning(); + } + return conn; +} + +#ifdef USE_API_OUTGOING_CONNECTION +APIConnection *APIServer::add_outgoing_client_(std::unique_ptr sock) { + // Re-check at the handoff: inbound clients may have taken the last slot and + // the PSK may have been cleared since the dial started (mark_outgoing() + // needs the noise helper) + const bool at_limit = this->at_client_limit_(); + if (at_limit || !this->noise_ctx_.has_psk()) { + ESP_LOGW(TAG, "Dropping outgoing connection (%s)", + at_limit ? LOG_STR_LITERAL("max connections") : LOG_STR_LITERAL("no key")); + return nullptr; + } + auto *conn = this->add_client_(std::move(sock)); + // After start(): sends our server hello first so the peer can pick the key + conn->mark_outgoing(); + return conn; +} + +void APIServer::on_outgoing_target_client(APIConnection *conn) { + this->outgoing_target_count_++; + this->outgoing_conn_.on_target_client(conn); +} +#endif + void APIServer::dump_config() { char addr_buf[network::USE_ADDRESS_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, @@ -320,6 +379,9 @@ void APIServer::dump_config() { #else ESP_LOGCONFIG(TAG, " Noise encryption: NO"); #endif +#ifdef USE_API_OUTGOING_CONNECTION + this->outgoing_conn_.dump_config(); +#endif } void APIServer::handle_disconnect(APIConnection *conn) {} @@ -442,7 +504,16 @@ void APIServer::on_zwave_proxy_request(const ZWaveProxyRequest &msg) { } #endif -#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY) +#ifdef USE_SERIAL_PROXY_USB_IDENTITY +void APIServer::send_serial_proxy_identity(const SerialProxyIdentity &msg) { + for (auto &c : this->active_clients()) { + if (c->flags_.serial_proxy_identity_subscription) + c->send_serial_proxy_identity(msg); + } +} +#endif + +#ifdef USE_IR_RF void APIServer::send_infrared_rf_receive_event([[maybe_unused]] uint32_t device_id, uint32_t key, const std::vector *timings) { InfraredRFReceiveEvent resp{}; @@ -455,6 +526,7 @@ void APIServer::send_infrared_rf_receive_event([[maybe_unused]] uint32_t device_ for (auto &c : this->active_clients()) c->send_infrared_rf_receive_event(resp); } + #endif #ifdef USE_ALARM_CONTROL_PANEL @@ -616,6 +688,8 @@ bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString if (!c->send_message(req)) { API_LOG_MSG_DROPPED(TAG, "Disconnect request"); } + // Force it: a session from before the key was active must not survive + c->flags_.next_close = true; } }); } @@ -717,6 +791,9 @@ void APIServer::on_shutdown() { // Close the listening socket to prevent new connections this->destroy_socket_(); +#ifdef USE_API_OUTGOING_CONNECTION + this->outgoing_conn_.on_shutdown(); +#endif // Change batch delay to 5ms for quick flushing during shutdown this->batch_delay_ = 5; diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 865cd17156d..09b08de4e3f 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -11,6 +11,7 @@ #endif #include "api_pb2.h" #include "api_pb2_service.h" +#include "api_outgoing_connection.h" #include "esphome/components/socket/socket.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" @@ -90,6 +91,10 @@ class APIServer final : public Component void set_noise_psk(const uint8_t *psk) { this->noise_ctx_.set_psk(psk); } noise::NoiseContext &get_noise_ctx() { return this->noise_ctx_; } #endif // USE_API_NOISE +#ifdef USE_API_OUTGOING_CONNECTION + // Called by APIConnection when a client declares itself a dial-back target in its hello + void on_outgoing_target_client(APIConnection *conn); +#endif void handle_disconnect(APIConnection *conn); #ifdef USE_BINARY_SENSOR @@ -198,7 +203,11 @@ class APIServer final : public Component #ifdef USE_ZWAVE_PROXY void on_zwave_proxy_request(const ZWaveProxyRequest &msg); #endif -#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY) +#ifdef USE_SERIAL_PROXY_USB_IDENTITY + /// Tell every subscribed client that a serial proxy port's identity changed + void send_serial_proxy_identity(const SerialProxyIdentity &msg); +#endif +#ifdef USE_IR_RF void send_infrared_rf_receive_event(uint32_t device_id, uint32_t key, const std::vector *timings); #endif @@ -267,6 +276,16 @@ class APIServer final : public Component protected: // Accept incoming socket connections. Only called when socket has pending connections. void __attribute__((noinline)) accept_new_connections_(); + /// Takes the socket into a new connection and starts it; callers must have + /// checked at_client_limit_() first + APIConnection *add_client_(std::unique_ptr sock); + bool at_client_limit_() const { return this->api_connection_count_ >= MAX_API_CONNECTIONS; } +#ifdef USE_API_OUTGOING_CONNECTION + // Returns the new connection, or nullptr (socket dropped) when at the limit + APIConnection *add_outgoing_client_(std::unique_ptr sock); + bool has_outgoing_target_client_() const { return this->outgoing_target_count_ != 0; } + friend class OutgoingConnectionManager; +#endif // Remove a disconnected client by index. Swaps with the last populated slot and resets it. void __attribute__((noinline)) remove_client_(uint8_t client_index); @@ -307,6 +326,8 @@ class APIServer final : public Component delete this->socket_; this->socket_ = nullptr; } + /// Log the failure, drop the listen socket, and mark the component failed + /// unless this build can still dial out void socket_failed_(const LogString *msg); // Pointers and pointer-like types first (4 bytes each) socket::ListenSocket *socket_{nullptr}; @@ -357,11 +378,13 @@ class APIServer final : public Component // Group smaller types together uint16_t port_{6053}; // Keep in sync with DEFAULT_PORT in __init__.py uint16_t batch_delay_{100}; // Keep in sync with DEFAULT_BATCH_DELAY in __init__.py - // Connection limits - these defaults will be overridden by config values - // from cv.SplitDefault in __init__.py which sets platform-specific defaults. - uint8_t listen_backlog_{4}; + uint8_t listen_backlog_{4}; // Keep in sync with DEFAULT_LISTEN_BACKLOG in __init__.py bool shutting_down_ = false; uint8_t api_connection_count_{0}; +#ifdef USE_API_OUTGOING_CONNECTION + // Connected clients whose hello declared them a dial-back target + uint8_t outgoing_target_count_{0}; +#endif #if defined(USE_PROVISIONING) && defined(USE_API_NOISE) // Index assigned by the provisioning manager for reporting this transport's state. uint8_t provisioning_source_{0}; @@ -377,6 +400,9 @@ class APIServer final : public Component #endif ESPPreferenceObject noise_pref_; #endif // USE_API_NOISE +#ifdef USE_API_OUTGOING_CONNECTION + OutgoingConnectionManager outgoing_conn_; +#endif }; extern APIServer *global_api_server; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/api/api_wizard.cpp b/esphome/components/api/api_wizard.cpp new file mode 100644 index 00000000000..10a5cbc70cc --- /dev/null +++ b/esphome/components/api/api_wizard.cpp @@ -0,0 +1,86 @@ +#include "api_wizard.h" + +#ifdef USE_API_WIZARD + +#include + +#include "api_connection.h" +#include "api_pb2.h" +#include "api_server.h" +#include "esphome/core/log.h" + +namespace esphome::api { + +ESPHOME_LOG_TAG(TAG, "api.wizard"); + +#ifdef USE_API_WIZARD_INPUTS +static bool wizard_entity_id_valid(const char *entity_id, size_t length) { + return length > 0 && length < WIZARD_ENTITY_ID_BUFFER_SIZE && memchr(entity_id, '.', length) != nullptr; +} + +const char *wizard_set_input(const WizardInputSetRequest &msg) { + if (!wizard_entity_id_valid(msg.entity_id.c_str(), msg.entity_id.size())) { + ESP_LOGW(TAG, "Ignoring an invalid entity id for wizard input"); + return nullptr; + } + for (size_t i = 0; i < API_WIZARD_INPUT_COUNT; i++) { + // The table is in flash, which ESP8266 can only read through progmem_memcpy + WizardInputEntry entry; + progmem_memcpy(&entry, &API_WIZARD_INPUTS[i], sizeof(entry)); + if (entry.key != msg.key) + continue; + memcpy(entry.entity_id, msg.entity_id.c_str(), msg.entity_id.size()); + entry.entity_id[msg.entity_id.size()] = '\0'; + return entry.entity_id; + } + ESP_LOGW(TAG, "Ignoring an entity id for an unknown wizard input"); + return nullptr; +} +#endif // USE_API_WIZARD_INPUTS + +void APIConnection::on_device_wizard_request() { + DeviceWizardResponse resp; + resp.data = API_WIZARD_DATA; + resp.data_len = API_WIZARD_DATA_SIZE; + if (!this->send_message(resp)) { + this->on_fatal_error(); + } +} + +#ifdef USE_API_WIZARD_INPUTS +void APIConnection::on_wizard_input_set_request(const WizardInputSetRequest &msg) { + const char *entity_id = wizard_set_input(msg); + if (entity_id == nullptr) + return; +#ifdef USE_API_WIZARD_LINKED_INPUTS + // Entities subscribed to the buffer before it held an entity id, so every client needs to learn of it now + for (auto &client : this->parent_->active_clients()) { + client->resend_state_subscriptions(entity_id); + } +#endif +} +#endif + +#ifdef USE_API_WIZARD_LINKED_INPUTS +void APIConnection::resend_state_subscriptions(const char *entity_id) { + if (!this->flags_.home_assistant_states) + return; + for (const auto &it : this->parent_->get_state_subs()) { + if (it.entity_id != entity_id) + continue; + SubscribeHomeAssistantStateResponse resp; + resp.entity_id = StringRef(it.entity_id); + resp.attribute = it.attribute != nullptr ? StringRef(it.attribute) : StringRef(""); + resp.once = it.once; + if (!this->send_message(resp)) { + // Could not send now: send every subscription again from the loop + this->state_subs_at_ = 0; + return; + } + } +} +#endif + +} // namespace esphome::api + +#endif // USE_API_WIZARD diff --git a/esphome/components/api/api_wizard.h b/esphome/components/api/api_wizard.h new file mode 100644 index 00000000000..7d5a566b550 --- /dev/null +++ b/esphome/components/api/api_wizard.h @@ -0,0 +1,58 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_API_WIZARD + +#include +#include + +#include "esphome/core/hal.h" +#include "proto.h" +#include "esphome/core/string_ref.h" + +namespace esphome::api { + +class WizardInputSetRequest; + +/// Size of the buffer holding the entity id of a wizard input. Home Assistant entity ids are at most 255 bytes. +static constexpr size_t WIZARD_ENTITY_ID_BUFFER_SIZE = 256; + +/// The wizard as zstd compressed JSON (see DeviceWizardResponse), built by the generated code +/// (components/api/wizard.py) and kept in flash. API_WIZARD_DATA_SIZE bytes long. +extern const uint8_t API_WIZARD_DATA[] PROGMEM; + +#ifdef USE_API_WIZARD_INPUTS +/// Where the entity id of an input is kept, found by the key the client uses for it. +struct WizardInputEntry { + uint32_t key; // FNV-1 hash of the ESPHome id of the input + char *entity_id; // RAM buffer of WIZARD_ENTITY_ID_BUFFER_SIZE bytes, shared with the homeassistant entity +}; + +/// The inputs of the wizard, in flash. API_WIZARD_INPUT_COUNT entries long. +extern const WizardInputEntry API_WIZARD_INPUTS[] PROGMEM; + +/// Apply a WizardInputSetRequest: validate it and copy the entity id into the input's buffer. Nothing is stored +/// across restarts, so the client sends the choices again after every connection. +/// Returns the buffer, or nullptr when the request was ignored. +const char *wizard_set_input(const WizardInputSetRequest &msg); +#endif + +#ifdef USE_API_WIZARD_STANDALONE_INPUTS +/// An input of the wizard that is not tied to an entity of the device. The entity ID the user picks is only ever read +/// by lambdas, for example `id(input).entity_id()`. It lives in the same RAM buffer a linked input uses. +class WizardInput { + public: + explicit WizardInput(const char *entity_id) : entity_id_(entity_id) {} + /// The Home Assistant entity ID, empty until the wizard sets one. + StringRef entity_id() const { return StringRef(this->entity_id_); } + bool has_entity_id() const { return this->entity_id_[0] != '\0'; } + + protected: + const char *entity_id_; +}; +#endif + +} // namespace esphome::api + +#endif // USE_API_WIZARD diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 9e0faf98819..e7798a5d267 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -4,64 +4,40 @@ #ifdef USE_API #ifdef USE_API_HOMEASSISTANT_SERVICES #include +#include +#include #include -#include #include "api_pb2.h" #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #include "esphome/components/json/json_util.h" #endif #include "esphome/core/automation.h" #include "esphome/core/helpers.h" +#include "esphome/core/progmem.h" #include "esphome/core/string_ref.h" namespace esphome::api { -template class TemplatableStringValue : public TemplatableValue { - // Verify that const char* uses the base class STATIC_STRING optimization (no heap allocation) - // rather than being wrapped in a lambda. The base class constructor for const char* is more - // specialized than the templated constructor here, so it should be selected. - static_assert(std::is_constructible_v, const char *>, - "Base class must have const char* constructor for STATIC_STRING optimization"); +// Converts a lambda result to the string sent to Home Assistant +template +requires(!std::is_pointer_v>) std::string field_to_string(T &&val) { + return to_string(std::forward(val)); // NOLINT +} +inline std::string field_to_string(const char *val) { return val ? std::string(val) : std::string(); } +inline std::string field_to_string(std::string val) { return val; } +inline std::string field_to_string(StringRef val) { return val.str(); } - private: - // Helper to convert value to string - handles the case where value is already a string - template static std::string value_to_string(T &&val) { - return to_string(std::forward(val)); // NOLINT +/// A key and value from codegen; on ESP8266 the table and its strings are in flash. +/// The value is the constant `value`, or the result of `fn` when it is set. +template struct HomeAssistantField { + const char *key; + const char *value; + std::string (*fn)(const Ts &...); + + template static constexpr HomeAssistantField from_lambda(const char *key, F /*lambda*/) { + return {key, nullptr, &call_lambda}; } - - // Overloads for string types - needed because std::to_string doesn't support them - static std::string value_to_string(char *val) { - return val ? std::string(val) : std::string(); - } // For lambdas returning char* (e.g., itoa) - static std::string value_to_string(const char *val) { return std::string(val); } // For lambdas returning .c_str() - static std::string value_to_string(const std::string &val) { return val; } - static std::string value_to_string(std::string &&val) { return std::move(val); } - static std::string value_to_string(const StringRef &val) { return val.str(); } - static std::string value_to_string(StringRef &&val) { return val.str(); } - - public: - TemplatableStringValue() : TemplatableValue() {} - - template::value, int> = 0> - TemplatableStringValue(F value) : TemplatableValue(value) {} - - template::value, int> = 0> - TemplatableStringValue(F f) - : TemplatableValue([f](X... x) -> std::string { return value_to_string(f(x...)); }) {} -}; - -template class TemplatableKeyValuePair { - public: - // Default constructor needed for FixedVector::emplace_back() - TemplatableKeyValuePair() = default; - - // Keys are always string literals from YAML dictionary keys (e.g., "code", "event") - // and never templatable values or lambdas. Only the value parameter can be a lambda/template. - // Using const char* avoids std::string heap allocation - keys remain in flash. - template TemplatableKeyValuePair(const char *key, T value) : key(key), value(value) {} - - const char *key{nullptr}; - TemplatableStringValue value; + template static std::string call_lambda(const Ts &...x) { return field_to_string(F{}(x...)); } }; #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES @@ -106,45 +82,20 @@ template using ActionResponseCallback = std::function class HomeAssistantServiceCallAction final : public Action { public: - explicit HomeAssistantServiceCallAction(APIServer *parent, bool is_event) : parent_(parent) { + using Field = HomeAssistantField; + + /// `fields` is a codegen table: the action or event name (no key), then the data, data_template + /// and variables entries. + HomeAssistantServiceCallAction(APIServer *parent, bool is_event, const Field *fields, uint8_t data_count, + uint8_t data_template_count, uint8_t variables_count) + : parent_(parent), + fields_(fields), + data_count_(data_count), + data_template_count_(data_template_count), + variables_count_(variables_count) { this->flags_.is_event = is_event; } - template void set_service(T service) { this->service_ = service; } - - // Initialize FixedVector members - called from Python codegen with compile-time known sizes. - // Must be called before any add_* methods; capacity must match the number of subsequent add_* calls. - void init_data(size_t count) { this->data_.init(count); } - void init_data_template(size_t count) { this->data_template_.init(count); } - void init_variables(size_t count) { this->variables_.init(count); } - - // Keys are always string literals from the Python code generation (e.g., cg.add(var.add_data("tag_id", templ))). - // The value parameter can be a lambda/template, but keys are never templatable. - // Using const char* for keys avoids std::string heap allocation - keys remain in flash. - template void add_data(const char *key, V &&value) { - this->add_kv_(this->data_, key, std::forward(value)); - } - template void add_data_template(const char *key, V &&value) { - this->add_kv_(this->data_template_, key, std::forward(value)); - } - template void add_variable(const char *key, V &&value) { - this->add_kv_(this->variables_, key, std::forward(value)); - } - -#ifdef USE_ESP8266 - // On ESP8266, ESPHOME_F() returns __FlashStringHelper* (PROGMEM pointer). - // Store as const char* — populate_service_map copies from PROGMEM at play() time. - template void add_data(const __FlashStringHelper *key, V &&value) { - this->add_kv_(this->data_, reinterpret_cast(key), std::forward(value)); - } - template void add_data_template(const __FlashStringHelper *key, V &&value) { - this->add_kv_(this->data_template_, reinterpret_cast(key), std::forward(value)); - } - template void add_variable(const __FlashStringHelper *key, V &&value) { - this->add_kv_(this->variables_, reinterpret_cast(key), std::forward(value)); - } -#endif - #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES template void set_response_template(T response_template) { this->response_template_ = response_template; @@ -162,19 +113,61 @@ template class HomeAssistantServiceCallAction final : public Act #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES void play(const Ts &...x) override { + const Field *fields = this->fields_; + const size_t total = 1 + this->data_count_ + this->data_template_count_ + this->variables_count_; + + // Lambda results, and on ESP8266 the RAM copies of the flash strings, must live until the send + size_t lambda_count = 0; +#ifdef USE_ESP8266 + size_t flash_len = 0; +#endif + for (size_t i = 0; i < total; i++) { + lambda_count += fields[i].fn != nullptr; +#ifdef USE_ESP8266 + if (fields[i].fn == nullptr) + flash_len += ESPHOME_strlen_P(fields[i].value); + if (fields[i].key != nullptr) + flash_len += ESPHOME_strlen_P(fields[i].key); +#endif + } + FixedVector results; + results.init(lambda_count); +#ifdef USE_ESP8266 + SmallBufferWithHeapFallback<128, char> flash_copy(flash_len); + char *cursor = flash_copy.get(); +#endif + auto string_ref = [&](const char *str) { +#ifdef USE_ESP8266 + size_t len = ESPHOME_strlen_P(str); + memcpy_P(cursor, str, len); + StringRef ref(cursor, len); + cursor += len; + return ref; +#else + return StringRef(str); +#endif + }; + auto value_ref = [&](const Field &field) { + if (field.fn == nullptr) + return string_ref(field.value); + results.push_back(field.fn(x...)); + return StringRef(results.back()); + }; + auto fill = [&](FixedVector &dest, uint8_t count) { + dest.init(count); + for (uint8_t i = 0; i < count; i++, fields++) { + auto &kv = dest.emplace_back(); + kv.key = string_ref(fields->key); + kv.value = value_ref(*fields); + } + }; + HomeassistantActionRequest resp; - std::string service_value = this->service_.value(x...); - resp.service = StringRef(service_value); + resp.service = value_ref(*fields++); resp.is_event = this->flags_.is_event; - - // Local storage for lambda-evaluated strings - lives until after send - FixedVector data_storage; - FixedVector data_template_storage; - FixedVector variables_storage; - - this->populate_service_map(resp.data, this->data_, data_storage, x...); - this->populate_service_map(resp.data_template, this->data_template_, data_template_storage, x...); - this->populate_service_map(resp.variables, this->variables_, variables_storage, x...); + fill(resp.data, this->data_count_); + fill(resp.data_template, this->data_template_count_); + fill(resp.variables, this->variables_count_); #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON @@ -223,90 +216,26 @@ template class HomeAssistantServiceCallAction final : public Act } protected: - // Helper to add key-value pairs to FixedVectors - // Keys are always string literals (const char*), values can be lambdas/templates - template void add_kv_(FixedVector> &vec, const char *key, V &&value) { - auto &kv = vec.emplace_back(); - kv.key = key; - kv.value = std::forward(value); - } - - template - static void populate_service_map(VectorType &dest, SourceType &source, FixedVector &value_storage, - Ts... x) { - dest.init(source.size()); - -#ifdef USE_ESP8266 - // On ESP8266, all static strings from codegen are FLASH_STRING (PROGMEM), - // so is_static_string() is always false — the zero-copy STATIC_STRING fast - // path from the non-ESP8266 branch cannot trigger. We copy all keys and - // values unconditionally: keys via _P functions (may be in PROGMEM), values - // via value() which handles FLASH_STRING internally. - value_storage.init(source.size() * 2); - - for (auto &it : source) { - auto &kv = dest.emplace_back(); - - // Key: copy from possible PROGMEM - { - size_t key_len = strlen_P(it.key); - value_storage.push_back(std::string(key_len, '\0')); - memcpy_P(value_storage.back().data(), it.key, key_len); - kv.key = StringRef(value_storage.back()); - } - - // Value: value() handles FLASH_STRING via _P functions internally - value_storage.push_back(it.value.value(x...)); - kv.value = StringRef(value_storage.back()); - } -#else - // On non-ESP8266, strings are directly readable from flash-mapped memory. - // Count non-static strings to allocate exact storage needed. - size_t lambda_count = 0; - for (const auto &it : source) { - if (!it.value.is_static_string()) { - lambda_count++; - } - } - value_storage.init(lambda_count); - - for (auto &it : source) { - auto &kv = dest.emplace_back(); - kv.key = StringRef(it.key); - - if (it.value.is_static_string()) { - // Static string — pointer directly readable, zero allocation - kv.value = StringRef(it.value.get_static_string()); - } else { - // Lambda — evaluate and store result - value_storage.push_back(it.value.value(x...)); - kv.value = StringRef(value_storage.back()); - } - } -#endif - } - APIServer *parent_; - TemplatableStringValue service_{}; - FixedVector> data_; - FixedVector> data_template_; - FixedVector> variables_; -#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES -#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - TemplatableStringValue response_template_{""}; - Trigger success_trigger_with_response_; -#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - Trigger success_trigger_; - Trigger error_trigger_; -#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES - + const Field *fields_; + uint8_t data_count_; + uint8_t data_template_count_; + uint8_t variables_count_; struct Flags { uint8_t is_event : 1; uint8_t wants_status : 1; uint8_t wants_response : 1; uint8_t has_response_template : 1; - uint8_t reserved : 5; + uint8_t reserved : 4; } flags_{0}; +#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES +#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON + TemplatableValue response_template_{}; + Trigger success_trigger_with_response_; +#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON + Trigger success_trigger_; + Trigger error_trigger_; +#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES }; } // namespace esphome::api diff --git a/esphome/components/api/proto.cpp b/esphome/components/api/proto.cpp index fd1a9519dcd..e808e8a9ecf 100644 --- a/esphome/components/api/proto.cpp +++ b/esphome/components/api/proto.cpp @@ -6,7 +6,7 @@ namespace esphome::api { -static const char *const TAG = "api.proto"; +ESPHOME_LOG_TAG(TAG, "api.proto"); uint32_t ProtoSize::varint_slow(uint32_t value) { return varint_wide(value); } @@ -119,7 +119,7 @@ uint32_t ProtoDecodableMessage::count_repeated_field(const uint8_t *buffer, size } // Single-pass encode for repeated submessage elements (non-template core). -// Writes field tag, reserves 1 byte for length varint, encodes the submessage body, +// Reserves 1 byte for length varint, encodes the submessage body, // then backpatches the actual length. For the common case (body < 128 bytes), this is // just a single byte write with no memmove — all current repeated submessage types // (BLE advertisements at ~47B, GATT descriptors at ~24B, service args, etc.) take @@ -143,51 +143,34 @@ uint32_t ProtoDecodableMessage::count_repeated_field(const uint8_t *buffer, size // // After writing 2-byte varint at len_pos: // [tag][v1][v2][body ..... body] -// ^-- pos_ = element end, within buffer -void ProtoWriteBuffer::encode_sub_message(uint32_t field_id, const void *value, - uint8_t *(*encode_fn)(const void *, - ProtoWriteBuffer &PROTO_ENCODE_DEBUG_PARAM)) { - this->encode_field_raw(field_id, 2); - // Reserve 1 byte for length varint (optimistic: submessage < 128 bytes) - uint8_t *len_pos = this->pos_; - this->debug_check_bounds_(1); - this->pos_++; - uint8_t *body_start = this->pos_; - this->pos_ = encode_fn(value, *this PROTO_ENCODE_DEBUG_INIT(this->buffer_)); - uint32_t body_size = static_cast(this->pos_ - body_start); - if (body_size < 128) [[likely]] { +// ^-- returned cursor = element end, within buffer +uint8_t *ProtoEncode::encode_sub_message_body(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, const void *value, + ProtoEncodeFn encode_fn) { + // Reserve 1 byte for the length varint (optimistic: submessage < 128 bytes) + uint8_t *len_pos = pos; + PROTO_ENCODE_CHECK_BOUNDS(pos, 1); + uint8_t *body_start = pos + 1; + uint8_t *after_body = encode_fn(value, body_start PROTO_ENCODE_DEBUG_ARG); + uint32_t body_size = static_cast(after_body - body_start); + if (body_size < VARINT_MAX_1_BYTE) [[likely]] { // Common case: 1-byte varint, just backpatch *len_pos = static_cast(body_size); - return; + return after_body; } - // Compute extra bytes needed for varint beyond the 1 already reserved + // Shift the body forward to make room for the extra length varint bytes uint8_t extra = ProtoSize::varint(body_size) - 1; - // Shift body forward to make room for the extra varint bytes - this->debug_check_bounds_(extra); + PROTO_ENCODE_CHECK_BOUNDS(after_body, extra); std::memmove(body_start + extra, body_start, body_size); - uint8_t *end = this->pos_ + extra; // Write the full varint at len_pos - this->pos_ = len_pos; - this->encode_varint_raw(body_size); - this->pos_ = end; + (void) encode_varint_raw_loop(len_pos PROTO_ENCODE_DEBUG_ARG, body_size); + return after_body + extra; } // Non-template core for encode_optional_sub_message. -void ProtoWriteBuffer::encode_optional_sub_message(uint32_t field_id, uint32_t nested_size, const void *value, - uint8_t *(*encode_fn)(const void *, - ProtoWriteBuffer &PROTO_ENCODE_DEBUG_PARAM)) { - if (nested_size == 0) - return; - this->encode_field_raw(field_id, 2); - this->encode_varint_raw(nested_size); -#ifdef ESPHOME_DEBUG_API - uint8_t *start = this->pos_; - this->pos_ = encode_fn(value, *this PROTO_ENCODE_DEBUG_INIT(this->buffer_)); - if (static_cast(this->pos_ - start) != nested_size) - this->debug_check_encode_size_(field_id, nested_size, this->pos_ - start); -#else - this->pos_ = encode_fn(value, *this PROTO_ENCODE_DEBUG_INIT(this->buffer_)); -#endif +uint8_t *ProtoEncode::encode_sized_sub_message_body(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, + uint32_t nested_size, const void *value, ProtoEncodeFn encode_fn) { + pos = encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, nested_size); + return encode_fn(value, pos PROTO_ENCODE_DEBUG_ARG); } #ifdef ESPHOME_DEBUG_API @@ -201,6 +184,14 @@ void proto_check_encode_end(const uint8_t *end, const uint8_t *expected) { ESP_LOGE(TAG, "Proto encode ended %td bytes off the calculated size", end - expected); abort(); } +void proto_check_sub_message_size(uint32_t field_id, uint32_t expected, const uint8_t *len_pos, const uint8_t *end) { + ptrdiff_t actual = end - (len_pos + ProtoSize::varint(expected)); + if (actual == static_cast(expected)) + return; + ESP_LOGE(TAG, "encode_message: size mismatch for field %" PRIu32 ": calculated=%" PRIu32 " actual=%td", field_id, + expected, actual); + abort(); +} void ProtoWriteBuffer::debug_check_bounds_(size_t bytes, const char *caller) { if (this->pos_ + bytes > this->buffer_->data() + this->buffer_->size()) { ESP_LOGE(TAG, "ProtoWriteBuffer bounds check failed in %s: bytes=%zu offset=%td buf_size=%zu", caller, bytes, @@ -208,11 +199,6 @@ void ProtoWriteBuffer::debug_check_bounds_(size_t bytes, const char *caller) { abort(); } } -void ProtoWriteBuffer::debug_check_encode_size_(uint32_t field_id, uint32_t expected, ptrdiff_t actual) { - ESP_LOGE(TAG, "encode_message: size mismatch for field %" PRIu32 ": calculated=%" PRIu32 " actual=%td", field_id, - expected, actual); - abort(); -} #endif diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index c112b0f4159..0fdda5067ea 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -3,6 +3,7 @@ #include "api_pb2_defines.h" #include "api_buffer.h" #include "esphome/core/component.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/progmem.h" @@ -56,22 +57,25 @@ inline constexpr int64_t decode_zigzag64(uint64_t value) { return (value & 1) ? static_cast(~(value >> 1)) : static_cast(value >> 1); } -/// Count number of varints in a packed buffer -inline uint16_t count_packed_varints(const uint8_t *data, size_t len) { - uint16_t count = 0; - while (len > 0) { - // Skip varint bytes until we find one without continuation bit - while (len > 0 && (*data & 0x80)) { - data++; - len--; - } - if (len > 0) { - data++; - len--; - count++; +/// Count varints in a packed buffer: len minus bytes with the continuation bit, summed a word at a time. +/// Word is a template parameter so tests can cover the 32-bit path on a 64-bit host. +template inline uint16_t count_packed_varints(const uint8_t *data, size_t len) { + constexpr size_t word_size = sizeof(Word); + constexpr Word lane_ones = ~Word{0} / 0xFF; // 0x01..01 + const uint8_t *end = data + len; + size_t continuations = 0; + while (data != end) { + // Unaligned word loads fault on Xtensa + if ((reinterpret_cast(data) & (word_size - 1)) == 0 && static_cast(end - data) >= word_size) { + Word word; + memcpy(&word, __builtin_assume_aligned(data, word_size), word_size); + continuations += (((word >> 7) & lane_ones) * lane_ones) >> (word_size * 8 - 8); + data += word_size; + } else { + continuations += *data++ >> 7; } } - return count; + return static_cast(len - continuations); } /// Encode a varint directly into a pre-allocated buffer. @@ -228,6 +232,8 @@ void proto_check_bounds_failed(const uint8_t *pos, size_t bytes, const uint8_t * /// Aborts unless an encode body ended exactly where calculate_size() promised. A plain check rather than /// assert(), so NDEBUG cannot switch it off. void proto_check_encode_end(const uint8_t *end, const uint8_t *expected); +/// Aborts unless a sized sub-message (length prefix at len_pos) ended where its calculated size said. +void proto_check_sub_message_size(uint32_t field_id, uint32_t expected, const uint8_t *len_pos, const uint8_t *end); #else #define PROTO_ENCODE_DEBUG_PARAM #define PROTO_ENCODE_DEBUG_ARG @@ -260,21 +266,6 @@ class ProtoWriteBuffer { * Following https://protobuf.dev/programming-guides/encoding/#structure */ void encode_field_raw(uint32_t field_id, uint32_t type) { this->encode_varint_raw(proto_tag(field_id, type)); } - /// Single-pass encode for repeated submessage elements. - /// Thin template wrapper; all buffer work is in the non-template core. - template void encode_sub_message(uint32_t field_id, const T &value); - /// Encode an optional singular submessage field — skips if empty. - /// Thin template wrapper; all buffer work is in the non-template core. - template void encode_optional_sub_message(uint32_t field_id, const T &value); - - // NOLINTBEGIN(readability-identifier-naming) - // Non-template core for encode_sub_message — backpatch approach. - void encode_sub_message(uint32_t field_id, const void *value, - uint8_t *(*encode_fn)(const void *, ProtoWriteBuffer &PROTO_ENCODE_DEBUG_PARAM)); - // Non-template core for encode_optional_sub_message. - void encode_optional_sub_message(uint32_t field_id, uint32_t nested_size, const void *value, - uint8_t *(*encode_fn)(const void *, ProtoWriteBuffer &PROTO_ENCODE_DEBUG_PARAM)); - // NOLINTEND(readability-identifier-naming) APIBuffer *get_buffer() const { return buffer_; } uint8_t *get_pos() const { return pos_; } void set_pos(uint8_t *pos) { pos_ = pos; } @@ -285,7 +276,6 @@ class ProtoWriteBuffer { #ifdef ESPHOME_DEBUG_API void debug_check_bounds_(size_t bytes, const char *caller = __builtin_FUNCTION()); - void debug_check_encode_size_(uint32_t field_id, uint32_t expected, ptrdiff_t actual); #else void debug_check_bounds_([[maybe_unused]] size_t bytes) {} #endif @@ -310,6 +300,9 @@ class ProtoWriteBuffer { constexpr uint32_t VARINT_MAX_1_BYTE = 1 << 7; // 128 constexpr uint32_t VARINT_MAX_2_BYTE = 1 << 14; // 16384 +/// Generated encode body: writes the fields at pos, returns the cursor past them. +using ProtoEncodeFn = uint8_t *(*) (const void *, uint8_t *PROTO_ENCODE_DEBUG_PARAM); + /// Static encode helpers for the generated encode bodies. Each takes the write cursor by value and /// returns it advanced, so outlined calls at -Os chain through the return register instead of a /// stack slot. Helpers without a _force suffix skip fields holding the proto3 default. @@ -473,6 +466,21 @@ class ProtoEncode { uint32_t field_id, const uint8_t *data, size_t len) { return encode_string_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, reinterpret_cast(data), len); } + /// encode_bytes for data in flash (PROGMEM), which ESP8266 can only read with progmem_memcpy. + [[nodiscard]] static inline uint8_t *encode_progmem_bytes(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, + uint32_t field_id, const uint8_t *data, size_t len) { + if (len == 0) + return pos; + return encode_progmem_bytes_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, data, len); + } + [[nodiscard]] static inline uint8_t *encode_progmem_bytes_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, + uint32_t field_id, const uint8_t *data, size_t len) { + pos = encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 2); // type 2: Length-delimited + pos = encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, len); + PROTO_ENCODE_CHECK_BOUNDS(pos, len); + progmem_memcpy(pos, data, len); + return pos + len; + } [[nodiscard]] static inline uint8_t *encode_uint32_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, uint32_t value) { pos = encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 0); @@ -572,22 +580,36 @@ class ProtoEncode { uint32_t field_id, int64_t value) { return encode_uint64_force(pos PROTO_ENCODE_DEBUG_ARG, field_id, encode_zigzag64(value)); } - /// Sub-message encoding: sync pos to buffer, delegate, read the cursor back. + /// Repeated sub-message element; the constant tag is written inline. template [[nodiscard]] static inline uint8_t *encode_sub_message(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, - ProtoWriteBuffer &buffer, uint32_t field_id, const T &value) { - buffer.set_pos(pos); - buffer.encode_sub_message(field_id, value); - return buffer.get_pos(); + uint32_t field_id, const T &value) { + pos = encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 2); + return encode_sub_message_body(pos PROTO_ENCODE_DEBUG_ARG, &value, &T::encode_msg); } + /// Singular sub-message field, skipped when it encodes to nothing. template [[nodiscard]] static inline uint8_t *encode_optional_sub_message(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, - ProtoWriteBuffer &buffer, uint32_t field_id, - const T &value) { - buffer.set_pos(pos); - buffer.encode_optional_sub_message(field_id, value); - return buffer.get_pos(); + uint32_t field_id, const T &value) { + uint32_t nested_size = T::calc_size_msg(&value); + if (nested_size == 0) + return pos; + pos = encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 2); +#ifdef ESPHOME_DEBUG_API + uint8_t *end = encode_sized_sub_message_body(pos PROTO_ENCODE_DEBUG_ARG, nested_size, &value, &T::encode_msg); + proto_check_sub_message_size(field_id, nested_size, pos, end); + return end; +#else + return encode_sized_sub_message_body(pos, nested_size, &value, &T::encode_msg); +#endif } + /// Length and body, length backpatched after the body is written. + [[nodiscard]] static uint8_t *encode_sub_message_body(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, + const void *value, ProtoEncodeFn encode_fn); + /// Length and body for a precomputed size. + [[nodiscard]] static uint8_t *encode_sized_sub_message_body(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, + uint32_t nested_size, const void *value, + ProtoEncodeFn encode_fn); private: /// Unaligned little endian store of four bytes: byte stores where the outlined helper lives (ESP-IDF, ARM @@ -701,9 +723,7 @@ class ProtoMessage { public: // Non-virtual defaults for messages with no fields; generated classes hide all four. The // static encode_msg/calc_size_msg take const void * so &T::encode_msg needs no thunk. - static uint8_t *encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) { - return buffer.get_pos(); - } + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM) { return pos; } static uint32_t calc_size_msg(const void *self) { return 0; } uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const { return buffer.get_pos(); } uint32_t calculate_size() const { return 0; } @@ -957,18 +977,6 @@ class ProtoSize { } }; -// Implementation of methods that depend on ProtoSize being fully defined - -// Thin template wrapper; delegates to non-template core in proto.cpp. -template inline void ProtoWriteBuffer::encode_sub_message(uint32_t field_id, const T &value) { - this->encode_sub_message(field_id, &value, &T::encode_msg); -} - -// Thin template wrapper; delegates to non-template core. -template inline void ProtoWriteBuffer::encode_optional_sub_message(uint32_t field_id, const T &value) { - this->encode_optional_sub_message(field_id, T::calc_size_msg(&value), &value, &T::encode_msg); -} - template const char *proto_enum_to_string(T value); // ProtoService removed — its methods were inlined into APIConnection. diff --git a/esphome/components/api/wizard.py b/esphome/components/api/wizard.py new file mode 100644 index 00000000000..3ab80a063ee --- /dev/null +++ b/esphome/components/api/wizard.py @@ -0,0 +1,458 @@ +"""The device wizard of the api component: schema, validation and code generation. + +Home Assistant shows the wizard when the device is added. See api_wizard.h for the C++ side. +""" + +from collections.abc import Callable +import importlib +import json +from typing import Any + +from esphome import automation +import esphome.codegen as cg +from esphome.components.const import CONF_DESCRIPTION +from esphome.components.homeassistant import DOMAIN as HOMEASSISTANT_DOMAIN +import esphome.config_validation as cv +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_DEVICE_ID, + CONF_DOMAIN, + CONF_ENTITY_ID, + CONF_ID, + CONF_INTERNAL, + CONF_NAME, + CONF_PAGES, + CONF_PLATFORM, + CONF_TARGET, +) +from esphome.core import CORE, ID +import esphome.final_validate as fv +from esphome.helpers import ( + fnv1_hash, + fnv1_hash_object_id, + fnv1a_32bit_hash, + zstd_module, +) +from esphome.types import ConfigType + +API_DOMAIN = "api" + +CONF_ENTITIES = "entities" +CONF_ENTITY = "entity" +CONF_INPUTS = "inputs" +CONF_INTEGRATION = "integration" +CONF_SUPPORTED_FEATURES = "supported_features" +CONF_TITLE = "title" +CONF_WIZARD = "wizard" + +_API = cg.esphome_ns.namespace("api") +WizardInput = _API.class_("WizardInput") + +WIZARD_ENTITY_ID_BUFFER_SIZE = ( + 256 # api_wizard.h; Home Assistant entity IDs are at most 255 bytes +) +# One API message must fit APIBuffer::MAX_SIZE (65535) together with the largest frame header (7 bytes, +# Noise) and footer (16 bytes, Noise MAC) +WIZARD_RESPONSE_MAX_SIZE = 65535 - 7 - 16 +# Version of the JSON the wizard is sent as, see wizard_document() +WIZARD_JSON_VERSION = 1 +# zstd level the JSON is compressed with. Output is deterministic for a given zstd version. +WIZARD_ZSTD_LEVEL = 19 + +# Wizard string limits; api.proto documents the same values +WIZARD_TITLE_MAX_LENGTH = 127 +WIZARD_DESCRIPTION_MAX_LENGTH = 255 +WIZARD_FILTER_MAX_LENGTH = 63 +WIZARD_SUPPORTED_FEATURE_MAX_LENGTH = 127 + + +def _wizard_text(max_length: int) -> Callable[[Any], str]: + """A string passed to Home Assistant verbatim, so it may be a [%key:...%] translation placeholder.""" + return cv.All(cv.string_strict, cv.Length(max=max_length)) + + +def _wizard_strings(max_length: int) -> Callable[[Any], list[str]]: + """A single string or a list of strings, always validated to a non-empty list.""" + return cv.All( + cv.ensure_list(cv.All(cv.string_strict, cv.Length(min=1, max=max_length))), + cv.Length(min=1), + ) + + +# Mirrors Home Assistant's EntityFilterSelectorConfig +WIZARD_ENTITY_FILTER_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_INTEGRATION): cv.All( + cv.string_strict, cv.Length(min=1, max=WIZARD_FILTER_MAX_LENGTH) + ), + cv.Optional(CONF_DOMAIN): _wizard_strings(WIZARD_FILTER_MAX_LENGTH), + cv.Optional(CONF_DEVICE_CLASS): _wizard_strings(WIZARD_FILTER_MAX_LENGTH), + cv.Optional(CONF_SUPPORTED_FEATURES): _wizard_strings( + WIZARD_SUPPORTED_FEATURE_MAX_LENGTH + ), + } + ), + cv.has_at_least_one_key( + CONF_INTEGRATION, CONF_DOMAIN, CONF_DEVICE_CLASS, CONF_SUPPORTED_FEATURES + ), +) + +WIZARD_ENTITY_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(cg.EntityBase), + cv.Optional(CONF_DESCRIPTION): _wizard_text(WIZARD_DESCRIPTION_MAX_LENGTH), + } +) + + +# An input is either standalone (id declares a new WizardInput) or linked to a homeassistant entity that has no +# entity_id of its own: Home Assistant sets it +WIZARD_INPUT_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_ID): cv.declare_id(WizardInput), + cv.Optional(CONF_ENTITY): cv.use_id(cg.EntityBase), + cv.Optional(CONF_DESCRIPTION): _wizard_text(WIZARD_DESCRIPTION_MAX_LENGTH), + cv.Optional(CONF_TARGET): cv.Schema( + { + cv.Required(CONF_ENTITY): cv.All( + cv.ensure_list(WIZARD_ENTITY_FILTER_SCHEMA), cv.Length(min=1) + ), + } + ), + } + ), + cv.has_exactly_one_key(CONF_ID, CONF_ENTITY), +) + +WIZARD_PAGE_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_TITLE): _wizard_text(WIZARD_TITLE_MAX_LENGTH), + cv.Optional(CONF_DESCRIPTION): _wizard_text(WIZARD_DESCRIPTION_MAX_LENGTH), + cv.Optional(CONF_ENTITIES): cv.All( + cv.ensure_list(WIZARD_ENTITY_SCHEMA), cv.Length(min=1) + ), + cv.Optional(CONF_INPUTS): cv.All( + cv.ensure_list(WIZARD_INPUT_SCHEMA), cv.Length(min=1) + ), + } + ), + cv.has_at_least_one_key(CONF_ENTITIES, CONF_INPUTS), +) + + +def _wizard_inputs(wizard: ConfigType) -> list[ConfigType]: + return [conf for page in wizard[CONF_PAGES] for conf in page.get(CONF_INPUTS, [])] + + +def _wizard_input_id(conf: ConfigType) -> ID: + """The ID that names an input: the one a standalone input declares, or its linked entity.""" + return conf[CONF_ID] if CONF_ID in conf else conf[CONF_ENTITY] + + +def _validate_unique_wizard_inputs(wizard: ConfigType) -> ConfigType: + """An input is identified on the wire by a hash of its ID, so IDs and hashes must be unique.""" + seen: dict[int, str] = {} + for conf in _wizard_inputs(wizard): + input_id = _wizard_input_id(conf).id + if (key := fnv1_hash(input_id)) in seen: + if seen[key] == input_id: + raise cv.Invalid(f"Wizard input '{input_id}' is used more than once") + raise cv.Invalid( + f"Wizard inputs '{seen[key]}' and '{input_id}' have the same hash, rename one" + ) + seen[key] = input_id + return wizard + + +WIZARD_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_PAGES): cv.All( + cv.ensure_list(WIZARD_PAGE_SCHEMA), cv.Length(min=1) + ), + } + ), + _validate_unique_wizard_inputs, +) + +# Platforms of the homeassistant component that a wizard input can stand for +WIZARD_INPUT_DOMAINS = ( + "binary_sensor", + "button", + "number", + "select", + "sensor", + "switch", + "text", + "text_sensor", +) +# Platforms that act on one family of Home Assistant domains, which the input's filters must stay within +WIZARD_DOMAIN_LIMITED_PLATFORMS = ("button", "number", "select", "switch", "text") + + +def wizard_input_ids(api_config: ConfigType) -> set[str]: + """The IDs of the entities that are linked inputs of the wizard in the given api config.""" + if (wizard := api_config.get(CONF_WIZARD)) is None: + return set() + return { + conf[CONF_ENTITY].id for conf in _wizard_inputs(wizard) if CONF_ENTITY in conf + } + + +def _wizard_buffer_name(entity_id: ID) -> str: + return f"api_wizard_input_{entity_id.id}" + + +def wizard_input_buffer(entity_id: ID) -> str | None: + """Name of the RAM buffer holding the Home Assistant entity ID of a wizard input. + + Returns None when the entity is not an input of the wizard. The buffer is defined by the api + codegen, and the homeassistant entity of the input is given it in place of a constant. + """ + if entity_id.id not in wizard_input_ids(CORE.config.get(API_DOMAIN, {})): + return None + return _wizard_buffer_name(entity_id) + + +def _wizard_input_declaration( + config: fv.FinalValidateConfig, entity_id: ID +) -> tuple[str, ConfigType]: + """The domain (like sensor) and the config an input ID is declared in.""" + path = config.get_path_for_id(entity_id)[:-1] + return path[0], config.get_config_for_path(path) + + +def _wizard_input_filters( + conf: ConfigType, config: fv.FinalValidateConfig +) -> list[ConfigType]: + """The entity filters of an input, with the defaults of a domain limited linked platform.""" + if (filters := conf.get(CONF_TARGET, {}).get(CONF_ENTITY)) is not None: + return filters + if CONF_ENTITY in conf: + domain, _ = _wizard_input_declaration(config, conf[CONF_ENTITY]) + if domains := _wizard_default_domains(domain): + return [{CONF_DOMAIN: domains}] + return [] + + +def _wizard_defines(wizard: ConfigType) -> set[str]: + """The defines for the parts of the wizard the configuration uses, so the rest is not compiled.""" + inputs = _wizard_inputs(wizard) + defines = {"USE_API_WIZARD"} + if inputs: + defines.add("USE_API_WIZARD_INPUTS") + if any(CONF_ENTITY in conf for conf in inputs): + defines.add("USE_API_WIZARD_LINKED_INPUTS") + if any(CONF_ID in conf for conf in inputs): + defines.add("USE_API_WIZARD_STANDALONE_INPUTS") + return defines + + +def _wizard_default_domains(domain: str) -> list[str] | None: + """The domains Home Assistant entities can be picked from when the input sets no target.""" + if domain in WIZARD_DOMAIN_LIMITED_PLATFORMS: + platform = importlib.import_module(f"esphome.components.homeassistant.{domain}") + return list(platform.SUPPORTED_DOMAINS) + return None + + +def _validate_wizard_input(conf: ConfigType) -> ConfigType: + if CONF_ENTITY not in conf: + return conf + domain, declaration = _wizard_input_declaration( + fv.full_config.get(), conf[CONF_ENTITY] + ) + if ( + declaration.get(CONF_PLATFORM) != HOMEASSISTANT_DOMAIN + or domain not in WIZARD_INPUT_DOMAINS + ): + raise cv.Invalid( + f"Wizard input '{conf[CONF_ENTITY].id}' must be a homeassistant " + f"{', '.join(WIZARD_INPUT_DOMAINS)} entity" + ) + if CONF_ENTITY_ID in declaration: + # An entity_id in the configuration is a static entry, which is kept apart from the dynamic ones + raise cv.Invalid( + f"'{conf[CONF_ENTITY].id}' has an entity_id set in its configuration, so it cannot be a " + "wizard input. Remove entity_id to let Home Assistant set it through the wizard." + ) + if domain in WIZARD_DOMAIN_LIMITED_PLATFORMS: + supported = _wizard_default_domains(domain) + for entity_filter in conf.get(CONF_TARGET, {}).get(CONF_ENTITY, []): + if not (domains := entity_filter.get(CONF_DOMAIN)): + raise cv.Invalid( + f"Every filter of a homeassistant {domain} input must set domain" + ) + if unsupported := [d for d in domains if d not in supported]: + raise cv.Invalid( + f"The homeassistant {domain} does not support the domain(s) " + f"{', '.join(unsupported)}. Supported: {', '.join(supported)}" + ) + return conf + + +def _validate_wizard_entity_exposed(value: ID) -> ID: + """Reject entities that are internal, as they are not exposed over the API, or have no name. + + The key a client knows an entity by is a hash of its name. Without a name of its own, the + device works the name out at runtime from its friendly name, which can add the MAC address, + so the key cannot be known when the wizard is built. + """ + _, declaration = _wizard_input_declaration(fv.full_config.get(), value) + if declaration.get(CONF_INTERNAL, False): + raise cv.Invalid( + f"Entity '{value.id}' is internal, so it is not exposed over the API " + "and cannot be used in the wizard" + ) + if not declaration.get(CONF_NAME): + raise cv.Invalid( + f"Entity '{value.id}' has no name of its own, so its key is not known " + "when the wizard is built. Give it a name to use it in the wizard" + ) + return value + + +_WIZARD_FINAL_VALIDATE_SCHEMA = cv.Schema( + { + cv.Optional(CONF_WIZARD): { + cv.Optional(CONF_PAGES): [ + { + cv.Optional(CONF_ENTITIES): [ + {cv.Optional(CONF_ID): _validate_wizard_entity_exposed} + ], + cv.Optional(CONF_INPUTS): [_validate_wizard_input], + } + ] + } + }, + extra=cv.ALLOW_EXTRA, +) + + +def final_validate(config: ConfigType) -> None: + """Final validation of the wizard in the given api config, if it has one.""" + _WIZARD_FINAL_VALIDATE_SCHEMA(config) + if (wizard := config.get(CONF_WIZARD)) is not None: + size = len(wizard_blob(wizard, fv.full_config.get())) + if size > WIZARD_RESPONSE_MAX_SIZE: + raise cv.Invalid( + f"The compressed wizard is {size} bytes, {size - WIZARD_RESPONSE_MAX_SIZE} " + f"bytes over the {WIZARD_RESPONSE_MAX_SIZE} bytes one API message can hold. " + "Shorten the texts or use fewer pages, entities or filters", + path=[CONF_WIZARD], + ) + + +WIZARD_INPUT_IS_SET_SCHEMA = cv.maybe_simple_value( + {cv.Required(CONF_ID): cv.use_id(WizardInput)}, key=CONF_ID +) + +# Only for standalone inputs: a linked input is read through its homeassistant entity +automation.register_apply_condition( + "api.wizard.input_is_set", WIZARD_INPUT_IS_SET_SCHEMA, "has_entity_id()" +) + + +def _entity_document(conf: ConfigType, config: fv.FinalValidateConfig) -> ConfigType: + """An entity of the device that the page shows, keyed as ListEntitiesResponse keys it.""" + _, declaration = _wizard_input_declaration(config, conf[CONF_ID]) + document: ConfigType = {"key": fnv1_hash_object_id(declaration[CONF_NAME])} + if (device := declaration.get(CONF_DEVICE_ID)) is not None: + document["device_id"] = fnv1a_32bit_hash(device.id) + if description := conf.get(CONF_DESCRIPTION): + document[CONF_DESCRIPTION] = description + return document + + +def _input_document(conf: ConfigType, config: fv.FinalValidateConfig) -> ConfigType: + document: ConfigType = {"key": fnv1_hash(_wizard_input_id(conf).id)} + if description := conf.get(CONF_DESCRIPTION): + document[CONF_DESCRIPTION] = description + if filters := _wizard_input_filters(conf, config): + document["entity_filters"] = [dict(entity_filter) for entity_filter in filters] + return document + + +def wizard_document(wizard: ConfigType, config: fv.FinalValidateConfig) -> ConfigType: + """The wizard as the JSON document the device sends, before it is serialised. + + This is the format Home Assistant reads, and api.proto documents it for clients. Version 1: + + {"version": 1, + "pages": [{"title": "...", "description": "...", + "entities": [{"key": 123, "device_id": 456, "description": "..."}], + "inputs": [{"key": 789, "description": "...", + "entity_filters": [{"integration": "...", "domain": ["..."], + "device_class": ["..."], "supported_features": ["..."]}]}]}]} + + Anything empty or unset, and every empty list, is left out. Strings are passed through as + written, so they may be Home Assistant translation placeholders. + + - An entity key is the key ListEntitiesResponse sends for the entity: the FNV-1 hash of the + object id made from its name (entity_helpers). device_id is the hash of the ESPHome id of + the device it belongs to (esphome/core/config.py), and is left out for the main device. + - An input key is the FNV-1 hash of the ESPHome id of the input, or of the linked entity. A linked + entity must not set entity_id, as that is a static entry kept apart from the ones the wizard sets. + - entity_filters are the filters of the input, or the default filters of a linked switch, + number, text, select or button. + """ + pages: list[ConfigType] = [] + for page in wizard[CONF_PAGES]: + document: ConfigType = {} + for key in (CONF_TITLE, CONF_DESCRIPTION): + if value := page.get(key): + document[key] = value + if entities := [ + _entity_document(e, config) for e in page.get(CONF_ENTITIES, []) + ]: + document[CONF_ENTITIES] = entities + if inputs := [_input_document(i, config) for i in page.get(CONF_INPUTS, [])]: + document[CONF_INPUTS] = inputs + pages.append(document) + return {"version": WIZARD_JSON_VERSION, CONF_PAGES: pages} + + +def wizard_blob(wizard: ConfigType, config: fv.FinalValidateConfig) -> bytes: + """The wizard document as compact, sorted UTF-8 JSON in a single zstd frame.""" + text = json.dumps( + wizard_document(wizard, config), + separators=(",", ":"), + sort_keys=True, + ensure_ascii=False, + ) + return zstd_module().compress(text.encode("utf-8"), level=WIZARD_ZSTD_LEVEL) + + +async def to_code(wizard: ConfigType) -> None: + """Emit the compressed wizard, the table of inputs and the defines. + + The API reads both tables from its own sources, so they are externally linked PROGMEM arrays. + """ + blob = wizard_blob(wizard, CORE.config) + cg.extern_progmem_array("esphome::api::API_WIZARD_DATA", cg.uint8, list(blob)) + cg.add_define("API_WIZARD_DATA_SIZE", len(blob)) + for define in sorted(_wizard_defines(wizard)): + cg.add_define(define) + entries: list[cg.RawExpression] = [] + for conf in _wizard_inputs(wizard): + input_id = _wizard_input_id(conf) + # Every buffer starts empty, until the wizard sets it. A linked homeassistant entity uses it as its entity id. + buffer = _wizard_buffer_name(input_id) + cg.add_global( + cg.RawStatement( + f'static char {buffer}[{WIZARD_ENTITY_ID_BUFFER_SIZE}] = "";' + ) + ) + if CONF_ID in conf: + cg.new_Pvariable(input_id, cg.RawExpression(buffer)) + entries.append(cg.RawExpression(f"{{{fnv1_hash(input_id.id)}u, {buffer}}}")) + if entries: + cg.extern_progmem_array( + "esphome::api::API_WIZARD_INPUTS", + cg.esphome_ns.namespace("api").struct("WizardInputEntry"), + entries, + ) + cg.add_define("API_WIZARD_INPUT_COUNT", len(entries)) diff --git a/esphome/components/aqi/__init__.py b/esphome/components/aqi/__init__.py index 17d434294a3..02c0bd2228a 100644 --- a/esphome/components/aqi/__init__.py +++ b/esphome/components/aqi/__init__.py @@ -1,6 +1,7 @@ import esphome.codegen as cg CODEOWNERS = ["@jasstrong", "@ximex", "@freekode"] +DOMAIN = "aqi" aqi_ns = cg.esphome_ns.namespace("aqi") AQICalculatorType = aqi_ns.enum("AQICalculatorType") diff --git a/esphome/components/aqi/aqi_sensor.cpp b/esphome/components/aqi/aqi_sensor.cpp index e78f301b30d..c242ee39136 100644 --- a/esphome/components/aqi/aqi_sensor.cpp +++ b/esphome/components/aqi/aqi_sensor.cpp @@ -3,20 +3,22 @@ namespace esphome::aqi { -static const char *const TAG = "aqi"; +ESPHOME_LOG_TAG(TAG, "aqi"); + +static constexpr uint32_t CALCULATE_DEFER_ID = 0; void AQISensor::setup() { if (this->pm_2_5_sensor_ != nullptr) { this->pm_2_5_sensor_->add_on_state_callback([this](float value) { this->pm_2_5_value_ = value; // Defer calculation to avoid double-publishing if both sensors update in the same loop - this->defer("update", [this]() { this->calculate_aqi_(); }); + this->defer(CALCULATE_DEFER_ID, [this]() { this->calculate_aqi_(); }); }); } if (this->pm_10_0_sensor_ != nullptr) { this->pm_10_0_sensor_->add_on_state_callback([this](float value) { this->pm_10_0_value_ = value; - this->defer("update", [this]() { this->calculate_aqi_(); }); + this->defer(CALCULATE_DEFER_ID, [this]() { this->calculate_aqi_(); }); }); } } diff --git a/esphome/components/as3935/__init__.py b/esphome/components/as3935/__init__.py index bd02d22d1b3..b7b5b09c626 100644 --- a/esphome/components/as3935/__init__.py +++ b/esphome/components/as3935/__init__.py @@ -17,6 +17,7 @@ from esphome.const import ( from esphome.cpp_generator import MockObj from esphome.types import ConfigType +DOMAIN = "as3935" MULTI_CONF = True CONF_AS3935_ID = "as3935_id" diff --git a/esphome/components/as3935/as3935.cpp b/esphome/components/as3935/as3935.cpp index a1d74c78deb..00ac7ef365a 100644 --- a/esphome/components/as3935/as3935.cpp +++ b/esphome/components/as3935/as3935.cpp @@ -3,7 +3,7 @@ namespace esphome::as3935 { -static const char *const TAG = "as3935"; +ESPHOME_LOG_TAG(TAG, "as3935"); void AS3935Component::setup() { this->irq_pin_->setup(); diff --git a/esphome/components/as3935_i2c/__init__.py b/esphome/components/as3935_i2c/__init__.py index 83924de760f..e1d2d89ec5d 100644 --- a/esphome/components/as3935_i2c/__init__.py +++ b/esphome/components/as3935_i2c/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["as3935"] DEPENDENCIES = ["i2c"] +DOMAIN = "as3935_i2c" as3935_i2c_ns = cg.esphome_ns.namespace("as3935_i2c") I2CAS3935 = as3935_i2c_ns.class_("I2CAS3935Component", as3935.AS3935, i2c.I2CDevice) diff --git a/esphome/components/as3935_i2c/as3935_i2c.cpp b/esphome/components/as3935_i2c/as3935_i2c.cpp index b3d015114f0..06f816182ed 100644 --- a/esphome/components/as3935_i2c/as3935_i2c.cpp +++ b/esphome/components/as3935_i2c/as3935_i2c.cpp @@ -3,7 +3,7 @@ namespace esphome::as3935_i2c { -static const char *const TAG = "as3935_i2c"; +ESPHOME_LOG_TAG(TAG, "as3935_i2c"); void I2CAS3935Component::write_register(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_pos) { uint8_t write_reg; diff --git a/esphome/components/as3935_spi/__init__.py b/esphome/components/as3935_spi/__init__.py index 332a51c7a9f..9313705ab66 100644 --- a/esphome/components/as3935_spi/__init__.py +++ b/esphome/components/as3935_spi/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["as3935"] DEPENDENCIES = ["spi"] +DOMAIN = "as3935_spi" as3935_spi_ns = cg.esphome_ns.namespace("as3935_spi") SPIAS3935 = as3935_spi_ns.class_("SPIAS3935Component", as3935.AS3935, spi.SPIDevice) diff --git a/esphome/components/as3935_spi/as3935_spi.cpp b/esphome/components/as3935_spi/as3935_spi.cpp index 026fde2f213..f9661263574 100644 --- a/esphome/components/as3935_spi/as3935_spi.cpp +++ b/esphome/components/as3935_spi/as3935_spi.cpp @@ -3,7 +3,7 @@ namespace esphome::as3935_spi { -static const char *const TAG = "as3935_spi"; +ESPHOME_LOG_TAG(TAG, "as3935_spi"); void SPIAS3935Component::setup() { this->spi_setup(); diff --git a/esphome/components/as5600/__init__.py b/esphome/components/as5600/__init__.py index 780712c3bd0..3cc5ed5cc1a 100644 --- a/esphome/components/as5600/__init__.py +++ b/esphome/components/as5600/__init__.py @@ -18,6 +18,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@ammmze"] DEPENDENCIES = ["i2c"] +DOMAIN = "as5600" MULTI_CONF = True as5600_ns = cg.esphome_ns.namespace("as5600") diff --git a/esphome/components/as5600/as5600.cpp b/esphome/components/as5600/as5600.cpp index da1add5458b..fc768b383b0 100644 --- a/esphome/components/as5600/as5600.cpp +++ b/esphome/components/as5600/as5600.cpp @@ -3,7 +3,7 @@ namespace esphome::as5600 { -static const char *const TAG = "as5600"; +ESPHOME_LOG_TAG(TAG, "as5600"); // Configuration registers static const uint8_t REGISTER_ZMCO = 0x00; // 8 bytes / R diff --git a/esphome/components/as5600/sensor/as5600_sensor.cpp b/esphome/components/as5600/sensor/as5600_sensor.cpp index ba295659a13..88884805a0f 100644 --- a/esphome/components/as5600/sensor/as5600_sensor.cpp +++ b/esphome/components/as5600/sensor/as5600_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::as5600 { -static const char *const TAG = "as5600.sensor"; +ESPHOME_LOG_TAG(TAG, "as5600.sensor"); // Configuration registers static const uint8_t REGISTER_ZMCO = 0x00; // 8 bytes / R diff --git a/esphome/components/as7341/__init__.py b/esphome/components/as7341/__init__.py index e69de29bb2d..bdbe78a4ece 100644 --- a/esphome/components/as7341/__init__.py +++ b/esphome/components/as7341/__init__.py @@ -0,0 +1 @@ +DOMAIN = "as7341" diff --git a/esphome/components/as7341/as7341.cpp b/esphome/components/as7341/as7341.cpp index 32094ddf101..359636a4992 100644 --- a/esphome/components/as7341/as7341.cpp +++ b/esphome/components/as7341/as7341.cpp @@ -4,7 +4,7 @@ namespace esphome::as7341 { -static const char *const TAG = "as7341"; +ESPHOME_LOG_TAG(TAG, "as7341"); void AS7341Component::setup() { LOG_I2C_DEVICE(this); diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index 31007022d56..b67202db593 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] +DOMAIN = "async_tcp" def AUTO_LOAD() -> list[str]: diff --git a/esphome/components/async_tcp/async_tcp_socket.cpp b/esphome/components/async_tcp/async_tcp_socket.cpp index 853ff206c25..03191f6e370 100644 --- a/esphome/components/async_tcp/async_tcp_socket.cpp +++ b/esphome/components/async_tcp/async_tcp_socket.cpp @@ -9,7 +9,7 @@ namespace esphome::async_tcp { -static const char *const TAG = "async_tcp"; +ESPHOME_LOG_TAG(TAG, "async_tcp"); // Read buffer size matches TCP MSS (1500 MTU - 40 bytes IP/TCP headers). // This implementation only runs on ESP-IDF and host which have ample stack. diff --git a/esphome/components/at581x/__init__.py b/esphome/components/at581x/__init__.py index a29e166745e..50c9080c2dd 100644 --- a/esphome/components/at581x/__init__.py +++ b/esphome/components/at581x/__init__.py @@ -8,6 +8,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@X-Ryl669"] DEPENDENCIES = ["i2c"] +DOMAIN = "at581x" MULTI_CONF = True diff --git a/esphome/components/at581x/at581x.cpp b/esphome/components/at581x/at581x.cpp index 575a9afd40b..618e4bee5b6 100644 --- a/esphome/components/at581x/at581x.cpp +++ b/esphome/components/at581x/at581x.cpp @@ -51,7 +51,7 @@ const uint8_t SELF_CHECK_TIME_ADDR = 0x38; // 2 bytes, up to 0x39 namespace esphome::at581x { -static const char *const TAG = "at581x"; +ESPHOME_LOG_TAG(TAG, "at581x"); bool AT581XComponent::i2c_write_reg(uint8_t addr, uint8_t data) { return this->write_register(addr, &data, 1) == esphome::i2c::NO_ERROR; diff --git a/esphome/components/atc_mithermometer/__init__.py b/esphome/components/atc_mithermometer/__init__.py index e69de29bb2d..3d517583e83 100644 --- a/esphome/components/atc_mithermometer/__init__.py +++ b/esphome/components/atc_mithermometer/__init__.py @@ -0,0 +1 @@ +DOMAIN = "atc_mithermometer" diff --git a/esphome/components/atc_mithermometer/atc_mithermometer.cpp b/esphome/components/atc_mithermometer/atc_mithermometer.cpp index 22cb2b31504..95b482c8404 100644 --- a/esphome/components/atc_mithermometer/atc_mithermometer.cpp +++ b/esphome/components/atc_mithermometer/atc_mithermometer.cpp @@ -3,7 +3,7 @@ namespace esphome::atc_mithermometer { -static const char *const TAG = "atc_mithermometer"; +ESPHOME_LOG_TAG(TAG, "atc_mithermometer"); void ATCMiThermometer::dump_config() { ESP_LOGCONFIG(TAG, "ATC MiThermometer"); diff --git a/esphome/components/atm90e26/__init__.py b/esphome/components/atm90e26/__init__.py index ac441a9c2d3..b1047e9290f 100644 --- a/esphome/components/atm90e26/__init__.py +++ b/esphome/components/atm90e26/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@danieltwagner"] +DOMAIN = "atm90e26" diff --git a/esphome/components/atm90e26/atm90e26.cpp b/esphome/components/atm90e26/atm90e26.cpp index 46948be47d8..8ec611357c8 100644 --- a/esphome/components/atm90e26/atm90e26.cpp +++ b/esphome/components/atm90e26/atm90e26.cpp @@ -4,7 +4,7 @@ namespace esphome::atm90e26 { -static const char *const TAG = "atm90e26"; +ESPHOME_LOG_TAG(TAG, "atm90e26"); void ATM90E26Component::update() { if (this->read16_(ATM90E26_REGISTER_FUNCEN) != 0x0030) { diff --git a/esphome/components/atm90e32/__init__.py b/esphome/components/atm90e32/__init__.py index 766807872bc..d25f5c1ce94 100644 --- a/esphome/components/atm90e32/__init__.py +++ b/esphome/components/atm90e32/__init__.py @@ -1,6 +1,7 @@ import esphome.codegen as cg CODEOWNERS = ["@circuitsetup", "@descipher"] +DOMAIN = "atm90e32" atm90e32_ns = cg.esphome_ns.namespace("atm90e32") ATM90E32Component = atm90e32_ns.class_("ATM90E32Component", cg.Component) diff --git a/esphome/components/atm90e32/atm90e32.cpp b/esphome/components/atm90e32/atm90e32.cpp index 23701e78348..0c4f095ab57 100644 --- a/esphome/components/atm90e32/atm90e32.cpp +++ b/esphome/components/atm90e32/atm90e32.cpp @@ -7,7 +7,7 @@ namespace esphome::atm90e32 { -static const char *const TAG = "atm90e32"; +ESPHOME_LOG_TAG(TAG, "atm90e32"); static const LogString *offset_calibration_name(bool power_offsets) { return power_offsets ? LOG_STR("Power offset") : LOG_STR("Offset"); diff --git a/esphome/components/atm90e32/button/atm90e32_button.cpp b/esphome/components/atm90e32/button/atm90e32_button.cpp index e120e453644..03871495214 100644 --- a/esphome/components/atm90e32/button/atm90e32_button.cpp +++ b/esphome/components/atm90e32/button/atm90e32_button.cpp @@ -4,7 +4,7 @@ namespace esphome::atm90e32 { -static const char *const TAG = "atm90e32.button"; +ESPHOME_LOG_TAG(TAG, "atm90e32.button"); void ATM90E32GainCalibrationButton::press_action() { if (this->parent_ == nullptr) { diff --git a/esphome/components/audio/__init__.py b/esphome/components/audio/__init__.py index b882aaa6b75..1480faa8c3f 100644 --- a/esphome/components/audio/__init__.py +++ b/esphome/components/audio/__init__.py @@ -6,7 +6,7 @@ import esphome.codegen as cg from esphome.components.esp32 import ( add_idf_component, add_idf_sdkconfig_option, - include_builtin_idf_component, + request_http_client, require_certificate_bundle, ) import esphome.config_validation as cv @@ -334,8 +334,7 @@ def _emit_memory_pair(value: str | None, psram_key: str, internal_key: str) -> N async def to_code(config: ConfigType) -> None: - # Re-enable ESP-IDF's HTTP client (excluded by default to save compile time) - include_builtin_idf_component("esp_http_client") + request_http_client() # HTTPS streams verify the server against the root certificate bundle require_certificate_bundle() diff --git a/esphome/components/audio/audio_decoder.cpp b/esphome/components/audio/audio_decoder.cpp index 051395606c8..13dea3f9bc4 100644 --- a/esphome/components/audio/audio_decoder.cpp +++ b/esphome/components/audio/audio_decoder.cpp @@ -7,7 +7,7 @@ namespace esphome::audio { -static const char *const TAG = "audio.decoder"; +ESPHOME_LOG_TAG(TAG, "audio.decoder"); static const uint32_t READ_WRITE_TIMEOUT_MS = 20; // Timeout for transferring audio data diff --git a/esphome/components/audio/audio_reader.cpp b/esphome/components/audio/audio_reader.cpp index e69f33ac2d5..ea3c82e637e 100644 --- a/esphome/components/audio/audio_reader.cpp +++ b/esphome/components/audio/audio_reader.cpp @@ -22,7 +22,7 @@ static const size_t HTTP_STREAM_BUFFER_SIZE = 2048; static const uint8_t MAX_REDIRECTIONS = 5; -static const char *const TAG = "audio_reader"; +ESPHOME_LOG_TAG(TAG, "audio_reader"); // Some common HTTP status codes - borrowed from http_request component accessed 20241224 enum HttpStatus { @@ -72,7 +72,7 @@ esp_err_t AudioReader::add_sink(const std::weak_ptr &ou return ESP_ERR_INVALID_STATE; } -esp_err_t AudioReader::start(AudioFile *audio_file, AudioFileType &file_type) { +esp_err_t AudioReader::start(const AudioFile *audio_file, AudioFileType &file_type) { file_type = AudioFileType::NONE; this->current_audio_file_ = audio_file; diff --git a/esphome/components/audio/audio_reader.h b/esphome/components/audio/audio_reader.h index b1f76172b01..db85185ca6c 100644 --- a/esphome/components/audio/audio_reader.h +++ b/esphome/components/audio/audio_reader.h @@ -47,7 +47,7 @@ class AudioReader { /// @param audio_file AudioFile struct containing the file. /// @param file_type AudioFileType variable passed-by-reference indicating the type of file being read. /// @return ESP_OK - esp_err_t start(AudioFile *audio_file, AudioFileType &file_type); + esp_err_t start(const AudioFile *audio_file, AudioFileType &file_type); /// @brief Reads new file data from the source and sends to the ring buffer sink. /// @return AudioReaderState @@ -69,7 +69,7 @@ class AudioReader { esp_http_client_handle_t client_{nullptr}; - AudioFile *current_audio_file_{nullptr}; + const AudioFile *current_audio_file_{nullptr}; AudioFileType audio_file_type_{AudioFileType::NONE}; const uint8_t *file_current_{nullptr}; }; diff --git a/esphome/components/audio_adc/__init__.py b/esphome/components/audio_adc/__init__.py index 7d4b51db047..ba6544b50b2 100644 --- a/esphome/components/audio_adc/__init__.py +++ b/esphome/components/audio_adc/__init__.py @@ -6,6 +6,7 @@ from esphome.core import CoroPriority, coroutine_with_priority from esphome.types import ConfigType CODEOWNERS = ["@kbx81"] +DOMAIN = "audio_adc" IS_PLATFORM_COMPONENT = True audio_adc_ns = cg.esphome_ns.namespace("audio_adc") diff --git a/esphome/components/audio_dac/__init__.py b/esphome/components/audio_dac/__init__.py index fbaa72014fc..346f1be798a 100644 --- a/esphome/components/audio_dac/__init__.py +++ b/esphome/components/audio_dac/__init__.py @@ -7,6 +7,7 @@ from esphome.core import CoroPriority, coroutine_with_priority from esphome.types import ConfigType CODEOWNERS = ["@kbx81"] +DOMAIN = "audio_dac" IS_PLATFORM_COMPONENT = True audio_dac_ns = cg.esphome_ns.namespace("audio_dac") diff --git a/esphome/components/audio_file/__init__.py b/esphome/components/audio_file/__init__.py index d59ed7411a4..7726ef5837e 100644 --- a/esphome/components/audio_file/__init__.py +++ b/esphome/components/audio_file/__init__.py @@ -19,7 +19,7 @@ from esphome.const import ( CONF_URL, ) from esphome.core import CORE, ID, HexInt -from esphome.cpp_generator import MockObj +from esphome.cpp_generator import MockObj, ProgmemAssignmentExpression from esphome.external_files import download_web_files_in_config from esphome.types import ConfigType @@ -151,11 +151,14 @@ TYPED_FILE_SCHEMA = cv.typed_schema( ) +CONF_FILE_DATA_ID = "file_data_id" + MEDIA_FILE_TYPE_SCHEMA = cv.Schema( { cv.Required(CONF_ID): cv.declare_id(audio.AudioFile), cv.Required(CONF_FILE): _file_schema, cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), + cv.GenerateID(CONF_FILE_DATA_ID): cv.declare_id(audio.AudioFile), } ) @@ -217,9 +220,9 @@ def audio_files_schema() -> cv.All: def generate_audio_file_code(file_config: ConfigType) -> MockObj: - """Generate the progmem data, AudioFile struct, and Pvariable for one file. + """Generate the progmem data and a flash AudioFile for one file. - Returns the created Pvariable. Caller is responsible for any further + Returns a const pointer to the AudioFile. Caller is responsible for any further registration (the audio_file component additionally registers each file in its named C++ registry; other consumers may skip that). """ @@ -230,17 +233,33 @@ def generate_audio_file_code(file_config: ConfigType) -> MockObj: else: data, media_file_type = read_audio_file_and_type(file_config) - rhs = [HexInt(x) for x in data] - prog_arr = cg.progmem_array(file_config[CONF_RAW_DATA_ID], rhs) - - media_files_struct = cg.StructInitializer( + # Global constants so the AudioFile lives in flash; the id stays a plain pointer + # because actions render id arguments as ``::``. + data_id = file_config[CONF_RAW_DATA_ID] + cg.add_global( + ProgmemAssignmentExpression( + data_id.type, data_id, cg.safe_exp([HexInt(x) for x in data]) + ) + ) + media_file = cg.StructInitializer( audio.AudioFile, - ("data", prog_arr), - ("length", len(rhs)), + ("data", MockObj(data_id, ".")), + ("length", len(data)), ("file_type", media_file_type), ) - - return cg.new_Pvariable(file_config[CONF_ID], media_files_struct) + file_var_id = file_config[CONF_ID] + storage = file_config[CONF_FILE_DATA_ID] + cg.add_global( + cg.RawStatement(f"static constexpr {storage.type} {storage} = {media_file};") + ) + cg.add_global( + cg.RawStatement( + f"static const {storage.type} *const {file_var_id} = &{storage};" + ) + ) + var = MockObj(file_var_id, "->") + CORE.register_variable(file_var_id, var) + return var CONFIG_SCHEMA = cv.All( diff --git a/esphome/components/audio_file/audio_file.h b/esphome/components/audio_file/audio_file.h index 537e19fb3ca..177374cf91a 100644 --- a/esphome/components/audio_file/audio_file.h +++ b/esphome/components/audio_file/audio_file.h @@ -10,14 +10,14 @@ namespace esphome::audio_file { struct NamedAudioFile { - audio::AudioFile *file; + const audio::AudioFile *file; const char *file_id; }; inline StaticVector named_audio_files; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -inline void add_named_audio_file(audio::AudioFile *file, const char *file_id) { +inline void add_named_audio_file(const audio::AudioFile *file, const char *file_id) { named_audio_files.push_back({file, file_id}); } diff --git a/esphome/components/audio_file/media_source/audio_file_media_source.cpp b/esphome/components/audio_file/media_source/audio_file_media_source.cpp index 0cda1eca9ed..0aebcc176fd 100644 --- a/esphome/components/audio_file/media_source/audio_file_media_source.cpp +++ b/esphome/components/audio_file/media_source/audio_file_media_source.cpp @@ -11,7 +11,7 @@ namespace esphome::audio_file { -static const char *const TAG = "audio_file_media_source"; +ESPHOME_LOG_TAG(TAG, "audio_file_media_source"); static constexpr uint32_t AUDIO_WRITE_TIMEOUT_MS = 50; static constexpr size_t DECODER_TASK_STACK_SIZE = 5120; diff --git a/esphome/components/audio_file/media_source/audio_file_media_source.h b/esphome/components/audio_file/media_source/audio_file_media_source.h index d269f77c357..12735eaca18 100644 --- a/esphome/components/audio_file/media_source/audio_file_media_source.h +++ b/esphome/components/audio_file/media_source/audio_file_media_source.h @@ -46,7 +46,7 @@ class AudioFileMediaSource final : public Component, protected: std::unique_ptr decoder_; audio::AudioStreamInfo stream_info_; - audio::AudioFile *current_file_{nullptr}; + const audio::AudioFile *current_file_{nullptr}; // Written from the main loop in handle_command(), read from the decoder task in // on_audio_write(). Must be atomic to avoid a data race. diff --git a/esphome/components/audio_http/__init__.py b/esphome/components/audio_http/__init__.py index e69de29bb2d..0126fa0e8bc 100644 --- a/esphome/components/audio_http/__init__.py +++ b/esphome/components/audio_http/__init__.py @@ -0,0 +1 @@ +DOMAIN = "audio_http" diff --git a/esphome/components/audio_http/audio_http_media_source.cpp b/esphome/components/audio_http/audio_http_media_source.cpp index fb8620f7d9a..ce1f2543107 100644 --- a/esphome/components/audio_http/audio_http_media_source.cpp +++ b/esphome/components/audio_http/audio_http_media_source.cpp @@ -11,7 +11,7 @@ namespace esphome::audio_http { -static const char *const TAG = "audio_http_media_source"; +ESPHOME_LOG_TAG(TAG, "audio_http_media_source"); // Decoder task / buffer tuning. Kept here as constants so the header stays free of magic numbers. static constexpr size_t DEFAULT_TRANSFER_BUFFER_SIZE = 8 * 1024; // Staging buffer between HTTP reader and decoder @@ -33,6 +33,9 @@ void AudioHTTPMediaSource::dump_config() { " Persistent Ring Buffer: %s\n" " Decoder Task Stack in PSRAM: %s", this->buffer_size_, YESNO(this->persistent_ring_buffer_), YESNO(this->decoder_task_stack_in_psram_)); +#ifdef USE_AUDIO_HTTP_CA_CERTIFICATE + ESP_LOGCONFIG(TAG, " Custom CA Certificate: %s", YESNO(this->http_ca_certificate_ != nullptr)); +#endif } void AudioHTTPMediaSource::setup() { @@ -52,6 +55,12 @@ void AudioHTTPMediaSource::setup() { config.reader_stack_size = READER_TASK_STACK_SIZE; config.decoder_stack_size = DECODER_TASK_STACK_SIZE; config.decoder_stack_in_psram = this->decoder_task_stack_in_psram_; +#ifdef USE_AUDIO_HTTP_CA_CERTIFICATE + // micro-decoder verifies HTTPS against this PEM only, skipping the built-in certificate bundle. + if (this->http_ca_certificate_ != nullptr) { + config.http_ca_certificate = this->http_ca_certificate_; + } +#endif this->decoder_ = std::make_unique(config); if (this->decoder_ == nullptr) { diff --git a/esphome/components/audio_http/audio_http_media_source.h b/esphome/components/audio_http/audio_http_media_source.h index a97025e53e8..e75dcf7a678 100644 --- a/esphome/components/audio_http/audio_http_media_source.h +++ b/esphome/components/audio_http/audio_http_media_source.h @@ -34,6 +34,11 @@ class AudioHTTPMediaSource final : public Component, void set_buffer_size(size_t buffer_size) { this->buffer_size_ = buffer_size; } void set_task_stack_in_psram(bool task_stack_in_psram) { this->decoder_task_stack_in_psram_ = task_stack_in_psram; } void set_persistent_ring_buffer(bool persistent) { this->persistent_ring_buffer_ = persistent; } +#ifdef USE_AUDIO_HTTP_CA_CERTIFICATE + /// PEM-encoded CA certificate(s) used as the sole trust anchor for HTTPS playback URLs, + /// replacing the built-in certificate bundle. + void set_http_ca_certificate(const char *ca_certificate) { this->http_ca_certificate_ = ca_certificate; } +#endif // MediaSource interface implementation bool play_uri(const std::string &uri) override; @@ -56,6 +61,9 @@ class AudioHTTPMediaSource final : public Component, std::atomic pause_{false}; bool decoder_task_stack_in_psram_{false}; bool persistent_ring_buffer_{false}; +#ifdef USE_AUDIO_HTTP_CA_CERTIFICATE + const char *http_ca_certificate_{nullptr}; +#endif }; } // namespace esphome::audio_http diff --git a/esphome/components/audio_http/media_source.py b/esphome/components/audio_http/media_source.py index 14543957e95..3c23517b92f 100644 --- a/esphome/components/audio_http/media_source.py +++ b/esphome/components/audio_http/media_source.py @@ -1,13 +1,22 @@ +import logging +from pathlib import Path + import esphome.codegen as cg from esphome.components import audio, media_source, psram import esphome.config_validation as cv from esphome.const import CONF_BUFFER_SIZE, CONF_ID, CONF_TASK_STACK_IN_PSRAM +import esphome.final_validate as fv from esphome.types import ConfigType +_LOGGER = logging.getLogger(__name__) + CODEOWNERS = ["@kahrendt"] AUTO_LOAD = ["audio"] CONF_PERSISTENT_RING_BUFFER = "persistent_ring_buffer" +CONF_CA_CERTIFICATE_PATH = "ca_certificate_path" +CONF_HTTP_REQUEST = "http_request" +CONF_VERIFY_SSL = "verify_ssl" audio_http_ns = cg.esphome_ns.namespace("audio_http") AudioHTTPMediaSource = audio_http_ns.class_( @@ -20,6 +29,38 @@ def _request_micro_decoder(config: ConfigType) -> ConfigType: return config +def _inherit_ca_certificate_path(config: ConfigType) -> ConfigType: + # Default to the CA certificate configured on the http_request component so + # HTTPS playback verifies against the same trust anchor without repeating + # the option on every source. Needed because audio_http sources are often + # declared by device packages and cannot be extended from the device config. + # The PEM replaces the built-in certificate bundle as the sole trust anchor, + # so make the inheritance visible in the log. + if CONF_CA_CERTIFICATE_PATH in config: + return config + fconf = fv.full_config.get() + if CONF_HTTP_REQUEST not in fconf: + return config + http_request_config = fconf[CONF_HTTP_REQUEST] + # Mirror http_request's own semantics: it only applies its CA when SSL + # verification is enabled, so neither does the inheritance. + if not http_request_config.get(CONF_VERIFY_SSL, True): + return config + if ca_cert_path := http_request_config.get(CONF_CA_CERTIFICATE_PATH): + _LOGGER.info( + "audio_http source '%s' is inheriting ca_certificate_path from the " + "http_request component; HTTPS playback will verify against that PEM " + "instead of the built-in certificate bundle", + config[CONF_ID].id, + ) + # Already validated by http_request's schema; the value is a resolved Path. + config[CONF_CA_CERTIFICATE_PATH] = ca_cert_path + return config + + +FINAL_VALIDATE_SCHEMA = _inherit_ca_certificate_path + + CONFIG_SCHEMA = cv.All( media_source.media_source_schema( AudioHTTPMediaSource, @@ -31,6 +72,7 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_TASK_STACK_IN_PSRAM): psram.validate_task_stack_in_psram, cv.Optional(CONF_PERSISTENT_RING_BUFFER, default=False): cv.boolean, + cv.Optional(CONF_CA_CERTIFICATE_PATH): cv.file_, } ) .extend(cv.COMPONENT_SCHEMA), @@ -47,5 +89,16 @@ async def to_code(config: ConfigType) -> None: if config.get(CONF_TASK_STACK_IN_PSRAM): cg.add(var.set_task_stack_in_psram(True)) psram.request_external_task_stack() + cg.add(var.set_buffer_size(config[CONF_BUFFER_SIZE])) cg.add(var.set_persistent_ring_buffer(config[CONF_PERSISTENT_RING_BUFFER])) + + # Embed the certificate content, like http_request does. Passed to + # micro_decoder's DecoderConfig::http_ca_certificate, which then uses it as + # the sole trust anchor for HTTPS playback URLs instead of the certificate + # bundle. + if ca_cert_path := config.get(CONF_CA_CERTIFICATE_PATH): + cg.add_define("USE_AUDIO_HTTP_CA_CERTIFICATE") + with Path(ca_cert_path).open(encoding="utf-8") as f: + ca_cert_content = f.read() + cg.add(var.set_http_ca_certificate(ca_cert_content)) diff --git a/esphome/components/axs15231/__init__.py b/esphome/components/axs15231/__init__.py index 3246dbed247..bed826cf8b8 100644 --- a/esphome/components/axs15231/__init__.py +++ b/esphome/components/axs15231/__init__.py @@ -2,5 +2,6 @@ import esphome.codegen as cg CODEOWNERS = ["@clydebarrow"] DEPENDENCIES = ["i2c"] +DOMAIN = "axs15231" axs15231_ns = cg.esphome_ns.namespace("axs15231") diff --git a/esphome/components/axs15231/touchscreen/axs15231_touchscreen.cpp b/esphome/components/axs15231/touchscreen/axs15231_touchscreen.cpp index 3869224b911..1d6354549c5 100644 --- a/esphome/components/axs15231/touchscreen/axs15231_touchscreen.cpp +++ b/esphome/components/axs15231/touchscreen/axs15231_touchscreen.cpp @@ -5,7 +5,7 @@ namespace esphome::axs15231 { -static const char *const TAG = "ax15231.touchscreen"; +ESPHOME_LOG_TAG(TAG, "ax15231.touchscreen"); constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a, 0x0, 0x0, 0x0, 0x8}; diff --git a/esphome/components/b_parasite/__init__.py b/esphome/components/b_parasite/__init__.py index e69de29bb2d..147319ba959 100644 --- a/esphome/components/b_parasite/__init__.py +++ b/esphome/components/b_parasite/__init__.py @@ -0,0 +1 @@ +DOMAIN = "b_parasite" diff --git a/esphome/components/b_parasite/b_parasite.cpp b/esphome/components/b_parasite/b_parasite.cpp index e0ae824b0d0..8bed59b3dbc 100644 --- a/esphome/components/b_parasite/b_parasite.cpp +++ b/esphome/components/b_parasite/b_parasite.cpp @@ -3,7 +3,7 @@ namespace esphome::b_parasite { -static const char *const TAG = "b_parasite"; +ESPHOME_LOG_TAG(TAG, "b_parasite"); void BParasite::dump_config() { ESP_LOGCONFIG(TAG, "b_parasite"); diff --git a/esphome/components/ballu/__init__.py b/esphome/components/ballu/__init__.py index e69de29bb2d..d0d8d1b3b21 100644 --- a/esphome/components/ballu/__init__.py +++ b/esphome/components/ballu/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ballu" diff --git a/esphome/components/ballu/ballu.cpp b/esphome/components/ballu/ballu.cpp index eebc970795a..1553f336186 100644 --- a/esphome/components/ballu/ballu.cpp +++ b/esphome/components/ballu/ballu.cpp @@ -3,7 +3,7 @@ namespace esphome::ballu { -static const char *const TAG = "ballu.climate"; +ESPHOME_LOG_TAG(TAG, "ballu.climate"); const uint16_t BALLU_HEADER_MARK = 9000; const uint16_t BALLU_HEADER_SPACE = 4500; diff --git a/esphome/components/bang_bang/__init__.py b/esphome/components/bang_bang/__init__.py index 71a87b6ae50..6536dc2c4f7 100644 --- a/esphome/components/bang_bang/__init__.py +++ b/esphome/components/bang_bang/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@OttoWinter"] +DOMAIN = "bang_bang" diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index a1104aa1b2a..1546df9f10b 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -3,7 +3,7 @@ namespace esphome::bang_bang { -static const char *const TAG = "bang_bang.climate"; +ESPHOME_LOG_TAG(TAG, "bang_bang.climate"); BangBangClimate::BangBangClimate() = default; diff --git a/esphome/components/bedjet/__init__.py b/esphome/components/bedjet/__init__.py index 1b967e665af..61dc73c73d5 100644 --- a/esphome/components/bedjet/__init__.py +++ b/esphome/components/bedjet/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@jhansche"] DEPENDENCIES = ["ble_client"] +DOMAIN = "bedjet" MULTI_CONF = True CONF_BEDJET_ID = "bedjet_id" diff --git a/esphome/components/beken_spi_led_strip/__init__.py b/esphome/components/beken_spi_led_strip/__init__.py index e69de29bb2d..940eabbeb64 100644 --- a/esphome/components/beken_spi_led_strip/__init__.py +++ b/esphome/components/beken_spi_led_strip/__init__.py @@ -0,0 +1 @@ +DOMAIN = "beken_spi_led_strip" diff --git a/esphome/components/beken_spi_led_strip/led_strip.cpp b/esphome/components/beken_spi_led_strip/led_strip.cpp index 0cf970b3cc6..dcaae6d8d48 100644 --- a/esphome/components/beken_spi_led_strip/led_strip.cpp +++ b/esphome/components/beken_spi_led_strip/led_strip.cpp @@ -35,7 +35,7 @@ static const uint32_t SPI_RX_FINISH_EN = 1 << 3; namespace esphome::beken_spi_led_strip { -static const char *const TAG = "beken_spi_led_strip"; +ESPHOME_LOG_TAG(TAG, "beken_spi_led_strip"); struct SpiData { SemaphoreHandle_t dma_tx_semaphore; diff --git a/esphome/components/bh1750/__init__.py b/esphome/components/bh1750/__init__.py index e69de29bb2d..67b3f5ba5ad 100644 --- a/esphome/components/bh1750/__init__.py +++ b/esphome/components/bh1750/__init__.py @@ -0,0 +1 @@ +DOMAIN = "bh1750" diff --git a/esphome/components/bh1750/bh1750.cpp b/esphome/components/bh1750/bh1750.cpp index ab952895a88..2d9709f4228 100644 --- a/esphome/components/bh1750/bh1750.cpp +++ b/esphome/components/bh1750/bh1750.cpp @@ -4,7 +4,7 @@ namespace esphome::bh1750 { -static const char *const TAG = "bh1750.sensor"; +ESPHOME_LOG_TAG(TAG, "bh1750.sensor"); static const uint8_t BH1750_COMMAND_POWER_ON = 0b00000001; static const uint8_t BH1750_COMMAND_MT_REG_HI = 0b01000000; // last 3 bits diff --git a/esphome/components/bh1900nux/__init__.py b/esphome/components/bh1900nux/__init__.py index e69de29bb2d..b5b6c5eac96 100644 --- a/esphome/components/bh1900nux/__init__.py +++ b/esphome/components/bh1900nux/__init__.py @@ -0,0 +1 @@ +DOMAIN = "bh1900nux" diff --git a/esphome/components/bh1900nux/bh1900nux.cpp b/esphome/components/bh1900nux/bh1900nux.cpp index 4f746a17d4b..6b7f1a35863 100644 --- a/esphome/components/bh1900nux/bh1900nux.cpp +++ b/esphome/components/bh1900nux/bh1900nux.cpp @@ -3,7 +3,7 @@ namespace esphome::bh1900nux { -static const char *const TAG = "bh1900nux.sensor"; +ESPHOME_LOG_TAG(TAG, "bh1900nux.sensor"); // I2C Registers static const uint8_t TEMPERATURE_REG = 0x00; diff --git a/esphome/components/binary/__init__.py b/esphome/components/binary/__init__.py index 3f6a45d28a2..6244d89b8d6 100644 --- a/esphome/components/binary/__init__.py +++ b/esphome/components/binary/__init__.py @@ -1,3 +1,5 @@ import esphome.codegen as cg +DOMAIN = "binary" + binary_ns = cg.esphome_ns.namespace("binary") diff --git a/esphome/components/binary/fan/binary_fan.cpp b/esphome/components/binary/fan/binary_fan.cpp index 118d87c09d0..4cc0b145a2b 100644 --- a/esphome/components/binary/fan/binary_fan.cpp +++ b/esphome/components/binary/fan/binary_fan.cpp @@ -3,7 +3,7 @@ namespace esphome::binary { -static const char *const TAG = "binary.fan"; +ESPHOME_LOG_TAG(TAG, "binary.fan"); void BinaryFan::setup() { auto restore = this->restore_state_(); diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index f5cde2c8e6f..60e20aa1935 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -106,6 +106,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_VIBRATION, DEVICE_CLASS_WINDOW, ] +DOMAIN = "binary_sensor" IS_PLATFORM_COMPONENT = True diff --git a/esphome/components/binary_sensor/automation.cpp b/esphome/components/binary_sensor/automation.cpp index 65c7dbbdb69..d4b4906c30d 100644 --- a/esphome/components/binary_sensor/automation.cpp +++ b/esphome/components/binary_sensor/automation.cpp @@ -8,7 +8,7 @@ namespace esphome::binary_sensor { #ifdef USE_BINARY_SENSOR_MULTI_CLICK_TRIGGER -static const char *const TAG = "binary_sensor.automation"; +ESPHOME_LOG_TAG(TAG, "binary_sensor.automation"); // MultiClickTrigger timeout IDs. // MultiClickTrigger is its own Component instance, so the scheduler scopes diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index 7596975a684..43915c4e17b 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -5,7 +5,7 @@ namespace esphome::binary_sensor { -static const char *const TAG = "binary_sensor"; +ESPHOME_LOG_TAG(TAG, "binary_sensor"); // Function implementation of LOG_BINARY_SENSOR macro to reduce code size void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj) { diff --git a/esphome/components/binary_sensor/filter.cpp b/esphome/components/binary_sensor/filter.cpp index 8b882212c8a..32b25f72116 100644 --- a/esphome/components/binary_sensor/filter.cpp +++ b/esphome/components/binary_sensor/filter.cpp @@ -8,7 +8,7 @@ namespace esphome::binary_sensor { -static const char *const TAG = "sensor.filter"; +ESPHOME_LOG_TAG(TAG, "sensor.filter"); void Filter::output(bool value) { if (this->next_ == nullptr) { diff --git a/esphome/components/binary_sensor_map/__init__.py b/esphome/components/binary_sensor_map/__init__.py index e69de29bb2d..3ac51b590ee 100644 --- a/esphome/components/binary_sensor_map/__init__.py +++ b/esphome/components/binary_sensor_map/__init__.py @@ -0,0 +1 @@ +DOMAIN = "binary_sensor_map" diff --git a/esphome/components/binary_sensor_map/binary_sensor_map.cpp b/esphome/components/binary_sensor_map/binary_sensor_map.cpp index 3185f156976..4f00a85c8dd 100644 --- a/esphome/components/binary_sensor_map/binary_sensor_map.cpp +++ b/esphome/components/binary_sensor_map/binary_sensor_map.cpp @@ -3,7 +3,7 @@ namespace esphome::binary_sensor_map { -static const char *const TAG = "binary_sensor_map"; +ESPHOME_LOG_TAG(TAG, "binary_sensor_map"); void BinarySensorMap::dump_config() { LOG_SENSOR(" ", "binary_sensor_map", this); } diff --git a/esphome/components/bk72xx/__init__.py b/esphome/components/bk72xx/__init__.py index e64237c95ab..1db8f5d54fc 100644 --- a/esphome/components/bk72xx/__init__.py +++ b/esphome/components/bk72xx/__init__.py @@ -35,6 +35,7 @@ from .boards import BK72XX_BOARD_PINS, BK72XX_BOARDS CODEOWNERS = ["@kuba2k2"] AUTO_LOAD = ["libretiny"] +DOMAIN = "bk72xx" IS_TARGET_PLATFORM = True COMPONENT_DATA = LibreTinyComponent( diff --git a/esphome/components/bk72xx_ble/__init__.py b/esphome/components/bk72xx_ble/__init__.py index 38cba56c623..44c66919bb0 100644 --- a/esphome/components/bk72xx_ble/__init__.py +++ b/esphome/components/bk72xx_ble/__init__.py @@ -38,6 +38,7 @@ from esphome.types import ConfigType DEPENDENCIES = ["bk72xx"] CODEOWNERS = ["@Bl00d-B0b"] +DOMAIN = "bk72xx_ble" _LOGGER = logging.getLogger(__name__) diff --git a/esphome/components/bk72xx_ble/bdk_scan.cpp b/esphome/components/bk72xx_ble/bdk_scan.cpp index 3192fc79d70..05b94e17f9e 100644 --- a/esphome/components/bk72xx_ble/bdk_scan.cpp +++ b/esphome/components/bk72xx_ble/bdk_scan.cpp @@ -29,7 +29,7 @@ extern "C" { namespace esphome::bk72xx_ble { -static const char *const TAG = "bk72xx_ble"; +ESPHOME_LOG_TAG(TAG, "bk72xx_ble"); // Pin the SDK surface this file depends on: a beken-bdk bump that moves these // must fail the build, not corrupt the kernel message. diff --git a/esphome/components/bk72xx_ble/bk72xx_ble.cpp b/esphome/components/bk72xx_ble/bk72xx_ble.cpp index 7a4efff4550..49167c3000e 100644 --- a/esphome/components/bk72xx_ble/bk72xx_ble.cpp +++ b/esphome/components/bk72xx_ble/bk72xx_ble.cpp @@ -81,7 +81,7 @@ void ble_entry(void); namespace esphome::bk72xx_ble { -static const char *const TAG = "bk72xx_ble"; +ESPHOME_LOG_TAG(TAG, "bk72xx_ble"); static constexpr uint32_t RECONCILE_RETRY_MS = 10; // pump floor for fast loops static constexpr uint32_t RECONCILE_REJECTED_RETRY_MS = 500; // retry gate after a rejected release diff --git a/esphome/components/bk72xx_ble_tracker/__init__.py b/esphome/components/bk72xx_ble_tracker/__init__.py index 55013d40876..abef3c7752b 100644 --- a/esphome/components/bk72xx_ble_tracker/__init__.py +++ b/esphome/components/bk72xx_ble_tracker/__init__.py @@ -44,6 +44,7 @@ CONF_BK72XX_BLE_ID = "bk72xx_ble_id" DEPENDENCIES = ["bk72xx"] AUTO_LOAD = ["ble_device_base", "bk72xx_ble"] CODEOWNERS = ["@Bl00d-B0b"] +DOMAIN = "bk72xx_ble_tracker" ble_device_base.register_hub_provider("bk72xx_ble_tracker") diff --git a/esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.cpp b/esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.cpp index 0939e1259f6..7e7f04135e6 100644 --- a/esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.cpp +++ b/esphome/components/bk72xx_ble_tracker/bk72xx_ble_tracker.cpp @@ -16,7 +16,7 @@ namespace esphome::bk72xx_ble_tracker { -static const char *const TAG = "bk72xx_ble_tracker"; +ESPHOME_LOG_TAG(TAG, "bk72xx_ble_tracker"); // Minimum interval between scan (re)start attempts, so a failing controller start // cannot be retried every main-loop iteration (single-core CPU starvation). The diff --git a/esphome/components/bl0906/__init__.py b/esphome/components/bl0906/__init__.py index b0777926044..b840c0ee99e 100644 --- a/esphome/components/bl0906/__init__.py +++ b/esphome/components/bl0906/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@athom-tech", "@tarontop", "@jesserockz"] +DOMAIN = "bl0906" diff --git a/esphome/components/bl0906/bl0906.cpp b/esphome/components/bl0906/bl0906.cpp index 9a27cffd043..38edb6ed919 100644 --- a/esphome/components/bl0906/bl0906.cpp +++ b/esphome/components/bl0906/bl0906.cpp @@ -5,7 +5,7 @@ namespace esphome::bl0906 { -static const char *const TAG = "bl0906"; +ESPHOME_LOG_TAG(TAG, "bl0906"); constexpr uint32_t to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; } diff --git a/esphome/components/bl0939/__init__.py b/esphome/components/bl0939/__init__.py index 9bd4598dd26..a2c2dd4c7b3 100644 --- a/esphome/components/bl0939/__init__.py +++ b/esphome/components/bl0939/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@ziceva"] +DOMAIN = "bl0939" diff --git a/esphome/components/bl0939/bl0939.cpp b/esphome/components/bl0939/bl0939.cpp index 9ae3360e3ac..b0b9bb1c176 100644 --- a/esphome/components/bl0939/bl0939.cpp +++ b/esphome/components/bl0939/bl0939.cpp @@ -4,7 +4,7 @@ namespace esphome::bl0939 { -static const char *const TAG = "bl0939"; +ESPHOME_LOG_TAG(TAG, "bl0939"); // https://www.belling.com.cn/media/file_object/bel_product/BL0939/datasheet/BL0939_V1.2_cn.pdf // (unfortunately chinese, but the protocol can be understood with some translation tool) diff --git a/esphome/components/bl0940/__init__.py b/esphome/components/bl0940/__init__.py index 066c2818b6d..953824be51f 100644 --- a/esphome/components/bl0940/__init__.py +++ b/esphome/components/bl0940/__init__.py @@ -1,6 +1,7 @@ import esphome.codegen as cg CODEOWNERS = ["@tobias-", "@dan-s-github"] +DOMAIN = "bl0940" CONF_BL0940_ID = "bl0940_id" bl0940_ns = cg.esphome_ns.namespace("bl0940") diff --git a/esphome/components/bl0940/bl0940.cpp b/esphome/components/bl0940/bl0940.cpp index 642368d93fa..a8b470de7ec 100644 --- a/esphome/components/bl0940/bl0940.cpp +++ b/esphome/components/bl0940/bl0940.cpp @@ -4,7 +4,7 @@ namespace esphome::bl0940 { -static const char *const TAG = "bl0940"; +ESPHOME_LOG_TAG(TAG, "bl0940"); static const uint8_t BL0940_FULL_PACKET = 0xAA; static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to en doc but 0x55 in cn doc diff --git a/esphome/components/bl0940/button/calibration_reset_button.cpp b/esphome/components/bl0940/button/calibration_reset_button.cpp index a0b41b9a5bb..5598818f546 100644 --- a/esphome/components/bl0940/button/calibration_reset_button.cpp +++ b/esphome/components/bl0940/button/calibration_reset_button.cpp @@ -6,7 +6,7 @@ namespace esphome::bl0940 { -static const char *const TAG = "bl0940.button.calibration_reset"; +ESPHOME_LOG_TAG(TAG, "bl0940.button.calibration_reset"); void CalibrationResetButton::dump_config() { LOG_BUTTON("", "Calibration Reset Button", this); } diff --git a/esphome/components/bl0940/number/calibration_number.cpp b/esphome/components/bl0940/number/calibration_number.cpp index 6f054443a28..d382a12e699 100644 --- a/esphome/components/bl0940/number/calibration_number.cpp +++ b/esphome/components/bl0940/number/calibration_number.cpp @@ -3,7 +3,7 @@ namespace esphome::bl0940 { -static const char *const TAG = "bl0940.number"; +ESPHOME_LOG_TAG(TAG, "bl0940.number"); void CalibrationNumber::setup() { float value = 0.0f; diff --git a/esphome/components/bl0942/__init__.py b/esphome/components/bl0942/__init__.py index 38b68d84b5f..89141daae39 100644 --- a/esphome/components/bl0942/__init__.py +++ b/esphome/components/bl0942/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@dbuezas", "@dwmw2"] +DOMAIN = "bl0942" diff --git a/esphome/components/bl0942/bl0942.cpp b/esphome/components/bl0942/bl0942.cpp index e952df21bed..7d0eff8c9da 100644 --- a/esphome/components/bl0942/bl0942.cpp +++ b/esphome/components/bl0942/bl0942.cpp @@ -6,7 +6,7 @@ namespace esphome::bl0942 { -static const char *const TAG = "bl0942"; +ESPHOME_LOG_TAG(TAG, "bl0942"); static const uint8_t BL0942_READ_COMMAND = 0x58; static const uint8_t BL0942_FULL_PACKET = 0xAA; diff --git a/esphome/components/ble_client/__init__.py b/esphome/components/ble_client/__init__.py index 810f4b66e3d..b783ef1c153 100644 --- a/esphome/components/ble_client/__init__.py +++ b/esphome/components/ble_client/__init__.py @@ -22,6 +22,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["esp32_ble_client"] CODEOWNERS = ["@buxtronix", "@clydebarrow"] DEPENDENCIES = ["esp32_ble_tracker"] +DOMAIN = "ble_client" CONF_DESCRIPTOR_UUID = "descriptor_uuid" CONF_ON_NOTIFY = "on_notify" diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 25001c8f74b..eb24cdeeb43 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -9,7 +9,7 @@ namespace esphome::ble_client { -static const char *const TAG = "ble_client"; +ESPHOME_LOG_TAG(TAG, "ble_client"); void BLEClient::setup() { BLEClientBase::setup(); diff --git a/esphome/components/ble_client/output/ble_binary_output.cpp b/esphome/components/ble_client/output/ble_binary_output.cpp index 5d53c597086..479bfefd597 100644 --- a/esphome/components/ble_client/output/ble_binary_output.cpp +++ b/esphome/components/ble_client/output/ble_binary_output.cpp @@ -5,7 +5,7 @@ #ifdef USE_ESP32 namespace esphome::ble_client { -static const char *const TAG = "ble_binary_output"; +ESPHOME_LOG_TAG(TAG, "ble_binary_output"); void BLEBinaryOutput::dump_config() { ESP_LOGCONFIG(TAG, "BLE Binary Output:"); diff --git a/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp b/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp index 715298e5928..69e7450dc20 100644 --- a/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_rssi_sensor.cpp @@ -8,7 +8,7 @@ namespace esphome::ble_client { -static const char *const TAG = "ble_rssi_sensor"; +ESPHOME_LOG_TAG(TAG, "ble_rssi_sensor"); void BLEClientRSSISensor::loop() { // Parent BLEClientNode has a loop() method, but this component uses diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index 5dbb7e42ed2..1ddd50c3079 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -8,7 +8,7 @@ namespace esphome::ble_client { -static const char *const TAG = "ble_sensor"; +ESPHOME_LOG_TAG(TAG, "ble_sensor"); void BLESensor::loop() { // Parent BLEClientNode has a loop() method, but this component uses diff --git a/esphome/components/ble_client/switch/ble_switch.cpp b/esphome/components/ble_client/switch/ble_switch.cpp index 5baca2adcf1..1edd42760e0 100644 --- a/esphome/components/ble_client/switch/ble_switch.cpp +++ b/esphome/components/ble_client/switch/ble_switch.cpp @@ -6,7 +6,7 @@ namespace esphome::ble_client { -static const char *const TAG = "ble_switch"; +ESPHOME_LOG_TAG(TAG, "ble_switch"); void BLEClientSwitch::write_state(bool state) { this->parent_->set_enabled(state); diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp index ed2b0a63a0e..f6df7b43364 100644 --- a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -9,7 +9,7 @@ namespace esphome::ble_client { -static const char *const TAG = "ble_text_sensor"; +ESPHOME_LOG_TAG(TAG, "ble_text_sensor"); void BLETextSensor::loop() { // Parent BLEClientNode has a loop() method, but this component uses diff --git a/esphome/components/ble_device_base/__init__.py b/esphome/components/ble_device_base/__init__.py index 43ec7367277..e8283457c6c 100644 --- a/esphome/components/ble_device_base/__init__.py +++ b/esphome/components/ble_device_base/__init__.py @@ -41,6 +41,7 @@ from esphome.core import CORE, ID, KEY_CORE, TimePeriod from esphome.types import ConfigType CODEOWNERS = ["@Bl00d-B0b"] +DOMAIN = "ble_device_base" CONF_BLE_HUB_ID = "ble_hub_id" diff --git a/esphome/components/ble_device_base/automation.h b/esphome/components/ble_device_base/automation.h index ba3128c0eed..a50813c5856 100644 --- a/esphome/components/ble_device_base/automation.h +++ b/esphome/components/ble_device_base/automation.h @@ -11,29 +11,34 @@ #include "esphome/core/automation.h" #include "esphome/core/helpers.h" -#include -#include - namespace esphome::ble_device_base { +/// True if `address` is in `table`, a list of MACs ended by 0. +inline bool mac_in_table(const uint64_t *table, uint64_t address) { + for (; *table != 0; table++) { + if (*table == address) + return true; + } + return false; +} + // on_ble_advertise: fires on every BLE advertisement, optionally filtered to one or more MACs. class ESPBTAdvertiseTrigger final : public Trigger, public ESPBTDeviceListener { public: template explicit ESPBTAdvertiseTrigger(Hub *parent) { parent->register_listener(this); } - void set_addresses(std::initializer_list addresses) { this->addresses_ = addresses; } + /// Table of MACs ended by 0; must outlive the trigger. + void set_addresses(const uint64_t *addresses) { this->addresses_ = addresses; } bool parse_device(const ESPBTDevice &device) override { - if (!this->addresses_.empty() && std::find(this->addresses_.begin(), this->addresses_.end(), - device.address_uint64()) == this->addresses_.end()) { + if (this->addresses_ != nullptr && !mac_in_table(this->addresses_, device.address_uint64())) return false; - } this->trigger(device); return true; } protected: - FixedVector addresses_; + const uint64_t *addresses_{nullptr}; }; // on_ble_service_data_advertise: fires when an advertisement contains service diff --git a/esphome/components/ble_device_base/automation.py b/esphome/components/ble_device_base/automation.py index 6acc4edb924..7cc87add5a0 100644 --- a/esphome/components/ble_device_base/automation.py +++ b/esphome/components/ble_device_base/automation.py @@ -69,13 +69,24 @@ def uuid_trigger_schema( ) +def _filter_mac(value: Any) -> Any: + mac = cv.mac_address(value) + if not any(mac.parts): + # 0 ends the flash MAC table, and it is never a real device address. + raise cv.Invalid("00:00:00:00:00:00 cannot be used as a MAC address filter") + return mac + + +MAC_FILTER_LIST = cv.ensure_list(_filter_mac) + + def advertise_trigger_schema(trigger_class: MockObjClass) -> Callable[[Any], Any]: """on_ble_advertise schema: multi-mac list filter, unlike the single-mac uuid_trigger_schema() — pairs with advertise_trigger_to_code().""" return automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(trigger_class), - cv.Optional(CONF_MAC_ADDRESS): cv.ensure_list(cv.mac_address), + cv.Optional(CONF_MAC_ADDRESS): MAC_FILTER_LIST, } ) @@ -94,11 +105,18 @@ def scan_end_trigger_schema(trigger_class: MockObjClass) -> Callable[[Any], Any] _count_listener = cg.slot_counter(LISTENER_COUNT_DEFINE) +def mac_filter_table(macs: list) -> cg.MockObj: + """Shared flash table of MACs ended by 0 (never a valid address), so triggers store a pointer.""" + return cg.shared_progmem_array( + "ble_mac_filter", cg.uint64, [*(mac.as_hex for mac in macs), 0] + ) + + async def advertise_trigger_to_code(conf: ConfigType, var: cg.MockObj) -> None: """Build an on_ble_advertise trigger (optional multi-mac filter).""" trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - if (macs := conf.get(CONF_MAC_ADDRESS)) is not None: - cg.add(trigger.set_addresses([it.as_hex for it in macs])) + if macs := conf.get(CONF_MAC_ADDRESS): + cg.add(trigger.set_addresses(mac_filter_table(macs))) await automation.build_automation(trigger, [(ESPBTDeviceConstRef, "x")], conf) _count_listener() diff --git a/esphome/components/ble_device_base/ble_device.cpp b/esphome/components/ble_device_base/ble_device.cpp index 23ca6b1dbd8..f7fa488aac7 100644 --- a/esphome/components/ble_device_base/ble_device.cpp +++ b/esphome/components/ble_device_base/ble_device.cpp @@ -16,7 +16,7 @@ namespace esphome::ble_device_base { -static const char *const TAG = "ble_device_base"; +ESPHOME_LOG_TAG(TAG, "ble_device_base"); // Longest advertisement payload worth hex-dumping at VERY_VERBOSE // (legacy advertising: 31-byte adv + 31-byte scan response). diff --git a/esphome/components/ble_nus/__init__.py b/esphome/components/ble_nus/__init__.py index 40983c4dbb0..54f111e4af9 100644 --- a/esphome/components/ble_nus/__init__.py +++ b/esphome/components/ble_nus/__init__.py @@ -21,6 +21,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["zephyr_ble_server", "uart"] CODEOWNERS = ["@tomaszduda23"] +DOMAIN = "ble_nus" ble_nus_ns = cg.esphome_ns.namespace("ble_nus") BLENUS = ble_nus_ns.class_("BLENUS", cg.Component, UARTComponent) diff --git a/esphome/components/ble_nus/ble_nus.cpp b/esphome/components/ble_nus/ble_nus.cpp index b566122f8ad..64a36c468f3 100644 --- a/esphome/components/ble_nus/ble_nus.cpp +++ b/esphome/components/ble_nus/ble_nus.cpp @@ -19,7 +19,7 @@ RING_BUF_DECLARE(global_ble_rx_ring_buf, ESPHOME_BLE_NUS_RX_RING_BUFFER_SIZE); #endif // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) -static const char *const TAG = "ble_nus"; +ESPHOME_LOG_TAG(TAG, "ble_nus"); void BLENUS::write_array(const uint8_t *data, size_t len) { if (atomic_get(&this->tx_status_) == TX_DISABLED) { diff --git a/esphome/components/ble_presence/__init__.py b/esphome/components/ble_presence/__init__.py index e69de29bb2d..0be32a6e956 100644 --- a/esphome/components/ble_presence/__init__.py +++ b/esphome/components/ble_presence/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ble_presence" diff --git a/esphome/components/ble_presence/ble_presence_device.cpp b/esphome/components/ble_presence/ble_presence_device.cpp index bc169623ce0..91947ccd26a 100644 --- a/esphome/components/ble_presence/ble_presence_device.cpp +++ b/esphome/components/ble_presence/ble_presence_device.cpp @@ -3,7 +3,7 @@ namespace esphome::ble_presence { -static const char *const TAG = "ble_presence"; +ESPHOME_LOG_TAG(TAG, "ble_presence"); void BLEPresenceDevice::dump_config() { LOG_BINARY_SENSOR("", "BLE Presence", this); } diff --git a/esphome/components/ble_rssi/__init__.py b/esphome/components/ble_rssi/__init__.py index e69de29bb2d..2d31b9aec25 100644 --- a/esphome/components/ble_rssi/__init__.py +++ b/esphome/components/ble_rssi/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ble_rssi" diff --git a/esphome/components/ble_rssi/ble_rssi_sensor.cpp b/esphome/components/ble_rssi/ble_rssi_sensor.cpp index 7c7c7b21487..eb049313a8d 100644 --- a/esphome/components/ble_rssi/ble_rssi_sensor.cpp +++ b/esphome/components/ble_rssi/ble_rssi_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::ble_rssi { -static const char *const TAG = "ble_rssi"; +ESPHOME_LOG_TAG(TAG, "ble_rssi"); void BLERSSISensor::dump_config() { LOG_SENSOR("", "BLE RSSI Sensor", this); } diff --git a/esphome/components/ble_scanner/__init__.py b/esphome/components/ble_scanner/__init__.py index e69de29bb2d..e687d58e65a 100644 --- a/esphome/components/ble_scanner/__init__.py +++ b/esphome/components/ble_scanner/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ble_scanner" diff --git a/esphome/components/ble_scanner/ble_scanner.cpp b/esphome/components/ble_scanner/ble_scanner.cpp index 3d7a3017931..42a691b1d92 100644 --- a/esphome/components/ble_scanner/ble_scanner.cpp +++ b/esphome/components/ble_scanner/ble_scanner.cpp @@ -3,7 +3,7 @@ namespace esphome::ble_scanner { -static const char *const TAG = "ble_scanner"; +ESPHOME_LOG_TAG(TAG, "ble_scanner"); void BLEScanner::dump_config() { LOG_TEXT_SENSOR("", "BLE Scanner", this); } diff --git a/esphome/components/bluetooth_connection/bluetooth_connection.cpp b/esphome/components/bluetooth_connection/bluetooth_connection.cpp index a001729083c..a99a39bc9e1 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection.cpp @@ -12,7 +12,7 @@ namespace esphome::bluetooth_connection { -static const char *const TAG = "bluetooth_connection"; +ESPHOME_LOG_TAG(TAG, "bluetooth_connection"); BatchClose close_service_batch(api::BluetoothGATTGetServicesResponse &resp, size_t ¤t_size, int16_t &send_service, uint8_t connection_index, const char *address_str) { diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp index d3af02780fe..7b66c671835 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection_bluedroid.cpp @@ -27,7 +27,7 @@ namespace esphome::bluetooth_connection { -static const char *const TAG = "bluetooth_connection"; +ESPHOME_LOG_TAG(TAG, "bluetooth_connection"); using ble_device_base::FAST_CONN_TIMEOUT; using ble_device_base::FAST_MAX_CONN_INTERVAL; diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_hub.cpp b/esphome/components/bluetooth_connection/bluetooth_connection_hub.cpp index ddab4812cce..5e358175bf1 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_hub.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection_hub.cpp @@ -31,7 +31,7 @@ namespace esphome::bluetooth_connection { -static const char *const TAG = "bluetooth_connection"; +ESPHOME_LOG_TAG(TAG, "bluetooth_connection"); void BluetoothConnection::set_address(uint64_t address) { // Keep the proxy's pre-allocated connections-free message in step diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_rp2.cpp b/esphome/components/bluetooth_connection/bluetooth_connection_rp2.cpp index eec2c8c3186..c6e2703c48b 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_rp2.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection_rp2.cpp @@ -15,7 +15,7 @@ namespace esphome::bluetooth_connection { -static const char *const TAG = "bluetooth_connection"; +ESPHOME_LOG_TAG(TAG, "bluetooth_connection"); using ble_device_base::ESPBTUUID; using ble_device_base::GATT_ERR_NOT_CONNECTED; diff --git a/esphome/components/bluetooth_proxy/__init__.py b/esphome/components/bluetooth_proxy/__init__.py index c87ad7f5957..6332ea6dc12 100644 --- a/esphome/components/bluetooth_proxy/__init__.py +++ b/esphome/components/bluetooth_proxy/__init__.py @@ -58,6 +58,7 @@ _HUB_PLATFORMS = (PLATFORM_BK72XX, PLATFORM_LN882X, PLATFORM_RP2) DEPENDENCIES = ["api"] CODEOWNERS = ["@jesserockz", "@bdraco"] +DOMAIN = "bluetooth_proxy" _LOGGER = logging.getLogger(__name__) diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index cb37057cd4b..e4b94870747 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -14,7 +14,7 @@ namespace esphome::bluetooth_proxy { -static const char *const TAG = "bluetooth_proxy"; +ESPHOME_LOG_TAG(TAG, "bluetooth_proxy"); // BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE is defined during code generation // It sets the batch size for BLE advertisements to maximize WiFi efficiency diff --git a/esphome/components/bm8563/__init__.py b/esphome/components/bm8563/__init__.py index 20254a8b006..c3d1fd8cf61 100644 --- a/esphome/components/bm8563/__init__.py +++ b/esphome/components/bm8563/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@abmantis"] +DOMAIN = "bm8563" diff --git a/esphome/components/bm8563/bm8563.cpp b/esphome/components/bm8563/bm8563.cpp index d911301c9d3..df1cf96d698 100644 --- a/esphome/components/bm8563/bm8563.cpp +++ b/esphome/components/bm8563/bm8563.cpp @@ -6,7 +6,7 @@ namespace esphome::bm8563 { -static const char *const TAG = "bm8563"; +ESPHOME_LOG_TAG(TAG, "bm8563"); static constexpr uint8_t CONTROL_STATUS_1_REG = 0x00; static constexpr uint8_t CONTROL_STATUS_2_REG = 0x01; diff --git a/esphome/components/bme280_base/__init__.py b/esphome/components/bme280_base/__init__.py index 4b079def64d..8d54cbed2a0 100644 --- a/esphome/components/bme280_base/__init__.py +++ b/esphome/components/bme280_base/__init__.py @@ -20,6 +20,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] +DOMAIN = "bme280_base" bme280_ns = cg.esphome_ns.namespace("bme280_base") BME280Oversampling = bme280_ns.enum("BME280Oversampling") diff --git a/esphome/components/bme280_base/bme280_base.cpp b/esphome/components/bme280_base/bme280_base.cpp index 11c796352a3..96bb56f345e 100644 --- a/esphome/components/bme280_base/bme280_base.cpp +++ b/esphome/components/bme280_base/bme280_base.cpp @@ -11,7 +11,9 @@ namespace esphome::bme280_base { -static const char *const TAG = "bme280.sensor"; +ESPHOME_LOG_TAG(TAG, "bme280.sensor"); + +static constexpr uint32_t DATA_TIMEOUT_ID = 0; static const uint8_t BME280_REGISTER_DIG_T1 = 0x88; static const uint8_t BME280_REGISTER_DIG_T2 = 0x8A; @@ -221,7 +223,7 @@ void BME280Component::update() { meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f; meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f; - this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { + this->set_timeout(DATA_TIMEOUT_ID, uint32_t(ceilf(meas_time)), [this]() { uint8_t data[8]; if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) { ESP_LOGW(TAG, "Error reading registers"); diff --git a/esphome/components/bme280_i2c/__init__.py b/esphome/components/bme280_i2c/__init__.py index e69de29bb2d..d57004f5fbd 100644 --- a/esphome/components/bme280_i2c/__init__.py +++ b/esphome/components/bme280_i2c/__init__.py @@ -0,0 +1 @@ +DOMAIN = "bme280_i2c" diff --git a/esphome/components/bme280_spi/__init__.py b/esphome/components/bme280_spi/__init__.py index e69de29bb2d..5ca2ba45953 100644 --- a/esphome/components/bme280_spi/__init__.py +++ b/esphome/components/bme280_spi/__init__.py @@ -0,0 +1 @@ +DOMAIN = "bme280_spi" diff --git a/esphome/components/bme680/__init__.py b/esphome/components/bme680/__init__.py index e69de29bb2d..1ebff77202e 100644 --- a/esphome/components/bme680/__init__.py +++ b/esphome/components/bme680/__init__.py @@ -0,0 +1 @@ +DOMAIN = "bme680" diff --git a/esphome/components/bme680/bme680.cpp b/esphome/components/bme680/bme680.cpp index bac8ed8a5a8..6bacb71ff20 100644 --- a/esphome/components/bme680/bme680.cpp +++ b/esphome/components/bme680/bme680.cpp @@ -4,7 +4,9 @@ namespace esphome::bme680 { -static const char *const TAG = "bme680.sensor"; +ESPHOME_LOG_TAG(TAG, "bme680.sensor"); + +static constexpr uint32_t DATA_TIMEOUT_ID = 0; static const uint8_t BME680_REGISTER_COEFF1 = 0x89; static const uint8_t BME680_REGISTER_COEFF2 = 0xE1; @@ -242,7 +244,7 @@ void BME680Component::update() { return; } - this->set_timeout("data", this->calc_meas_duration_(), [this]() { this->read_data_(); }); + this->set_timeout(DATA_TIMEOUT_ID, this->calc_meas_duration_(), [this]() { this->read_data_(); }); } uint8_t BME680Component::calc_heater_resistance_(uint16_t temperature) { diff --git a/esphome/components/bme680_bsec/__init__.py b/esphome/components/bme680_bsec/__init__.py index 35df2a7ea3f..190994b2f8a 100644 --- a/esphome/components/bme680_bsec/__init__.py +++ b/esphome/components/bme680_bsec/__init__.py @@ -9,6 +9,7 @@ CODEOWNERS = ["@trvrnrth"] DEPENDENCIES = ["i2c"] AUTO_LOAD = ["sensor", "text_sensor"] CONFLICTS_WITH = ["bme68x_bsec2"] +DOMAIN = "bme680_bsec" MULTI_CONF = True CONF_BME680_BSEC_ID = "bme680_bsec_id" diff --git a/esphome/components/bme680_bsec/bme680_bsec.cpp b/esphome/components/bme680_bsec/bme680_bsec.cpp index 8e16a28e336..3f0082e1af7 100644 --- a/esphome/components/bme680_bsec/bme680_bsec.cpp +++ b/esphome/components/bme680_bsec/bme680_bsec.cpp @@ -5,7 +5,9 @@ namespace esphome::bme680_bsec { #ifdef USE_BSEC -static const char *const TAG = "bme680_bsec.sensor"; +ESPHOME_LOG_TAG(TAG, "bme680_bsec.sensor"); + +static constexpr uint32_t READ_TIMEOUT_ID = 0; static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"}; @@ -259,7 +261,7 @@ void BME680BSECComponent::run_() { this->snapshot_state_(); ESP_LOGV(TAG, "Queueing read in %ums", meas_dur); - this->set_timeout("read", meas_dur, [this]() { this->read_(); }); + this->set_timeout(READ_TIMEOUT_ID, meas_dur, [this]() { this->read_(); }); } else { ESP_LOGV(TAG, "Measurement not required"); this->read_(); diff --git a/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp b/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp index d4ac57d7500..f5a674d6b92 100644 --- a/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp +++ b/esphome/components/bme68x_bsec2/bme68x_bsec2.cpp @@ -13,7 +13,9 @@ namespace esphome::bme68x_bsec2 { #define BME68X_BSEC2_SAMPLE_RATE_LOG(r) (r == SAMPLE_RATE_DEFAULT ? "Default" : (r == SAMPLE_RATE_ULP ? "ULP" : "LP")) #define BME68X_BSEC2_VOLTAGE_LOG(v) (v == VOLTAGE_3_3V ? "3.3V" : "1.8V") -static const char *const TAG = "bme68x_bsec2.sensor"; +ESPHOME_LOG_TAG(TAG, "bme68x_bsec2.sensor"); + +static constexpr uint32_t READ_TIMEOUT_ID = 0; static constexpr const char *const IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"}; @@ -285,11 +287,11 @@ void BME68xBSEC2Component::run_() { uint32_t meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_); ESP_LOGV(TAG, "Queueing read in %" PRIu32 "us", meas_dur); this->trigger_time_ns_ = curr_time_ns; - this->set_timeout("read", meas_dur / 1000, [this]() { this->read_(this->trigger_time_ns_); }); + this->set_timeout(READ_TIMEOUT_ID, meas_dur / 1000, [this]() { this->read_(this->trigger_time_ns_); }); } else { ESP_LOGV(TAG, "Measurement not required, queueing immediate read"); this->trigger_time_ns_ = curr_time_ns; - this->set_timeout("read", 0, [this]() { this->read_(this->trigger_time_ns_); }); + this->set_timeout(READ_TIMEOUT_ID, 0, [this]() { this->read_(this->trigger_time_ns_); }); } } diff --git a/esphome/components/bme68x_bsec2_i2c/__init__.py b/esphome/components/bme68x_bsec2_i2c/__init__.py index 1da3bdbd6c6..8c50f6e165a 100644 --- a/esphome/components/bme68x_bsec2_i2c/__init__.py +++ b/esphome/components/bme68x_bsec2_i2c/__init__.py @@ -9,6 +9,7 @@ import esphome.config_validation as cv from esphome.types import ConfigType CODEOWNERS = ["@neffs", "@kbx81"] +DOMAIN = "bme68x_bsec2_i2c" AUTO_LOAD = ["bme68x_bsec2"] DEPENDENCIES = ["i2c"] diff --git a/esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp b/esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp index 2a2c5e2bcc8..8672e4f6ffc 100644 --- a/esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp +++ b/esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp @@ -11,7 +11,7 @@ namespace esphome::bme68x_bsec2_i2c { -static const char *const TAG = "bme68x_bsec2_i2c.sensor"; +ESPHOME_LOG_TAG(TAG, "bme68x_bsec2_i2c.sensor"); void BME68xBSEC2I2CComponent::setup() { // must set up our bme68x_dev instance before calling setup() diff --git a/esphome/components/bmi160/__init__.py b/esphome/components/bmi160/__init__.py index 49b6d0252a3..75386e98576 100644 --- a/esphome/components/bmi160/__init__.py +++ b/esphome/components/bmi160/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@flaviut"] +DOMAIN = "bmi160" diff --git a/esphome/components/bmi160/bmi160.cpp b/esphome/components/bmi160/bmi160.cpp index 442ee183bf9..a90d490ae18 100644 --- a/esphome/components/bmi160/bmi160.cpp +++ b/esphome/components/bmi160/bmi160.cpp @@ -4,7 +4,7 @@ namespace esphome::bmi160 { -static const char *const TAG = "bmi160"; +ESPHOME_LOG_TAG(TAG, "bmi160"); static constexpr uint32_t GYRO_WAKEUP_TIMEOUT_MS = 100; const uint8_t BMI160_REGISTER_CHIPID = 0x00; diff --git a/esphome/components/bmi270/__init__.py b/esphome/components/bmi270/__init__.py index 0e67e41a0e0..499e88b5b12 100644 --- a/esphome/components/bmi270/__init__.py +++ b/esphome/components/bmi270/__init__.py @@ -3,6 +3,7 @@ from esphome.components import i2c from esphome.components.motion import MotionComponent CODEOWNERS = ["@clydebarrow"] +DOMAIN = "bmi270" CONF_BMI270_ID = "bmi270_id" # C++ namespace / class diff --git a/esphome/components/bmi270/bmi270.cpp b/esphome/components/bmi270/bmi270.cpp index acb93158d4b..a1709c1fa56 100644 --- a/esphome/components/bmi270/bmi270.cpp +++ b/esphome/components/bmi270/bmi270.cpp @@ -5,7 +5,7 @@ namespace esphome::bmi270 { -static const char *const TAG = "bmi270"; +ESPHOME_LOG_TAG(TAG, "bmi270"); #if defined(USE_ARDUINO) && !defined(USE_ESP32) static const size_t MAX_I2C_BUFFER_SIZE = 32; diff --git a/esphome/components/bmp085/__init__.py b/esphome/components/bmp085/__init__.py index e69de29bb2d..51355f5bc0b 100644 --- a/esphome/components/bmp085/__init__.py +++ b/esphome/components/bmp085/__init__.py @@ -0,0 +1 @@ +DOMAIN = "bmp085" diff --git a/esphome/components/bmp085/bmp085.cpp b/esphome/components/bmp085/bmp085.cpp index f42875b2089..b989ed877b6 100644 --- a/esphome/components/bmp085/bmp085.cpp +++ b/esphome/components/bmp085/bmp085.cpp @@ -3,7 +3,10 @@ namespace esphome::bmp085 { -static const char *const TAG = "bmp085.sensor"; +ESPHOME_LOG_TAG(TAG, "bmp085.sensor"); + +static constexpr uint32_t TEMPERATURE_TIMEOUT_ID = 0; +static constexpr uint32_t PRESSURE_TIMEOUT_ID = 1; static const uint8_t BMP085_ADDRESS = 0x77; static const uint8_t BMP085_REGISTER_AC1_H = 0xAA; @@ -16,7 +19,7 @@ void BMP085Component::update() { if (!this->set_mode_(BMP085_CONTROL_MODE_TEMPERATURE)) return; - this->set_timeout("temperature", 5, [this]() { this->read_temperature_(); }); + this->set_timeout(TEMPERATURE_TIMEOUT_ID, 5, [this]() { this->read_temperature_(); }); } void BMP085Component::setup() { uint8_t data[22]; @@ -84,7 +87,7 @@ void BMP085Component::read_temperature_() { return; } - this->set_timeout("pressure", 26, [this]() { this->read_pressure_(); }); + this->set_timeout(PRESSURE_TIMEOUT_ID, 26, [this]() { this->read_pressure_(); }); } void BMP085Component::read_pressure_() { uint8_t buffer[3]; diff --git a/esphome/components/bmp280/__init__.py b/esphome/components/bmp280/__init__.py index e69de29bb2d..99130dd4e1e 100644 --- a/esphome/components/bmp280/__init__.py +++ b/esphome/components/bmp280/__init__.py @@ -0,0 +1 @@ +DOMAIN = "bmp280" diff --git a/esphome/components/bmp280_base/__init__.py b/esphome/components/bmp280_base/__init__.py index a956ed34f6e..68d9145378b 100644 --- a/esphome/components/bmp280_base/__init__.py +++ b/esphome/components/bmp280_base/__init__.py @@ -17,6 +17,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@ademuri"] +DOMAIN = "bmp280_base" bmp280_ns = cg.esphome_ns.namespace("bmp280_base") BMP280Oversampling = bmp280_ns.enum("BMP280Oversampling") diff --git a/esphome/components/bmp280_base/bmp280_base.cpp b/esphome/components/bmp280_base/bmp280_base.cpp index 34e1d671019..9cb023c6884 100644 --- a/esphome/components/bmp280_base/bmp280_base.cpp +++ b/esphome/components/bmp280_base/bmp280_base.cpp @@ -6,7 +6,9 @@ namespace esphome::bmp280_base { -static const char *const TAG = "bmp280.sensor"; +ESPHOME_LOG_TAG(TAG, "bmp280.sensor"); + +static constexpr uint32_t DATA_TIMEOUT_ID = 0; static const uint8_t BMP280_REGISTER_STATUS = 0xF3; static const uint8_t BMP280_REGISTER_CONTROL = 0xF4; @@ -166,7 +168,7 @@ void BMP280Component::update() { meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_); meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f; - this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { + this->set_timeout(DATA_TIMEOUT_ID, uint32_t(ceilf(meas_time)), [this]() { int32_t t_fine = 0; float temperature = this->read_temperature_(&t_fine); if (std::isnan(temperature)) { diff --git a/esphome/components/bmp280_i2c/__init__.py b/esphome/components/bmp280_i2c/__init__.py index e69de29bb2d..38fc9582e55 100644 --- a/esphome/components/bmp280_i2c/__init__.py +++ b/esphome/components/bmp280_i2c/__init__.py @@ -0,0 +1 @@ +DOMAIN = "bmp280_i2c" diff --git a/esphome/components/bmp280_spi/__init__.py b/esphome/components/bmp280_spi/__init__.py index e69de29bb2d..2b67436333f 100644 --- a/esphome/components/bmp280_spi/__init__.py +++ b/esphome/components/bmp280_spi/__init__.py @@ -0,0 +1 @@ +DOMAIN = "bmp280_spi" diff --git a/esphome/components/bmp3xx/__init__.py b/esphome/components/bmp3xx/__init__.py index e69de29bb2d..076eafa8e71 100644 --- a/esphome/components/bmp3xx/__init__.py +++ b/esphome/components/bmp3xx/__init__.py @@ -0,0 +1 @@ +DOMAIN = "bmp3xx" diff --git a/esphome/components/bmp3xx_base/__init__.py b/esphome/components/bmp3xx_base/__init__.py index 6d0618e136a..de9b227aac2 100644 --- a/esphome/components/bmp3xx_base/__init__.py +++ b/esphome/components/bmp3xx_base/__init__.py @@ -17,6 +17,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@martgras", "@latonita"] +DOMAIN = "bmp3xx_base" bmp3xx_ns = cg.esphome_ns.namespace("bmp3xx_base") Oversampling = bmp3xx_ns.enum("Oversampling") diff --git a/esphome/components/bmp3xx_base/bmp3xx_base.cpp b/esphome/components/bmp3xx_base/bmp3xx_base.cpp index aa5e799db16..b10a7a7a951 100644 --- a/esphome/components/bmp3xx_base/bmp3xx_base.cpp +++ b/esphome/components/bmp3xx_base/bmp3xx_base.cpp @@ -13,7 +13,9 @@ namespace esphome::bmp3xx_base { -static const char *const TAG = "bmp3xx.sensor"; +ESPHOME_LOG_TAG(TAG, "bmp3xx.sensor"); + +static constexpr uint32_t DATA_TIMEOUT_ID = 0; static const LogString *chip_type_to_str(uint8_t chip_type) { switch (chip_type) { @@ -170,7 +172,7 @@ void BMP3XXComponent::update() { const uint32_t meas_timeout = uint32_t(ceilf(meas_time)); ESP_LOGVV(TAG, "measurement time %" PRIu32, meas_timeout); - this->set_timeout("data", meas_timeout, [this]() { + this->set_timeout(DATA_TIMEOUT_ID, meas_timeout, [this]() { float temperature = 0.0f; float pressure = 0.0f; if (this->pressure_sensor_ != nullptr) { diff --git a/esphome/components/bmp3xx_i2c/__init__.py b/esphome/components/bmp3xx_i2c/__init__.py index e69de29bb2d..87cf0e52611 100644 --- a/esphome/components/bmp3xx_i2c/__init__.py +++ b/esphome/components/bmp3xx_i2c/__init__.py @@ -0,0 +1 @@ +DOMAIN = "bmp3xx_i2c" diff --git a/esphome/components/bmp3xx_i2c/bmp3xx_i2c.cpp b/esphome/components/bmp3xx_i2c/bmp3xx_i2c.cpp index 704d415993d..8d8f3122a91 100644 --- a/esphome/components/bmp3xx_i2c/bmp3xx_i2c.cpp +++ b/esphome/components/bmp3xx_i2c/bmp3xx_i2c.cpp @@ -4,7 +4,7 @@ namespace esphome::bmp3xx_i2c { -static const char *const TAG = "bmp3xx_i2c.sensor"; +ESPHOME_LOG_TAG(TAG, "bmp3xx_i2c.sensor"); bool BMP3XXI2CComponent::read_byte(uint8_t a_register, uint8_t *data) { return I2CDevice::read_byte(a_register, data); diff --git a/esphome/components/bmp3xx_spi/__init__.py b/esphome/components/bmp3xx_spi/__init__.py index e69de29bb2d..6e04124ccbd 100644 --- a/esphome/components/bmp3xx_spi/__init__.py +++ b/esphome/components/bmp3xx_spi/__init__.py @@ -0,0 +1 @@ +DOMAIN = "bmp3xx_spi" diff --git a/esphome/components/bmp3xx_spi/bmp3xx_spi.cpp b/esphome/components/bmp3xx_spi/bmp3xx_spi.cpp index 5657626c2d7..741f656c077 100644 --- a/esphome/components/bmp3xx_spi/bmp3xx_spi.cpp +++ b/esphome/components/bmp3xx_spi/bmp3xx_spi.cpp @@ -3,7 +3,7 @@ namespace esphome::bmp3xx_spi { -static const char *const TAG = "bmp3xx_spi.sensor"; +ESPHOME_LOG_TAG(TAG, "bmp3xx_spi.sensor"); uint8_t set_bit(uint8_t num, int position) { int mask = 1 << position; diff --git a/esphome/components/bmp581/__init__.py b/esphome/components/bmp581/__init__.py index e69de29bb2d..ad0da1d8f7f 100644 --- a/esphome/components/bmp581/__init__.py +++ b/esphome/components/bmp581/__init__.py @@ -0,0 +1 @@ +DOMAIN = "bmp581" diff --git a/esphome/components/bmp581_base/__init__.py b/esphome/components/bmp581_base/__init__.py index 5d031a8208a..18c18a2e9b9 100644 --- a/esphome/components/bmp581_base/__init__.py +++ b/esphome/components/bmp581_base/__init__.py @@ -19,6 +19,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@kahrendt", "@danielkent-net"] +DOMAIN = "bmp581_base" bmp581_ns = cg.esphome_ns.namespace("bmp581_base") diff --git a/esphome/components/bmp581_base/bmp581_base.cpp b/esphome/components/bmp581_base/bmp581_base.cpp index 7a627eee03f..29f67fadc51 100644 --- a/esphome/components/bmp581_base/bmp581_base.cpp +++ b/esphome/components/bmp581_base/bmp581_base.cpp @@ -17,7 +17,9 @@ namespace esphome::bmp581_base { -static const char *const TAG = "bmp581"; +ESPHOME_LOG_TAG(TAG, "bmp581"); + +static constexpr uint32_t MEASUREMENT_TIMEOUT_ID = 0; // Oversampling strings indexed by Oversampling enum (0-7): NONE, X2, X4, X8, X16, X32, X64, X128 PROGMEM_STRING_TABLE(OversamplingStrings, "None", "2x", "4x", "8x", "16x", "32x", "64x", "128x", ""); @@ -272,7 +274,7 @@ void BMP581Component::update() { ESP_LOGVV(TAG, "Measurement should take %d ms", this->conversion_time_); - this->set_timeout("measurement", this->conversion_time_, [this]() { + this->set_timeout(MEASUREMENT_TIMEOUT_ID, this->conversion_time_, [this]() { float temperature = 0.0; float pressure = 0.0; diff --git a/esphome/components/bmp581_i2c/__init__.py b/esphome/components/bmp581_i2c/__init__.py index e69de29bb2d..a5c70d3d425 100644 --- a/esphome/components/bmp581_i2c/__init__.py +++ b/esphome/components/bmp581_i2c/__init__.py @@ -0,0 +1 @@ +DOMAIN = "bmp581_i2c" diff --git a/esphome/components/bmp581_spi/__init__.py b/esphome/components/bmp581_spi/__init__.py index e69de29bb2d..d8f525adce0 100644 --- a/esphome/components/bmp581_spi/__init__.py +++ b/esphome/components/bmp581_spi/__init__.py @@ -0,0 +1 @@ +DOMAIN = "bmp581_spi" diff --git a/esphome/components/bmp581_spi/bmp581_spi.cpp b/esphome/components/bmp581_spi/bmp581_spi.cpp index 01435880f08..129128026c0 100644 --- a/esphome/components/bmp581_spi/bmp581_spi.cpp +++ b/esphome/components/bmp581_spi/bmp581_spi.cpp @@ -7,7 +7,7 @@ namespace esphome::bmp581_spi { -static const char *const TAG = "bmp581_spi"; +ESPHOME_LOG_TAG(TAG, "bmp581_spi"); // OR (|) register with BMP_SPI_READ for read inline constexpr uint8_t BMP_SPI_READ = 0x80; diff --git a/esphome/components/bp1658cj/__init__.py b/esphome/components/bp1658cj/__init__.py index b45272d2eaf..76b3d427a45 100644 --- a/esphome/components/bp1658cj/__init__.py +++ b/esphome/components/bp1658cj/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_ID from esphome.types import ConfigType CODEOWNERS = ["@Cossid"] +DOMAIN = "bp1658cj" MULTI_CONF = True CONF_MAX_POWER_COLOR_CHANNELS = "max_power_color_channels" diff --git a/esphome/components/bp1658cj/bp1658cj.cpp b/esphome/components/bp1658cj/bp1658cj.cpp index 95a6b3be457..c7dc61c6f04 100644 --- a/esphome/components/bp1658cj/bp1658cj.cpp +++ b/esphome/components/bp1658cj/bp1658cj.cpp @@ -3,7 +3,7 @@ namespace esphome::bp1658cj { -static const char *const TAG = "bp1658cj"; +ESPHOME_LOG_TAG(TAG, "bp1658cj"); static const uint8_t BP1658CJ_MODEL_ID = 0x80; static const uint8_t BP1658CJ_ADDR_STANDBY = 0x0; diff --git a/esphome/components/bp5758d/__init__.py b/esphome/components/bp5758d/__init__.py index fa4e8a231bb..bd6b6ca1106 100644 --- a/esphome/components/bp5758d/__init__.py +++ b/esphome/components/bp5758d/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_ID from esphome.types import ConfigType CODEOWNERS = ["@Cossid"] +DOMAIN = "bp5758d" MULTI_CONF = True AUTO_LOAD = ["output"] diff --git a/esphome/components/bp5758d/bp5758d.cpp b/esphome/components/bp5758d/bp5758d.cpp index 3cd841b2cd6..5789acdcb97 100644 --- a/esphome/components/bp5758d/bp5758d.cpp +++ b/esphome/components/bp5758d/bp5758d.cpp @@ -3,7 +3,7 @@ namespace esphome::bp5758d { -static const char *const TAG = "bp5758d"; +ESPHOME_LOG_TAG(TAG, "bp5758d"); static const uint8_t BP5758D_MODEL_ID = 0b10000000; static const uint8_t BP5758D_ADDR_STANDBY = 0b00000000; diff --git a/esphome/components/bthome_mithermometer/__init__.py b/esphome/components/bthome_mithermometer/__init__.py index ed0cbaa9e14..fe78162c650 100644 --- a/esphome/components/bthome_mithermometer/__init__.py +++ b/esphome/components/bthome_mithermometer/__init__.py @@ -8,6 +8,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@nagyrobi"] AUTO_LOAD = ["ble_device_base"] +DOMAIN = "bthome_mithermometer" bthome_mithermometer_ns = cg.esphome_ns.namespace("bthome_mithermometer") diff --git a/esphome/components/bthome_mithermometer/bthome_ble.cpp b/esphome/components/bthome_mithermometer/bthome_ble.cpp index 51f5d866a49..c055c430413 100644 --- a/esphome/components/bthome_mithermometer/bthome_ble.cpp +++ b/esphome/components/bthome_mithermometer/bthome_ble.cpp @@ -26,7 +26,7 @@ namespace esphome::bthome_mithermometer { -static const char *const TAG = "bthome_mithermometer"; +ESPHOME_LOG_TAG(TAG, "bthome_mithermometer"); static constexpr size_t BTHOME_BINDKEY_SIZE = 16; static constexpr size_t BTHOME_NONCE_SIZE = 13; static constexpr size_t BTHOME_MIC_SIZE = 4; diff --git a/esphome/components/button/__init__.py b/esphome/components/button/__init__.py index 342f4515093..f27475bdca3 100644 --- a/esphome/components/button/__init__.py +++ b/esphome/components/button/__init__.py @@ -28,6 +28,7 @@ from esphome.cpp_generator import MockObj, MockObjClass from esphome.types import ConfigType, SafeExpType CODEOWNERS = ["@esphome/core"] +DOMAIN = "button" IS_PLATFORM_COMPONENT = True DEVICE_CLASSES = [ diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp index 2a2a6451325..e85ce60df6e 100644 --- a/esphome/components/button/button.cpp +++ b/esphome/components/button/button.cpp @@ -3,7 +3,7 @@ namespace esphome::button { -static const char *const TAG = "button"; +ESPHOME_LOG_TAG(TAG, "button"); // Function implementation of LOG_BUTTON macro to reduce code size void log_button(const char *tag, const char *prefix, const char *type, Button *obj) { diff --git a/esphome/components/bytebuffer/__init__.py b/esphome/components/bytebuffer/__init__.py index 3c7c695118b..df0c76338c3 100644 --- a/esphome/components/bytebuffer/__init__.py +++ b/esphome/components/bytebuffer/__init__.py @@ -1,4 +1,5 @@ CODEOWNERS = ["@clydebarrow"] +DOMAIN = "bytebuffer" # Allows bytebuffer to be configured in yaml, to allow use of the C++ api. diff --git a/esphome/components/camera/__init__.py b/esphome/components/camera/__init__.py index a19f7707afe..0cb916ac49a 100644 --- a/esphome/components/camera/__init__.py +++ b/esphome/components/camera/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@DT-art1", "@bdraco"] +DOMAIN = "camera" diff --git a/esphome/components/camera_encoder/__init__.py b/esphome/components/camera_encoder/__init__.py index 344248fcf31..f63c316126b 100644 --- a/esphome/components/camera_encoder/__init__.py +++ b/esphome/components/camera_encoder/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_BUFFER_SIZE, CONF_ID, CONF_TYPE from esphome.types import ConfigType CODEOWNERS = ["@DT-art1"] +DOMAIN = "camera_encoder" AUTO_LOAD = ["camera"] diff --git a/esphome/components/camera_encoder/esp32_camera_jpeg_encoder.cpp b/esphome/components/camera_encoder/esp32_camera_jpeg_encoder.cpp index 55a3f0b96c2..84d5a9117da 100644 --- a/esphome/components/camera_encoder/esp32_camera_jpeg_encoder.cpp +++ b/esphome/components/camera_encoder/esp32_camera_jpeg_encoder.cpp @@ -6,7 +6,7 @@ namespace esphome::camera_encoder { -static const char *const TAG = "camera_encoder"; +ESPHOME_LOG_TAG(TAG, "camera_encoder"); ESP32CameraJPEGEncoder::ESP32CameraJPEGEncoder(uint8_t quality, camera::EncoderBuffer *output) { this->quality_ = quality; diff --git a/esphome/components/canbus/__init__.py b/esphome/components/canbus/__init__.py index b7de235dd18..51bda0c75df 100644 --- a/esphome/components/canbus/__init__.py +++ b/esphome/components/canbus/__init__.py @@ -10,6 +10,7 @@ from esphome.cpp_generator import MockObj, TemplateArgsType from esphome.types import ConfigType CODEOWNERS = ["@mvturnho", "@danielschramm"] +DOMAIN = "canbus" IS_PLATFORM_COMPONENT = True CONF_CAN_ID = "can_id" @@ -144,20 +145,26 @@ async def register_canbus(var: MockObj, config: ConfigType) -> None: # Actions +CANBUS_SEND_ACTION_SCHEMA = cv.maybe_simple_value( + { + cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent), + cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), + cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, + cv.Optional(CONF_REMOTE_TRANSMISSION_REQUEST, default=False): cv.boolean, + # One classic CAN frame + cv.Required(CONF_DATA): cv.templatable( + cv.All(validate_raw_data, cv.Length(max=8)) + ), + }, + validate_id, + key=CONF_DATA, +) + + @automation.register_action( "canbus.send", canbus_ns.class_("CanbusSendAction", automation.Action), - cv.maybe_simple_value( - { - cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent), - cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), - cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, - cv.Optional(CONF_REMOTE_TRANSMISSION_REQUEST, default=False): cv.boolean, - cv.Required(CONF_DATA): cv.templatable(validate_raw_data), - }, - validate_id, - key=CONF_DATA, - ), + CANBUS_SEND_ACTION_SCHEMA, synchronous=True, ) async def canbus_action_to_code( @@ -177,15 +184,11 @@ async def canbus_action_to_code( var.set_remote_transmission_request(config[CONF_REMOTE_TRANSMISSION_REQUEST]) ) - data = config[CONF_DATA] - if cg.is_template(data): - templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) - cg.add(var.set_data_template(templ)) - else: - if isinstance(data, bytes): - data = [int(x) for x in data] - # Generate static array in flash to avoid RAM copy - arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8) - arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data)) - cg.add(var.set_data_static(arr, len(data))) + await automation.templatable_bytes( + config[CONF_DATA], + args, + var.set_data_template, + var.set_data_static, + "canbus_data", + ) return var diff --git a/esphome/components/canbus/canbus.cpp b/esphome/components/canbus/canbus.cpp index c5ba59a645a..828eb77d1ca 100644 --- a/esphome/components/canbus/canbus.cpp +++ b/esphome/components/canbus/canbus.cpp @@ -4,7 +4,7 @@ namespace esphome::canbus { -static const char *const TAG = "canbus"; +ESPHOME_LOG_TAG(TAG, "canbus"); void Canbus::setup() { if (!this->setup_internal()) { @@ -22,10 +22,11 @@ void Canbus::dump_config() { } canbus::Error Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request, - const std::vector &data) { + const uint8_t *data, size_t len) { struct CanFrame can_message; - uint8_t size = static_cast(data.size()); + // Clamp before narrowing, so a lambda payload of 256 bytes or more cannot wrap to a short length + const uint8_t size = static_cast(std::min(len, CAN_MAX_DATA_LENGTH)); if (use_extended_id) { ESP_LOGD(TAG, "send extended id=0x%08" PRIx32 " rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size); @@ -33,8 +34,6 @@ canbus::Error Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remo ESP_LOGD(TAG, "send standard id=0x%03" PRIx32 " rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size); } - if (size > CAN_MAX_DATA_LENGTH) - size = CAN_MAX_DATA_LENGTH; can_message.can_data_length_code = size; can_message.can_id = can_id; can_message.use_extended_id = use_extended_id; diff --git a/esphome/components/canbus/canbus.h b/esphome/components/canbus/canbus.h index 1bc4d6e3455..8685c026b0b 100644 --- a/esphome/components/canbus/canbus.h +++ b/esphome/components/canbus/canbus.h @@ -69,8 +69,12 @@ class Canbus : public Component { float get_setup_priority() const override { return setup_priority::HARDWARE; } void loop() override; + canbus::Error send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request, const uint8_t *data, + size_t len); canbus::Error send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request, - const std::vector &data); + const std::vector &data) { + return this->send_data(can_id, use_extended_id, remote_transmission_request, data.data(), data.size()); + } canbus::Error send_data(uint32_t can_id, bool use_extended_id, const std::vector &data) { // for backwards compatibility only return this->send_data(can_id, use_extended_id, false, data); @@ -107,18 +111,7 @@ class Canbus : public Component { }; template class CanbusSendAction final : public Action, public Parented { - public: - void set_data_template(std::vector (*func)(Ts...)) { - // Stateless lambdas (generated by ESPHome) implicitly convert to function pointers - this->data_.func = func; - this->len_ = -1; // Sentinel value indicates template mode - } - - // Store pointer to static data in flash (no RAM copy) - void set_data_static(const uint8_t *data, size_t len) { - this->data_.data = data; - this->len_ = len; // Length >= 0 indicates static mode - } + TEMPLATABLE_BYTES(data) void set_can_id(uint32_t can_id) { this->can_id_ = can_id; } @@ -132,26 +125,17 @@ template class CanbusSendAction final : public Action, pu auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_; auto use_extended_id = this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_; - std::vector data; - if (this->len_ >= 0) { - // Static mode: copy from flash to vector - data.assign(this->data_.data, this->data_.data + this->len_); - } else { - // Template mode: call function - data = this->data_.func(x...); - } - this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, data); + this->data_.template visit( + [&](const uint8_t *data, size_t len) { + this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, data, len); + }, + x...); } protected: optional can_id_{}; optional use_extended_id_{}; bool remote_transmission_request_{false}; - ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length - union Data { - std::vector (*func)(Ts...); // Function pointer (stateless lambdas) - const uint8_t *data; // Pointer to static data in flash - } data_; }; class CanbusTrigger final : public Trigger, uint32_t, bool>, public Component { diff --git a/esphome/components/cap1188/__init__.py b/esphome/components/cap1188/__init__.py index eff0a051639..24f6e627d2c 100644 --- a/esphome/components/cap1188/__init__.py +++ b/esphome/components/cap1188/__init__.py @@ -11,6 +11,7 @@ CONF_ALLOW_MULTIPLE_TOUCHES = "allow_multiple_touches" DEPENDENCIES = ["i2c"] AUTO_LOAD = ["binary_sensor", "output"] CODEOWNERS = ["@mreditor97"] +DOMAIN = "cap1188" cap1188_ns = cg.esphome_ns.namespace("cap1188") CONF_CAP1188_ID = "cap1188_id" diff --git a/esphome/components/cap1188/cap1188.cpp b/esphome/components/cap1188/cap1188.cpp index 125a4aba51e..d6d97329db4 100644 --- a/esphome/components/cap1188/cap1188.cpp +++ b/esphome/components/cap1188/cap1188.cpp @@ -4,7 +4,7 @@ namespace esphome::cap1188 { -static const char *const TAG = "cap1188"; +ESPHOME_LOG_TAG(TAG, "cap1188"); void CAP1188Component::setup() { this->disable_loop(); diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index e490d89062a..3bdea03bf52 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -3,6 +3,7 @@ import logging import esphome.codegen as cg from esphome.components import web_server_base, wifi from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID +from esphome.components.wifi import DOMAIN as WIFI_DOMAIN from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( @@ -34,6 +35,7 @@ def AUTO_LOAD() -> list[str]: DEPENDENCIES = ["wifi"] CODEOWNERS = ["@esphome/core"] +DOMAIN = "captive_portal" captive_portal_ns = cg.esphome_ns.namespace("captive_portal") CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component) @@ -63,7 +65,7 @@ CONFIG_SCHEMA = cv.All( def _final_validate(config: ConfigType) -> None: full_config = fv.full_config.get() - wifi_conf = full_config.get("wifi") + wifi_conf = full_config.get(WIFI_DOMAIN) if wifi_conf is None: # This shouldn't happen due to DEPENDENCIES = ["wifi"], but check anyway diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index 599422a46b0..36e9ec406d1 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -13,7 +13,7 @@ namespace esphome::captive_portal { -static const char *const TAG = "captive_portal"; +ESPHOME_LOG_TAG(TAG, "captive_portal"); void CaptivePortal::handle_config(AsyncWebServerRequest *request) { AsyncResponseStream *stream = request->beginResponseStream(ESPHOME_F("application/json")); diff --git a/esphome/components/captive_portal/dns_server_esp32_idf.cpp b/esphome/components/captive_portal/dns_server_esp32_idf.cpp index 56ad9f7176b..d7a46373b86 100644 --- a/esphome/components/captive_portal/dns_server_esp32_idf.cpp +++ b/esphome/components/captive_portal/dns_server_esp32_idf.cpp @@ -9,7 +9,7 @@ namespace esphome::captive_portal { -static const char *const TAG = "captive_portal.dns"; +ESPHOME_LOG_TAG(TAG, "captive_portal.dns"); // DNS constants static constexpr uint16_t DNS_PORT = 53; diff --git a/esphome/components/cc1101/__init__.py b/esphome/components/cc1101/__init__.py index 5de39a16cec..8b9e91f833a 100644 --- a/esphome/components/cc1101/__init__.py +++ b/esphome/components/cc1101/__init__.py @@ -13,10 +13,10 @@ from esphome.const import ( CONF_VALUE, CONF_WAIT_TIME, ) -from esphome.core import ID CODEOWNERS = ["@lygris", "@gabest11"] DEPENDENCIES = ["spi"] +DOMAIN = "cc1101" MULTI_CONF = True ns = cg.esphome_ns.namespace("cc1101") @@ -384,7 +384,10 @@ def validate_raw_data(value): SEND_PACKET_ACTION_SCHEMA = cv.maybe_simple_value( { cv.GenerateID(): cv.use_id(CC1101Component), - cv.Required(CONF_DATA): cv.templatable(validate_raw_data), + # TX FIFO size + cv.Required(CONF_DATA): cv.templatable( + cv.All(validate_raw_data, cv.Length(max=64)) + ), }, key=CONF_DATA, ) @@ -399,17 +402,13 @@ SEND_PACKET_ACTION_SCHEMA = cv.maybe_simple_value( async def send_packet_action_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - data = config[CONF_DATA] - if isinstance(data, bytes): - data = list(data) - if cg.is_template(data): - templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) - cg.add(var.set_data_template(templ)) - else: - # Generate static array in flash to avoid RAM copy - arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8) - arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data)) - cg.add(var.set_data_static(arr, len(data))) + await automation.templatable_bytes( + config[CONF_DATA], + args, + var.set_data_template, + var.set_data_static, + "cc1101_data", + ) return var diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index f7b90b91cf6..94d89b899d3 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -3,10 +3,16 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include +#include + +#ifdef USE_ESP32 +#include +#endif namespace esphome::cc1101 { -static const char *const TAG = "cc1101"; +ESPHOME_LOG_TAG(TAG, "cc1101"); +static constexpr size_t TX_FIFO_SIZE = 64; static void split_float(float value, int mbits, uint8_t &e, uint32_t &m) { int e_tmp; @@ -253,11 +259,17 @@ void CC1101Component::dump_config() { } void CC1101Component::begin_tx() { + // Read the mode before the PKTCTRL0 write below overwrites it in state_ + const bool packet_mode = this->state_.PKT_FORMAT == static_cast(PacketFormat::PACKET_FORMAT_FIFO); // Ensure Packet Format is 3 (Async Serial) this->write_(Register::PKTCTRL0, 0x32); ESP_LOGV(TAG, "Beginning TX sequence"); if (this->gdo0_pin_ != nullptr) { - this->gdo0_pin_->detach_interrupt(); + // setup() only attaches the interrupt in packet mode. In async serial mode the pin may be + // shared with remote_receiver, and detaching here would remove the receiver's interrupt. + if (packet_mode) { + this->gdo0_pin_->detach_interrupt(); + } this->gdo0_pin_->pin_mode(gpio::FLAG_OUTPUT); } // Transition through IDLE to bypass CCA (Clear Channel Assessment) which can @@ -379,18 +391,32 @@ void CC1101Component::read_(Register reg, uint8_t *buffer, size_t length) { this->disable(); } -CC1101Error CC1101Component::transmit_packet(const std::vector &packet) { +CC1101Error CC1101Component::transmit_packet(const uint8_t *data, size_t len) { if (this->state_.PKT_FORMAT != static_cast(PacketFormat::PACKET_FORMAT_FIFO)) { return CC1101Error::PARAMS; } + const bool variable = this->state_.LENGTH_CONFIG == static_cast(LengthConfig::LENGTH_CONFIG_VARIABLE); + // In variable length mode the FIFO also holds the length byte + if (len + (variable ? 1 : 0) > TX_FIFO_SIZE) { + ESP_LOGE(TAG, "Packet of %u bytes does not fit the TX FIFO", static_cast(len)); + return CC1101Error::PARAMS; + } // Write packet this->enter_idle_(); this->strobe_(Command::FTX); - if (this->state_.LENGTH_CONFIG == static_cast(LengthConfig::LENGTH_CONFIG_VARIABLE)) { - this->write_(Register::FIFO, static_cast(packet.size())); + if (variable) { + this->write_(Register::FIFO, static_cast(len)); } - this->write_(Register::FIFO, packet.data(), packet.size()); +#ifdef USE_ESP32 + // SPI DMA cannot read flash or unaligned buffers; stage them here so the driver does not allocate a copy + alignas(4) uint8_t staged[TX_FIFO_SIZE]; + if (len != 0 && (!esp_ptr_dma_capable(data) || (reinterpret_cast(data) & 3) != 0)) { + std::memcpy(staged, data, len); + data = staged; + } +#endif + this->write_(Register::FIFO, data, len); // Calibrate PLL if (!this->enter_calibrated_(State::FSTXON, Command::FSTXON)) { diff --git a/esphome/components/cc1101/cc1101.h b/esphome/components/cc1101/cc1101.h index 499f77a809c..d7065c60487 100644 --- a/esphome/components/cc1101/cc1101.h +++ b/esphome/components/cc1101/cc1101.h @@ -89,7 +89,10 @@ class CC1101Component final : public Component, void set_whitening(bool value); // Packet mode operations - CC1101Error transmit_packet(const std::vector &packet); + CC1101Error transmit_packet(const std::vector &packet) { + return this->transmit_packet(packet.data(), packet.size()); + } + CC1101Error transmit_packet(const uint8_t *data, size_t len); void register_listener(CC1101Listener *listener) { this->listeners_.push_back(listener); } Trigger, float, float, uint8_t> *get_packet_trigger() { return &this->packet_trigger_; } @@ -131,27 +134,11 @@ class CC1101Component final : public Component, // Action Wrappers template class SendPacketAction final : public Action, public Parented { - public: - void set_data_template(std::function(Ts...)> func) { this->data_func_ = func; } - void set_data_static(const uint8_t *data, size_t len) { - this->data_static_ = data; - this->data_static_len_ = len; - } + TEMPLATABLE_BYTES(data) void play(const Ts &...x) override { - if (this->data_func_) { - auto data = this->data_func_(x...); - this->parent_->transmit_packet(data); - } else if (this->data_static_ != nullptr) { - std::vector data(this->data_static_, this->data_static_ + this->data_static_len_); - this->parent_->transmit_packet(data); - } + this->data_.visit([this](const uint8_t *data, size_t len) { this->parent_->transmit_packet(data, len); }, x...); } - - protected: - std::function(Ts...)> data_func_{}; - const uint8_t *data_static_{nullptr}; - size_t data_static_len_{0}; }; } // namespace esphome::cc1101 diff --git a/esphome/components/ccs811/__init__.py b/esphome/components/ccs811/__init__.py index e69de29bb2d..4a86948d394 100644 --- a/esphome/components/ccs811/__init__.py +++ b/esphome/components/ccs811/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ccs811" diff --git a/esphome/components/ccs811/ccs811.cpp b/esphome/components/ccs811/ccs811.cpp index afaeac55aaa..126ed0049d1 100644 --- a/esphome/components/ccs811/ccs811.cpp +++ b/esphome/components/ccs811/ccs811.cpp @@ -5,7 +5,7 @@ namespace esphome::ccs811 { -static const char *const TAG = "ccs811"; +ESPHOME_LOG_TAG(TAG, "ccs811"); // based on // - https://cdn.sparkfun.com/datasheets/BreakoutBoards/CCS811_Programming_Guide.pdf diff --git a/esphome/components/cd74hc4067/__init__.py b/esphome/components/cd74hc4067/__init__.py index 5f7778e1861..655b8db819d 100644 --- a/esphome/components/cd74hc4067/__init__.py +++ b/esphome/components/cd74hc4067/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["sensor", "voltage_sampler"] CODEOWNERS = ["@asoehlke"] +DOMAIN = "cd74hc4067" MULTI_CONF = True cd74hc4067_ns = cg.esphome_ns.namespace("cd74hc4067") diff --git a/esphome/components/cd74hc4067/cd74hc4067.cpp b/esphome/components/cd74hc4067/cd74hc4067.cpp index c275aca75f7..b0a042e868a 100644 --- a/esphome/components/cd74hc4067/cd74hc4067.cpp +++ b/esphome/components/cd74hc4067/cd74hc4067.cpp @@ -4,7 +4,7 @@ namespace esphome::cd74hc4067 { -static const char *const TAG = "cd74hc4067"; +ESPHOME_LOG_TAG(TAG, "cd74hc4067"); void CD74HC4067Component::setup() { this->pin_s0_->setup(); diff --git a/esphome/components/cdc_acm_uart/__init__.py b/esphome/components/cdc_acm_uart/__init__.py index 516af848564..9d5c0a3e951 100644 --- a/esphome/components/cdc_acm_uart/__init__.py +++ b/esphome/components/cdc_acm_uart/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@kbx81"] +DOMAIN = "cdc_acm_uart" diff --git a/esphome/components/cdc_acm_uart/bridge/__init__.py b/esphome/components/cdc_acm_uart/bridge/__init__.py index cee048df5da..133ea92a483 100644 --- a/esphome/components/cdc_acm_uart/bridge/__init__.py +++ b/esphome/components/cdc_acm_uart/bridge/__init__.py @@ -36,19 +36,6 @@ CONFIG_SCHEMA = cv.All( ) -def _subtree_references_uart(node: object, uart_id: str) -> bool: - """Return True if any dict in the subtree has a uart_id entry naming this bus.""" - if isinstance(node, dict): - return any( - (key == CONF_UART_ID and str(value) == uart_id) - or _subtree_references_uart(value, uart_id) - for key, value in node.items() - ) - if isinstance(node, list): - return any(_subtree_references_uart(item, uart_id) for item in node) - return False - - def _reject_debug(uart_conf: ConfigType) -> ConfigType: # The worker tasks use the IDF driver directly, so the uart debugger never sees # bridge traffic and its dummy_receiver would drain RX bytes on the main loop. @@ -86,7 +73,7 @@ def _final_validate(config: ConfigType) -> ConfigType: for domain, domain_conf in full_config.items(): if domain == BRIDGE_DOMAIN: continue - if _subtree_references_uart(domain_conf, owned_id): + if uart.subtree_references_uart(domain_conf, owned_id): raise cv.Invalid( f"The {label} '{owned_id}' is also used by '{domain}'; a bridge " f"requires exclusive use of its {label}.", diff --git a/esphome/components/cdc_acm_uart/bridge/cdc_acm_uart_bridge.cpp b/esphome/components/cdc_acm_uart/bridge/cdc_acm_uart_bridge.cpp index 042688bfe61..a1bf0a19729 100644 --- a/esphome/components/cdc_acm_uart/bridge/cdc_acm_uart_bridge.cpp +++ b/esphome/components/cdc_acm_uart/bridge/cdc_acm_uart_bridge.cpp @@ -13,7 +13,7 @@ namespace esphome::cdc_acm_uart { -static const char *const TAG = "cdc_acm_uart"; +ESPHOME_LOG_TAG(TAG, "cdc_acm_uart"); static constexpr size_t UART_TASK_STACK_SIZE = 4096; static constexpr size_t RINGBUF_RETRY_CHUNK_SIZE = 64; diff --git a/esphome/components/ch422g/__init__.py b/esphome/components/ch422g/__init__.py index 7f0c5bb95e6..5110503bf84 100644 --- a/esphome/components/ch422g/__init__.py +++ b/esphome/components/ch422g/__init__.py @@ -18,6 +18,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@jesterret", "@clydebarrow"] DEPENDENCIES = ["i2c"] +DOMAIN = "ch422g" MULTI_CONF = True ch422g_ns = cg.esphome_ns.namespace("ch422g") diff --git a/esphome/components/ch422g/ch422g.cpp b/esphome/components/ch422g/ch422g.cpp index 85603f46f4f..28b355a4f1d 100644 --- a/esphome/components/ch422g/ch422g.cpp +++ b/esphome/components/ch422g/ch422g.cpp @@ -10,7 +10,7 @@ static const uint8_t CH422G_REG_IN = 0x26; // read reg for input bits static const uint8_t CH422G_REG_OUT = 0x38; // write reg for output bits 0-7 static const uint8_t CH422G_REG_OUT_UPPER = 0x23; // write reg for output bits 8-11 -static const char *const TAG = "ch422g"; +ESPHOME_LOG_TAG(TAG, "ch422g"); void CH422GComponent::setup() { // set outputs before mode diff --git a/esphome/components/ch423/__init__.py b/esphome/components/ch423/__init__.py index 9fbf3ea515e..3997f9581f0 100644 --- a/esphome/components/ch423/__init__.py +++ b/esphome/components/ch423/__init__.py @@ -19,6 +19,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@dwmw2"] DEPENDENCIES = ["i2c"] +DOMAIN = "ch423" MULTI_CONF = True ch423_ns = cg.esphome_ns.namespace("ch423") @@ -59,7 +60,7 @@ def pin_mode_check(pin_config: ConfigType, _: ConfigType) -> None: is_open_drain = pin_config[CONF_MODE][CONF_OPEN_DRAIN] # Track pin modes per CH423 instance in CORE.data - ch423_modes = CORE.data.setdefault(CONF_CH423, {}) + ch423_modes = CORE.data.setdefault(DOMAIN, {}) if ch423_id not in ch423_modes: ch423_modes[ch423_id] = {"gpio_output": None, "gpo_open_drain": None} diff --git a/esphome/components/ch423/ch423.cpp b/esphome/components/ch423/ch423.cpp index 8424d130b4b..b7e9d224faf 100644 --- a/esphome/components/ch423/ch423.cpp +++ b/esphome/components/ch423/ch423.cpp @@ -12,7 +12,7 @@ static constexpr uint8_t CH423_REG_IO_RD = 0x26; // Read IO7-IO0 (0x4D >> 1, ro static constexpr uint8_t CH423_REG_OCL = 0x22; // Write OC7-OC0 (0x44 >> 1) static constexpr uint8_t CH423_REG_OCH = 0x23; // Write OC15-OC8 (0x46 >> 1) -static const char *const TAG = "ch423"; +ESPHOME_LOG_TAG(TAG, "ch423"); void CH423Component::setup() { // set outputs before mode diff --git a/esphome/components/chsc6x/__init__.py b/esphome/components/chsc6x/__init__.py index 80f3a0b33ec..bbdc8707732 100644 --- a/esphome/components/chsc6x/__init__.py +++ b/esphome/components/chsc6x/__init__.py @@ -1,2 +1,3 @@ CODEOWNERS = ["@kkosik20"] DEPENDENCIES = ["i2c"] +DOMAIN = "chsc6x" diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index e64092d30e5..17aeb1467d3 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -62,6 +62,7 @@ from esphome.types import ConfigType, SafeExpType IS_PLATFORM_COMPONENT = True CODEOWNERS = ["@esphome/core"] +DOMAIN = "climate" climate_ns = cg.esphome_ns.namespace("climate") Climate = climate_ns.class_("Climate", cg.EntityBase) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index d08952115bd..790307cec14 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -6,7 +6,7 @@ namespace esphome::climate { -static const char *const TAG = "climate"; +ESPHOME_LOG_TAG(TAG, "climate"); // Memory-efficient lookup tables struct StringToUint8 { diff --git a/esphome/components/climate_ir/__init__.py b/esphome/components/climate_ir/__init__.py index f5a31b31c4f..f4b43e3a523 100644 --- a/esphome/components/climate_ir/__init__.py +++ b/esphome/components/climate_ir/__init__.py @@ -19,6 +19,7 @@ CONF_SUPPORTS_HEAT_COOL = "supports_heat_cool" DEPENDENCIES = ["remote_transmitter"] AUTO_LOAD = ["sensor", "remote_base"] CODEOWNERS = ["@glmnet"] +DOMAIN = "climate_ir" climate_ir_ns = cg.esphome_ns.namespace("climate_ir") ClimateIR = climate_ir_ns.class_( diff --git a/esphome/components/climate_ir/climate_ir.cpp b/esphome/components/climate_ir/climate_ir.cpp index 47143e6e607..622aff243cd 100644 --- a/esphome/components/climate_ir/climate_ir.cpp +++ b/esphome/components/climate_ir/climate_ir.cpp @@ -3,7 +3,7 @@ namespace esphome::climate_ir { -static const char *const TAG = "climate_ir"; +ESPHOME_LOG_TAG(TAG, "climate_ir"); climate::ClimateTraits ClimateIR::traits() { auto traits = climate::ClimateTraits(); diff --git a/esphome/components/climate_ir_lg/__init__.py b/esphome/components/climate_ir_lg/__init__.py index e69de29bb2d..b62e9d17fb1 100644 --- a/esphome/components/climate_ir_lg/__init__.py +++ b/esphome/components/climate_ir_lg/__init__.py @@ -0,0 +1 @@ +DOMAIN = "climate_ir_lg" diff --git a/esphome/components/climate_ir_lg/climate_ir_lg.cpp b/esphome/components/climate_ir_lg/climate_ir_lg.cpp index bb612eda7bd..042d1dcb21f 100644 --- a/esphome/components/climate_ir_lg/climate_ir_lg.cpp +++ b/esphome/components/climate_ir_lg/climate_ir_lg.cpp @@ -3,7 +3,7 @@ namespace esphome::climate_ir_lg { -static const char *const TAG = "climate.climate_ir_lg"; +ESPHOME_LOG_TAG(TAG, "climate.climate_ir_lg"); // All codes provided here are missing the checksum (last 4 bits) // this checksum needs to be calculated before sending (look at `calc_checksum_()`) diff --git a/esphome/components/cm1106/__init__.py b/esphome/components/cm1106/__init__.py index fa3c3f19252..24203304ae3 100644 --- a/esphome/components/cm1106/__init__.py +++ b/esphome/components/cm1106/__init__.py @@ -1 +1,3 @@ """CM1106 component for ESPHome.""" + +DOMAIN = "cm1106" diff --git a/esphome/components/cm1106/cm1106.cpp b/esphome/components/cm1106/cm1106.cpp index 2e3352b8953..a25a753f754 100644 --- a/esphome/components/cm1106/cm1106.cpp +++ b/esphome/components/cm1106/cm1106.cpp @@ -5,7 +5,7 @@ namespace esphome::cm1106 { -static const char *const TAG = "cm1106"; +ESPHOME_LOG_TAG(TAG, "cm1106"); static const uint8_t C_M1106_CMD_GET_CO2[4] = {0x11, 0x01, 0x01, 0xED}; static const uint8_t C_M1106_CMD_SET_CO2_CALIB[6] = {0x11, 0x03, 0x03, 0x00, 0x00, 0x00}; static const uint8_t C_M1106_CMD_SET_CO2_CALIB_RESPONSE[4] = {0x16, 0x01, 0x03, 0xE6}; diff --git a/esphome/components/color/__init__.py b/esphome/components/color/__init__.py index 70240eff07b..49978c29c2e 100644 --- a/esphome/components/color/__init__.py +++ b/esphome/components/color/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType ColorStruct = cg.esphome_ns.struct("Color") +DOMAIN = "color" INSTANCE_TYPE = ColorStruct MULTI_CONF = True diff --git a/esphome/components/color_temperature/__init__.py b/esphome/components/color_temperature/__init__.py index e69de29bb2d..2c38fe6fc6b 100644 --- a/esphome/components/color_temperature/__init__.py +++ b/esphome/components/color_temperature/__init__.py @@ -0,0 +1 @@ +DOMAIN = "color_temperature" diff --git a/esphome/components/combination/__init__.py b/esphome/components/combination/__init__.py index e69de29bb2d..7625bf2d023 100644 --- a/esphome/components/combination/__init__.py +++ b/esphome/components/combination/__init__.py @@ -0,0 +1 @@ +DOMAIN = "combination" diff --git a/esphome/components/combination/combination.cpp b/esphome/components/combination/combination.cpp index 8ef0976e3b7..0488fbb6ab2 100644 --- a/esphome/components/combination/combination.cpp +++ b/esphome/components/combination/combination.cpp @@ -7,7 +7,9 @@ namespace esphome::combination { -static const char *const TAG = "combination"; +ESPHOME_LOG_TAG(TAG, "combination"); + +static constexpr uint32_t NEW_VALUE_DEFER_ID = 0; void CombinationComponent::log_config_(const LogString *combo_type) { LOG_SENSOR("", "Combination Sensor:", this); @@ -43,8 +45,9 @@ void CombinationNoParameterComponent::setup() { for (const auto &sensor : this->sensors_) { // All sensor updates are deferred until the next loop. This avoids publishing the combined sensor's result // repeatedly in the same loop if multiple source senors update. - sensor->add_on_state_callback( - [this](float value) -> void { this->defer("update", [this, value]() { this->handle_new_value(value); }); }); + sensor->add_on_state_callback([this](float value) -> void { + this->defer(NEW_VALUE_DEFER_ID, [this, value]() { this->handle_new_value(value); }); + }); } } @@ -119,8 +122,9 @@ void LinearCombinationComponent::setup() { for (auto &source : this->sensor_sources_) { // All sensor updates are deferred until the next loop. This avoids publishing the combined sensor's result // repeatedly in the same loop if multiple source senors update. - source.sensor->add_on_state_callback( - [this](float value) -> void { this->defer("update", [this, value]() { this->handle_new_value(value); }); }); + source.sensor->add_on_state_callback([this](float value) -> void { + this->defer(NEW_VALUE_DEFER_ID, [this, value]() { this->handle_new_value(value); }); + }); } } diff --git a/esphome/components/combination/sensor.py b/esphome/components/combination/sensor.py index 34e9b99ce84..608ebac4d6a 100644 --- a/esphome/components/combination/sensor.py +++ b/esphome/components/combination/sensor.py @@ -1,5 +1,3 @@ -import logging - import esphome.codegen as cg from esphome.components import sensor import esphome.config_validation as cv @@ -18,8 +16,6 @@ from esphome.const import ( from esphome.core.entity_helpers import inherit_property_from from esphome.types import ConfigType -_LOGGER = logging.getLogger(__name__) - CODEOWNERS = ["@Cat-Ion", "@kahrendt"] combination_ns = cg.esphome_ns.namespace("combination") @@ -75,33 +71,19 @@ KALMAN_SOURCE_SCHEMA = cv.Schema( ) -def _migrate_coeffecient(config: ConfigType) -> ConfigType: - """Migrate deprecated 'coeffecient' spelling to 'coefficient'.""" - if CONF_COEFFECIENT in config: - if CONF_COEFFICIENT in config: - raise cv.Invalid( - f"Cannot specify both '{CONF_COEFFICIENT}' and '{CONF_COEFFECIENT}'" - ) - _LOGGER.warning( - "'%s' is deprecated, use '%s' instead. Will be removed in 2026.12.0", - CONF_COEFFECIENT, - CONF_COEFFICIENT, - ) - config[CONF_COEFFICIENT] = config.pop(CONF_COEFFECIENT) - elif CONF_COEFFICIENT not in config: - raise cv.Invalid(f"'{CONF_COEFFICIENT}' is a required option") - return config - - LINEAR_SOURCE_SCHEMA = cv.All( + cv.rename_key( + CONF_COEFFECIENT, + CONF_COEFFICIENT, + removed_in="2026.12.0", + component="combination", + ), cv.Schema( { cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor), - cv.Optional(CONF_COEFFICIENT): cv.templatable(cv.float_), - cv.Optional(CONF_COEFFECIENT): cv.templatable(cv.float_), + cv.Required(CONF_COEFFICIENT): cv.templatable(cv.float_), } ), - _migrate_coeffecient, ) SENSOR_ONLY_SOURCE_SCHEMA = cv.Schema( diff --git a/esphome/components/const/__init__.py b/esphome/components/const/__init__.py index 6e98162baae..b54d797f096 100644 --- a/esphome/components/const/__init__.py +++ b/esphome/components/const/__init__.py @@ -1,6 +1,7 @@ """Constants used by esphome components.""" CODEOWNERS = ["@esphome/core"] +DOMAIN = "const" BYTE_ORDER_LITTLE = "little_endian" BYTE_ORDER_BIG = "big_endian" @@ -26,6 +27,7 @@ CONF_ENABLE_OTA_DOWNGRADE_PROTECTION = "enable_ota_downgrade_protection" CONF_ENABLED = "enabled" CONF_GYROSCOPE_ODR = "gyroscope_odr" CONF_GYROSCOPE_RANGE = "gyroscope_range" +CONF_HOLD_STATE = "hold_state" CONF_HOST = "host" CONF_HUMIDITY_SOURCE = "humidity_source" CONF_IAQ = "iaq" diff --git a/esphome/components/coolix/__init__.py b/esphome/components/coolix/__init__.py index e69de29bb2d..e916a942384 100644 --- a/esphome/components/coolix/__init__.py +++ b/esphome/components/coolix/__init__.py @@ -0,0 +1 @@ +DOMAIN = "coolix" diff --git a/esphome/components/coolix/coolix.cpp b/esphome/components/coolix/coolix.cpp index 56eb43cd82b..00a7b8a854a 100644 --- a/esphome/components/coolix/coolix.cpp +++ b/esphome/components/coolix/coolix.cpp @@ -4,7 +4,7 @@ namespace esphome::coolix { -static const char *const TAG = "coolix.climate"; +ESPHOME_LOG_TAG(TAG, "coolix.climate"); static const uint32_t COOLIX_OFF = 0xB27BE0; static const uint32_t COOLIX_SWING = 0xB26BE0; diff --git a/esphome/components/copy/__init__.py b/esphome/components/copy/__init__.py index 75948946501..917991b2fc9 100644 --- a/esphome/components/copy/__init__.py +++ b/esphome/components/copy/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg CODEOWNERS = ["@OttoWinter"] +DOMAIN = "copy" copy_ns = cg.esphome_ns.namespace("copy") diff --git a/esphome/components/copy/binary_sensor/copy_binary_sensor.cpp b/esphome/components/copy/binary_sensor/copy_binary_sensor.cpp index b084b0080a9..80093a4a965 100644 --- a/esphome/components/copy/binary_sensor/copy_binary_sensor.cpp +++ b/esphome/components/copy/binary_sensor/copy_binary_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::copy { -static const char *const TAG = "copy.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "copy.binary_sensor"); void CopyBinarySensor::setup() { source_->add_on_state_callback([this](bool value) { this->publish_state(value); }); diff --git a/esphome/components/copy/button/copy_button.cpp b/esphome/components/copy/button/copy_button.cpp index 297e1b0c948..c9441500a96 100644 --- a/esphome/components/copy/button/copy_button.cpp +++ b/esphome/components/copy/button/copy_button.cpp @@ -3,7 +3,7 @@ namespace esphome::copy { -static const char *const TAG = "copy.button"; +ESPHOME_LOG_TAG(TAG, "copy.button"); void CopyButton::dump_config() { LOG_BUTTON("", "Copy Button", this); } diff --git a/esphome/components/copy/cover/copy_cover.cpp b/esphome/components/copy/cover/copy_cover.cpp index 3412784cc77..71bdef667e9 100644 --- a/esphome/components/copy/cover/copy_cover.cpp +++ b/esphome/components/copy/cover/copy_cover.cpp @@ -3,7 +3,7 @@ namespace esphome::copy { -static const char *const TAG = "copy.cover"; +ESPHOME_LOG_TAG(TAG, "copy.cover"); void CopyCover::setup() { source_->add_on_state_callback([this]() { diff --git a/esphome/components/copy/fan/copy_fan.cpp b/esphome/components/copy/fan/copy_fan.cpp index 6eed787b058..1e721332be7 100644 --- a/esphome/components/copy/fan/copy_fan.cpp +++ b/esphome/components/copy/fan/copy_fan.cpp @@ -3,7 +3,7 @@ namespace esphome::copy { -static const char *const TAG = "copy.fan"; +ESPHOME_LOG_TAG(TAG, "copy.fan"); void CopyFan::setup() { // Copy preset modes once from source fan — stored on Fan base class diff --git a/esphome/components/copy/lock/copy_lock.cpp b/esphome/components/copy/lock/copy_lock.cpp index 5fdb9a757b8..d8b0b17f84b 100644 --- a/esphome/components/copy/lock/copy_lock.cpp +++ b/esphome/components/copy/lock/copy_lock.cpp @@ -3,7 +3,7 @@ namespace esphome::copy { -static const char *const TAG = "copy.lock"; +ESPHOME_LOG_TAG(TAG, "copy.lock"); void CopyLock::setup() { source_->add_on_state_callback([this](lock::LockState state) { this->publish_state(state); }); diff --git a/esphome/components/copy/number/copy_number.cpp b/esphome/components/copy/number/copy_number.cpp index a10bd209f4e..7ddc7da1704 100644 --- a/esphome/components/copy/number/copy_number.cpp +++ b/esphome/components/copy/number/copy_number.cpp @@ -3,7 +3,7 @@ namespace esphome::copy { -static const char *const TAG = "copy.number"; +ESPHOME_LOG_TAG(TAG, "copy.number"); void CopyNumber::setup() { source_->add_on_state_callback([this](float value) { this->publish_state(value); }); diff --git a/esphome/components/copy/select/copy_select.cpp b/esphome/components/copy/select/copy_select.cpp index e4f97f3c162..2315514ac54 100644 --- a/esphome/components/copy/select/copy_select.cpp +++ b/esphome/components/copy/select/copy_select.cpp @@ -3,7 +3,7 @@ namespace esphome::copy { -static const char *const TAG = "copy.select"; +ESPHOME_LOG_TAG(TAG, "copy.select"); void CopySelect::setup() { source_->add_on_state_callback([this](size_t index) { this->publish_state(index); }); diff --git a/esphome/components/copy/sensor/copy_sensor.cpp b/esphome/components/copy/sensor/copy_sensor.cpp index 9df23ab92d0..17eee92c12c 100644 --- a/esphome/components/copy/sensor/copy_sensor.cpp +++ b/esphome/components/copy/sensor/copy_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::copy { -static const char *const TAG = "copy.sensor"; +ESPHOME_LOG_TAG(TAG, "copy.sensor"); void CopySensor::setup() { source_->add_on_state_callback([this](float value) { this->publish_state(value); }); diff --git a/esphome/components/copy/switch/copy_switch.cpp b/esphome/components/copy/switch/copy_switch.cpp index 555f0030a5b..51850f898a0 100644 --- a/esphome/components/copy/switch/copy_switch.cpp +++ b/esphome/components/copy/switch/copy_switch.cpp @@ -3,7 +3,7 @@ namespace esphome::copy { -static const char *const TAG = "copy.switch"; +ESPHOME_LOG_TAG(TAG, "copy.switch"); void CopySwitch::setup() { source_->add_on_state_callback([this](float value) { this->publish_state(value); }); diff --git a/esphome/components/copy/text/copy_text.cpp b/esphome/components/copy/text/copy_text.cpp index 5ec3ba05b7c..fe8e1a010aa 100644 --- a/esphome/components/copy/text/copy_text.cpp +++ b/esphome/components/copy/text/copy_text.cpp @@ -3,7 +3,7 @@ namespace esphome::copy { -static const char *const TAG = "copy.text"; +ESPHOME_LOG_TAG(TAG, "copy.text"); void CopyText::setup() { source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); }); diff --git a/esphome/components/copy/text_sensor/copy_text_sensor.cpp b/esphome/components/copy/text_sensor/copy_text_sensor.cpp index 97182e8a618..96a0aa32b5f 100644 --- a/esphome/components/copy/text_sensor/copy_text_sensor.cpp +++ b/esphome/components/copy/text_sensor/copy_text_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::copy { -static const char *const TAG = "copy.text_sensor"; +ESPHOME_LOG_TAG(TAG, "copy.text_sensor"); void CopyTextSensor::setup() { source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); }); diff --git a/esphome/components/counter/__init__.py b/esphome/components/counter/__init__.py index c58ce8a01e8..a85a88d6e64 100644 --- a/esphome/components/counter/__init__.py +++ b/esphome/components/counter/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@clydebarrow"] +DOMAIN = "counter" diff --git a/esphome/components/counter/counter_sensor.cpp b/esphome/components/counter/counter_sensor.cpp index 22adb49cca7..c2f6688b2dd 100644 --- a/esphome/components/counter/counter_sensor.cpp +++ b/esphome/components/counter/counter_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::counter { -static const char *const TAG = "counter"; +ESPHOME_LOG_TAG(TAG, "counter"); void CounterSensor::setup() { if (this->restore_) { diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 0f5aa9b4503..c570f3d4ca2 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -62,6 +62,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_SHUTTER, DEVICE_CLASS_WINDOW, ] +DOMAIN = "cover" _LOGGER = logging.getLogger(__name__) diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index dc2db3bf329..30e7f70b02b 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -8,7 +8,7 @@ namespace esphome::cover { -static const char *const TAG = "cover"; +ESPHOME_LOG_TAG(TAG, "cover"); const LogString *cover_command_to_str(float pos) { if (pos == COVER_OPEN) { diff --git a/esphome/components/cs5460a/__init__.py b/esphome/components/cs5460a/__init__.py index e69de29bb2d..2ea88409a90 100644 --- a/esphome/components/cs5460a/__init__.py +++ b/esphome/components/cs5460a/__init__.py @@ -0,0 +1 @@ +DOMAIN = "cs5460a" diff --git a/esphome/components/cs5460a/cs5460a.cpp b/esphome/components/cs5460a/cs5460a.cpp index 1f9233841a6..7c4e4847911 100644 --- a/esphome/components/cs5460a/cs5460a.cpp +++ b/esphome/components/cs5460a/cs5460a.cpp @@ -3,7 +3,9 @@ namespace esphome::cs5460a { -static const char *const TAG = "cs5460a"; +ESPHOME_LOG_TAG(TAG, "cs5460a"); + +static constexpr uint32_t STATUS_CHECK_TIMEOUT_ID = 0; void CS5460AComponent::write_register_(enum CS5460ARegister addr, uint32_t value) { this->write_byte(CMD_WRITE | (addr << 1)); @@ -146,16 +148,16 @@ void CS5460AComponent::schedule_next_check_() { */ if (time_left > -15000) { time_left = 1000; - this->status_momentary_warning("warning", 1000); + this->status_momentary_warning(1000); } else { ESP_LOGCONFIG(TAG, "Device officially stuck, resetting"); - this->cancel_timeout("status-check"); + this->cancel_timeout(STATUS_CHECK_TIMEOUT_ID); this->hw_init_(); return; } } - this->set_timeout("status-check", time_left, [this]() { + this->set_timeout(STATUS_CHECK_TIMEOUT_ID, time_left, [this]() { if (!this->check_status_()) this->schedule_next_check_(); }); @@ -177,67 +179,67 @@ bool CS5460AComponent::check_status_() { if (status & (1 << 2)) { clear |= 1 << 2; ESP_LOGE(TAG, "Low supply detected"); - this->status_momentary_warning("warning", 500); + this->status_momentary_warning(500); } if (status & (1 << 3)) { clear |= 1 << 3; ESP_LOGE(TAG, "Modulator oscillation on current channel"); - this->status_momentary_warning("warning", 500); + this->status_momentary_warning(500); } if (status & (1 << 4)) { clear |= 1 << 4; ESP_LOGE(TAG, "Modulator oscillation on voltage channel"); - this->status_momentary_warning("warning", 500); + this->status_momentary_warning(500); } if (status & (1 << 5)) { clear |= 1 << 5; ESP_LOGE(TAG, "Watch-dog timeout"); - this->status_momentary_warning("warning", 500); + this->status_momentary_warning(500); } if (status & (1 << 11)) { clear |= 1 << 11; ESP_LOGE(TAG, "EOUT Energy Accumulation Register out of range"); - this->status_momentary_warning("warning", 500); + this->status_momentary_warning(500); } if (status & (1 << 12)) { clear |= 1 << 12; ESP_LOGE(TAG, "Energy out of range"); - this->status_momentary_warning("warning", 500); + this->status_momentary_warning(500); } if (status & (1 << 13)) { clear |= 1 << 13; ESP_LOGE(TAG, "RMS voltage out of range"); - this->status_momentary_warning("warning", 500); + this->status_momentary_warning(500); } if (status & (1 << 14)) { clear |= 1 << 14; ESP_LOGE(TAG, "RMS current out of range"); - this->status_momentary_warning("warning", 500); + this->status_momentary_warning(500); } if (status & (1 << 15)) { clear |= 1 << 15; ESP_LOGE(TAG, "Power calculation out of range"); - this->status_momentary_warning("warning", 500); + this->status_momentary_warning(500); } if (status & (1 << 16)) { clear |= 1 << 16; ESP_LOGE(TAG, "Voltage out of range"); - this->status_momentary_warning("warning", 500); + this->status_momentary_warning(500); } if (status & (1 << 17)) { clear |= 1 << 17; ESP_LOGE(TAG, "Current out of range"); - this->status_momentary_warning("warning", 500); + this->status_momentary_warning(500); } if (status & (1 << 19)) { diff --git a/esphome/components/cse7761/__init__.py b/esphome/components/cse7761/__init__.py index e69de29bb2d..3db7e9a1d48 100644 --- a/esphome/components/cse7761/__init__.py +++ b/esphome/components/cse7761/__init__.py @@ -0,0 +1 @@ +DOMAIN = "cse7761" diff --git a/esphome/components/cse7761/cse7761.cpp b/esphome/components/cse7761/cse7761.cpp index 103bc844528..695b7f33d6c 100644 --- a/esphome/components/cse7761/cse7761.cpp +++ b/esphome/components/cse7761/cse7761.cpp @@ -4,7 +4,7 @@ namespace esphome::cse7761 { -static const char *const TAG = "cse7761"; +ESPHOME_LOG_TAG(TAG, "cse7761"); /*********************************************************************************************\ * CSE7761 - Energy (Sonoff Dual R3 Pow v1.x) diff --git a/esphome/components/cse7766/__init__.py b/esphome/components/cse7766/__init__.py index e69de29bb2d..df45f6395c2 100644 --- a/esphome/components/cse7766/__init__.py +++ b/esphome/components/cse7766/__init__.py @@ -0,0 +1 @@ +DOMAIN = "cse7766" diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index 30f1b7a8679..92769011563 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -6,7 +6,7 @@ namespace esphome::cse7766 { -static const char *const TAG = "cse7766"; +ESPHOME_LOG_TAG(TAG, "cse7766"); void CSE7766Component::loop() { const uint32_t now = App.get_loop_component_start_time(); diff --git a/esphome/components/cst226/__init__.py b/esphome/components/cst226/__init__.py index 847e44dbdaf..5bccdc1cba0 100644 --- a/esphome/components/cst226/__init__.py +++ b/esphome/components/cst226/__init__.py @@ -2,5 +2,6 @@ import esphome.codegen as cg CODEOWNERS = ["@clydebarrow"] DEPENDENCIES = ["i2c"] +DOMAIN = "cst226" cst226_ns = cg.esphome_ns.namespace("cst226") diff --git a/esphome/components/cst226/binary_sensor/cstt6_button.cpp b/esphome/components/cst226/binary_sensor/cstt6_button.cpp index c14da2b176a..a755c3d09c8 100644 --- a/esphome/components/cst226/binary_sensor/cstt6_button.cpp +++ b/esphome/components/cst226/binary_sensor/cstt6_button.cpp @@ -3,7 +3,7 @@ namespace esphome::cst226 { -static const char *const TAG = "CST226.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "CST226.binary_sensor"); void CST226Button::setup() { this->parent_->register_button_listener(this); diff --git a/esphome/components/cst226/touchscreen/cst226_touchscreen.cpp b/esphome/components/cst226/touchscreen/cst226_touchscreen.cpp index 49d61cbfbca..849cff4a49e 100644 --- a/esphome/components/cst226/touchscreen/cst226_touchscreen.cpp +++ b/esphome/components/cst226/touchscreen/cst226_touchscreen.cpp @@ -2,7 +2,7 @@ namespace esphome::cst226 { -static const char *const TAG = "cst226.touchscreen"; +ESPHOME_LOG_TAG(TAG, "cst226.touchscreen"); void CST226Touchscreen::setup() { if (this->reset_pin_ != nullptr) { diff --git a/esphome/components/cst328/__init__.py b/esphome/components/cst328/__init__.py index 374df648982..6b103989694 100644 --- a/esphome/components/cst328/__init__.py +++ b/esphome/components/cst328/__init__.py @@ -2,5 +2,6 @@ import esphome.codegen as cg CODEOWNERS = ["@latonita"] DEPENDENCIES = ["i2c"] +DOMAIN = "cst328" cst328_ns = cg.esphome_ns.namespace("cst328") diff --git a/esphome/components/cst328/binary_sensor/cst328_button.cpp b/esphome/components/cst328/binary_sensor/cst328_button.cpp index b58f4b4b9f6..2010f097515 100644 --- a/esphome/components/cst328/binary_sensor/cst328_button.cpp +++ b/esphome/components/cst328/binary_sensor/cst328_button.cpp @@ -2,7 +2,7 @@ #include "esphome/core/log.h" namespace esphome::cst328 { -static const char *const TAG = "cst328.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "cst328.binary_sensor"); void CST328Button::setup() { this->parent_->register_button_listener(this); diff --git a/esphome/components/cst328/touchscreen/cst328_touchscreen.cpp b/esphome/components/cst328/touchscreen/cst328_touchscreen.cpp index 5e1a2ebf728..946eab1b177 100644 --- a/esphome/components/cst328/touchscreen/cst328_touchscreen.cpp +++ b/esphome/components/cst328/touchscreen/cst328_touchscreen.cpp @@ -3,7 +3,7 @@ namespace esphome::cst328 { -static const char *const TAG = "cst328.touchscreen"; +ESPHOME_LOG_TAG(TAG, "cst328.touchscreen"); static const uint32_t CST328_BEFORE_RESET_TIMEOUT = 50; // 50 ms from datasheet static const uint32_t CST328_TRANSITION_TIMEOUT = 300; // 200 ms from datasheet, but typically much less diff --git a/esphome/components/cst816/__init__.py b/esphome/components/cst816/__init__.py index 674a80b7c19..8d6e15abc58 100644 --- a/esphome/components/cst816/__init__.py +++ b/esphome/components/cst816/__init__.py @@ -2,5 +2,6 @@ import esphome.codegen as cg CODEOWNERS = ["@clydebarrow"] DEPENDENCIES = ["i2c"] +DOMAIN = "cst816" cst816_ns = cg.esphome_ns.namespace("cst816") diff --git a/esphome/components/cst9220/__init__.py b/esphome/components/cst9220/__init__.py index f97c8944efa..e3de3243e07 100644 --- a/esphome/components/cst9220/__init__.py +++ b/esphome/components/cst9220/__init__.py @@ -2,5 +2,6 @@ import esphome.codegen as cg CODEOWNERS = ["@clydebarrow"] DEPENDENCIES = ["i2c"] +DOMAIN = "cst9220" cst9220_ns = cg.esphome_ns.namespace("cst9220") diff --git a/esphome/components/ct_clamp/__init__.py b/esphome/components/ct_clamp/__init__.py index e69de29bb2d..f7ab2905b28 100644 --- a/esphome/components/ct_clamp/__init__.py +++ b/esphome/components/ct_clamp/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ct_clamp" diff --git a/esphome/components/ct_clamp/ct_clamp_sensor.cpp b/esphome/components/ct_clamp/ct_clamp_sensor.cpp index 613c3428be3..6488c468c34 100644 --- a/esphome/components/ct_clamp/ct_clamp_sensor.cpp +++ b/esphome/components/ct_clamp/ct_clamp_sensor.cpp @@ -6,7 +6,9 @@ namespace esphome::ct_clamp { -static const char *const TAG = "ct_clamp"; +ESPHOME_LOG_TAG(TAG, "ct_clamp"); + +static constexpr uint32_t READ_TIMEOUT_ID = 0; void CTClampSensor::dump_config() { LOG_SENSOR("", "CT Clamp Sensor", this); @@ -21,7 +23,7 @@ void CTClampSensor::update() { this->high_freq_.start(); // Set timeout for ending sampling phase - this->set_timeout("read", this->sample_duration_, [this]() { + this->set_timeout(READ_TIMEOUT_ID, this->sample_duration_, [this]() { this->is_sampling_ = false; this->high_freq_.stop(); diff --git a/esphome/components/current_based/__init__.py b/esphome/components/current_based/__init__.py index e7f41154b70..0ef871eeed7 100644 --- a/esphome/components/current_based/__init__.py +++ b/esphome/components/current_based/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@djwmarcx"] +DOMAIN = "current_based" diff --git a/esphome/components/current_based/current_based_cover.cpp b/esphome/components/current_based/current_based_cover.cpp index d15b310a37a..04743975715 100644 --- a/esphome/components/current_based/current_based_cover.cpp +++ b/esphome/components/current_based/current_based_cover.cpp @@ -6,7 +6,9 @@ namespace esphome::current_based { -static const char *const TAG = "current_based.cover"; +ESPHOME_LOG_TAG(TAG, "current_based.cover"); + +static constexpr uint32_t ROLLBACK_TIMEOUT_ID = 0; using namespace esphome::cover; @@ -73,7 +75,7 @@ void CurrentBasedCover::loop() { ESP_LOGD(TAG, "'%s' - Obstacle detected during opening.", this->name_.c_str()); this->direction_idle_(); if (this->obstacle_rollback_ != 0) { - this->set_timeout("rollback", 300, [this]() { + this->set_timeout(ROLLBACK_TIMEOUT_ID, 300, [this]() { ESP_LOGD(TAG, "'%s' - Rollback.", this->name_.c_str()); this->target_position_ = clamp(this->position - this->obstacle_rollback_, 0.0F, 1.0F); this->start_direction_(COVER_OPERATION_CLOSING); @@ -94,7 +96,7 @@ void CurrentBasedCover::loop() { ESP_LOGD(TAG, "'%s' - Obstacle detected during closing.", this->name_.c_str()); this->direction_idle_(); if (this->obstacle_rollback_ != 0) { - this->set_timeout("rollback", 300, [this]() { + this->set_timeout(ROLLBACK_TIMEOUT_ID, 300, [this]() { ESP_LOGD(TAG, "'%s' - Rollback.", this->name_.c_str()); this->target_position_ = clamp(this->position + this->obstacle_rollback_, 0.0F, 1.0F); this->start_direction_(COVER_OPERATION_OPENING); diff --git a/esphome/components/custom/__init__.py b/esphome/components/custom/__init__.py index e69de29bb2d..4eb4f32c12c 100644 --- a/esphome/components/custom/__init__.py +++ b/esphome/components/custom/__init__.py @@ -0,0 +1 @@ +DOMAIN = "custom" diff --git a/esphome/components/custom_component/__init__.py b/esphome/components/custom_component/__init__.py index 982153414d3..395f140d6c4 100644 --- a/esphome/components/custom_component/__init__.py +++ b/esphome/components/custom_component/__init__.py @@ -1,5 +1,6 @@ import esphome.config_validation as cv +DOMAIN = "custom_component" MULTI_CONF = True CONFIG_SCHEMA = cv.invalid( diff --git a/esphome/components/cwww/__init__.py b/esphome/components/cwww/__init__.py index e69de29bb2d..2da0e50fa0d 100644 --- a/esphome/components/cwww/__init__.py +++ b/esphome/components/cwww/__init__.py @@ -0,0 +1 @@ +DOMAIN = "cwww" diff --git a/esphome/components/d01/__init__.py b/esphome/components/d01/__init__.py index e69de29bb2d..45595aada31 100644 --- a/esphome/components/d01/__init__.py +++ b/esphome/components/d01/__init__.py @@ -0,0 +1 @@ +DOMAIN = "d01" diff --git a/esphome/components/d01/d01.cpp b/esphome/components/d01/d01.cpp index f7a0c08ec43..e15a5959cbc 100644 --- a/esphome/components/d01/d01.cpp +++ b/esphome/components/d01/d01.cpp @@ -17,7 +17,7 @@ namespace esphome::d01 { -static const char *const TAG = "d01"; +ESPHOME_LOG_TAG(TAG, "d01"); static const uint8_t D01_FRAME_HEADER = 0xA5; diff --git a/esphome/components/dac7678/__init__.py b/esphome/components/dac7678/__init__.py index 668cc87cece..4ee9f1f5413 100644 --- a/esphome/components/dac7678/__init__.py +++ b/esphome/components/dac7678/__init__.py @@ -8,6 +8,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["output"] CODEOWNERS = ["@NickB1"] DEPENDENCIES = ["i2c"] +DOMAIN = "dac7678" MULTI_CONF = True dac7678_ns = cg.esphome_ns.namespace("dac7678") diff --git a/esphome/components/dac7678/dac7678_output.cpp b/esphome/components/dac7678/dac7678_output.cpp index 15575583d97..d1a076733aa 100644 --- a/esphome/components/dac7678/dac7678_output.cpp +++ b/esphome/components/dac7678/dac7678_output.cpp @@ -5,7 +5,7 @@ namespace esphome::dac7678 { -static const char *const TAG = "dac7678"; +ESPHOME_LOG_TAG(TAG, "dac7678"); static const uint8_t DAC7678_REG_INPUT_N = 0x00; static const uint8_t DAC7678_REG_SELECT_UPDATE_N = 0x10; diff --git a/esphome/components/daikin/__init__.py b/esphome/components/daikin/__init__.py index e69de29bb2d..cc5617d4d53 100644 --- a/esphome/components/daikin/__init__.py +++ b/esphome/components/daikin/__init__.py @@ -0,0 +1 @@ +DOMAIN = "daikin" diff --git a/esphome/components/daikin/daikin.cpp b/esphome/components/daikin/daikin.cpp index d45586ba2dc..7a1310b9ac1 100644 --- a/esphome/components/daikin/daikin.cpp +++ b/esphome/components/daikin/daikin.cpp @@ -3,7 +3,7 @@ namespace esphome::daikin { -static const char *const TAG = "daikin.climate"; +ESPHOME_LOG_TAG(TAG, "daikin.climate"); void DaikinClimate::transmit_state() { uint8_t remote_state[35] = {0x11, 0xDA, 0x27, 0x00, 0xC5, 0x00, 0x00, 0xD7, 0x11, 0xDA, 0x27, 0x00, diff --git a/esphome/components/daikin_arc/__init__.py b/esphome/components/daikin_arc/__init__.py index f9164fb02b4..39d99e9da0e 100644 --- a/esphome/components/daikin_arc/__init__.py +++ b/esphome/components/daikin_arc/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@MagicBear"] +DOMAIN = "daikin_arc" diff --git a/esphome/components/daikin_arc/daikin_arc.cpp b/esphome/components/daikin_arc/daikin_arc.cpp index e31f72dfb96..e79135b1859 100644 --- a/esphome/components/daikin_arc/daikin_arc.cpp +++ b/esphome/components/daikin_arc/daikin_arc.cpp @@ -7,7 +7,7 @@ namespace esphome::daikin_arc { -static const char *const TAG = "daikin.climate"; +ESPHOME_LOG_TAG(TAG, "daikin.climate"); void DaikinArcClimate::setup() { climate_ir::ClimateIR::setup(); diff --git a/esphome/components/daikin_brc/__init__.py b/esphome/components/daikin_brc/__init__.py index 69002c015f7..c9352db0733 100644 --- a/esphome/components/daikin_brc/__init__.py +++ b/esphome/components/daikin_brc/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@hagak"] +DOMAIN = "daikin_brc" diff --git a/esphome/components/daikin_brc/daikin_brc.cpp b/esphome/components/daikin_brc/daikin_brc.cpp index 1b085013f1d..5d8ef52dc01 100644 --- a/esphome/components/daikin_brc/daikin_brc.cpp +++ b/esphome/components/daikin_brc/daikin_brc.cpp @@ -3,7 +3,7 @@ namespace esphome::daikin_brc { -static const char *const TAG = "daikin_brc.climate"; +ESPHOME_LOG_TAG(TAG, "daikin_brc.climate"); void DaikinBrcClimate::control(const climate::ClimateCall &call) { this->mode_button_ = 0x00; diff --git a/esphome/components/dallas/__init__.py b/esphome/components/dallas/__init__.py index 6c2a9d830e6..7d55ae91299 100644 --- a/esphome/components/dallas/__init__.py +++ b/esphome/components/dallas/__init__.py @@ -1,5 +1,6 @@ import esphome.config_validation as cv +DOMAIN = "dallas" MULTI_CONF = True CONFIG_SCHEMA = cv.invalid( diff --git a/esphome/components/dallas_temp/__init__.py b/esphome/components/dallas_temp/__init__.py index 3f73044ca8a..6a7af3ad4d9 100644 --- a/esphome/components/dallas_temp/__init__.py +++ b/esphome/components/dallas_temp/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@ssieb"] +DOMAIN = "dallas_temp" diff --git a/esphome/components/dallas_temp/dallas_temp.cpp b/esphome/components/dallas_temp/dallas_temp.cpp index c418362ced1..45101e4981f 100644 --- a/esphome/components/dallas_temp/dallas_temp.cpp +++ b/esphome/components/dallas_temp/dallas_temp.cpp @@ -3,7 +3,9 @@ namespace esphome::dallas_temp { -static const char *const TAG = "dallas.temp.sensor"; +ESPHOME_LOG_TAG(TAG, "dallas.temp.sensor"); + +static constexpr uint32_t CONVERSION_TIMEOUT_ID = 0; static const uint8_t DALLAS_MODEL_DS18S20 = 0x10; static const uint8_t DALLAS_MODEL_DS18B20 = 0x28; @@ -44,7 +46,7 @@ void DallasTemperatureSensor::update() { this->send_command_(DALLAS_COMMAND_START_CONVERSION); - this->set_timeout(this->get_address_name().c_str(), this->millis_to_wait_for_conversion_(), [this] { + this->set_timeout(CONVERSION_TIMEOUT_ID, this->millis_to_wait_for_conversion_(), [this] { if (!this->read_scratch_pad_() || !this->check_scratch_pad_()) { this->publish_state(NAN); return; diff --git a/esphome/components/daly_bms/__init__.py b/esphome/components/daly_bms/__init__.py index c0d7d0aa624..01d0eef857f 100644 --- a/esphome/components/daly_bms/__init__.py +++ b/esphome/components/daly_bms/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_ADDRESS, CONF_ID from esphome.types import ConfigType CODEOWNERS = ["@s1lvi0"] +DOMAIN = "daly_bms" MULTI_CONF = True DEPENDENCIES = ["uart"] diff --git a/esphome/components/daly_bms/daly_bms.cpp b/esphome/components/daly_bms/daly_bms.cpp index 45d4db49721..a486037266e 100644 --- a/esphome/components/daly_bms/daly_bms.cpp +++ b/esphome/components/daly_bms/daly_bms.cpp @@ -6,7 +6,7 @@ namespace esphome::daly_bms { -static const char *const TAG = "daly_bms"; +ESPHOME_LOG_TAG(TAG, "daly_bms"); static const uint8_t DALY_FRAME_SIZE = 13; static const uint8_t DALY_TEMPERATURE_OFFSET = 40; diff --git a/esphome/components/dashboard_import/__init__.py b/esphome/components/dashboard_import/__init__.py index 906e9762bfa..aca0901d2b8 100644 --- a/esphome/components/dashboard_import/__init__.py +++ b/esphome/components/dashboard_import/__init__.py @@ -22,6 +22,7 @@ dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import") # is enabled DEPENDENCIES = ["api"] CODEOWNERS = ["@esphome/core"] +DOMAIN = "dashboard_import" def validate_import_url(value: Any) -> str: diff --git a/esphome/components/datetime/__init__.py b/esphome/components/datetime/__init__.py index 3a33e5e0044..20fd258cb00 100644 --- a/esphome/components/datetime/__init__.py +++ b/esphome/components/datetime/__init__.py @@ -33,6 +33,7 @@ from esphome.cpp_generator import MockObj, MockObjClass from esphome.types import ConfigType, SafeExpType CODEOWNERS = ["@rfdarter", "@jesserockz"] +DOMAIN = "datetime" IS_PLATFORM_COMPONENT = True @@ -42,9 +43,6 @@ DateEntity = datetime_ns.class_("DateEntity", DateTimeBase) TimeEntity = datetime_ns.class_("TimeEntity", DateTimeBase) DateTimeEntity = datetime_ns.class_("DateTimeEntity", DateTimeBase) -DateTimeStateTrigger = datetime_ns.class_( - "DateTimeStateTrigger", automation.Trigger.template(cg.ESPTime) -) OnTimeTrigger = datetime_ns.class_( "OnTimeTrigger", automation.Trigger, cg.Component, cg.Parented.template(TimeEntity) @@ -74,11 +72,7 @@ def _validate_time_present(config: ConfigType) -> ConfigType: _DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( cv.Schema( { - cv.Optional(CONF_ON_VALUE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger), - } - ), + cv.Optional(CONF_ON_VALUE): automation.validate_automation({}), cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), } ) @@ -143,9 +137,16 @@ async def setup_datetime_core_(var: MockObj, config: ConfigType) -> None: await mqtt.register_mqtt_component(mqtt_, config) if web_server_config := config.get(CONF_WEB_SERVER): await web_server.add_entity_config(var, web_server_config) + # The state callback carries nothing; the automation gets the value as an ESPTime. for conf in config.get(CONF_ON_VALUE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf) + await automation.build_callback_automation( + var, + "add_on_state_callback", + [(cg.ESPTime, "x")], + conf, + params=[], + forward=[automation.parent_ref(var).state_as_esptime()], + ) if CONF_TIME_ID in config: rtc = await cg.get_variable(config[CONF_TIME_ID]) diff --git a/esphome/components/datetime/date_entity.cpp b/esphome/components/datetime/date_entity.cpp index b99b89259fb..795d54afd90 100644 --- a/esphome/components/datetime/date_entity.cpp +++ b/esphome/components/datetime/date_entity.cpp @@ -7,7 +7,7 @@ namespace esphome::datetime { -static const char *const TAG = "datetime.date_entity"; +ESPHOME_LOG_TAG(TAG, "datetime.date_entity"); void DateEntity::publish_state() { if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) { diff --git a/esphome/components/datetime/datetime_base.h b/esphome/components/datetime/datetime_base.h index f99debb692d..fdbb1930068 100644 --- a/esphome/components/datetime/datetime_base.h +++ b/esphome/components/datetime/datetime_base.h @@ -31,14 +31,4 @@ class DateTimeBase : public EntityBase { #endif }; -class DateTimeStateTrigger final : public Trigger { - public: - explicit DateTimeStateTrigger(DateTimeBase *parent) : parent_(parent) { - parent->add_on_state_callback([this]() { this->trigger(this->parent_->state_as_esptime()); }); - } - - protected: - DateTimeBase *parent_; -}; - } // namespace esphome::datetime diff --git a/esphome/components/datetime/datetime_entity.cpp b/esphome/components/datetime/datetime_entity.cpp index 8f180fd081e..6aa14163430 100644 --- a/esphome/components/datetime/datetime_entity.cpp +++ b/esphome/components/datetime/datetime_entity.cpp @@ -7,7 +7,7 @@ namespace esphome::datetime { -static const char *const TAG = "datetime.datetime_entity"; +ESPHOME_LOG_TAG(TAG, "datetime.datetime_entity"); void DateTimeEntity::publish_state() { if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) { diff --git a/esphome/components/datetime/time_entity.cpp b/esphome/components/datetime/time_entity.cpp index da4c9eb31e4..ea1a97256cd 100644 --- a/esphome/components/datetime/time_entity.cpp +++ b/esphome/components/datetime/time_entity.cpp @@ -7,7 +7,7 @@ namespace esphome::datetime { -static const char *const TAG = "datetime.time_entity"; +ESPHOME_LOG_TAG(TAG, "datetime.time_entity"); void TimeEntity::publish_state() { if (this->hour_ > 23) { diff --git a/esphome/components/debug/__init__.py b/esphome/components/debug/__init__.py index 898d7d6c0e7..889cfaaa682 100644 --- a/esphome/components/debug/__init__.py +++ b/esphome/components/debug/__init__.py @@ -17,6 +17,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["logger"] +DOMAIN = "debug" CONF_DEBUG_ID = "debug_id" debug_ns = cg.esphome_ns.namespace("debug") diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 97f4522c623..97f8550c5bc 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -11,7 +11,7 @@ namespace esphome::debug { -static const char *const TAG = "debug"; +ESPHOME_LOG_TAG(TAG, "debug"); void DebugComponent::dump_config() { ESP_LOGCONFIG(TAG, "Debug component:"); @@ -58,7 +58,7 @@ void DebugComponent::loop() { if (new_free_heap < this->free_heap_ / 2) { this->free_heap_ = new_free_heap; ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_); - this->status_momentary_warning("heap", 1000); + this->status_momentary_warning(1000); } #ifdef USE_SENSOR diff --git a/esphome/components/debug/debug_esp32.cpp b/esphome/components/debug/debug_esp32.cpp index 8e1a67224eb..1ffc0a2eb60 100644 --- a/esphome/components/debug/debug_esp32.cpp +++ b/esphome/components/debug/debug_esp32.cpp @@ -19,7 +19,7 @@ namespace esphome::debug { -static const char *const TAG = "debug"; +ESPHOME_LOG_TAG(TAG, "debug"); // index by values returned by esp_reset_reason diff --git a/esphome/components/debug/debug_esp8266.cpp b/esphome/components/debug/debug_esp8266.cpp index acce28818c7..5b62eb61907 100644 --- a/esphome/components/debug/debug_esp8266.cpp +++ b/esphome/components/debug/debug_esp8266.cpp @@ -17,7 +17,7 @@ extern const char *core_release; namespace esphome::debug { -static const char *const TAG = "debug"; +ESPHOME_LOG_TAG(TAG, "debug"); // PROGMEM string table for reset reasons, indexed by reason code (0-6), with "Unknown" as fallback // clang-format off diff --git a/esphome/components/debug/debug_libretiny.cpp b/esphome/components/debug/debug_libretiny.cpp index 55b29310a14..a1166758e26 100644 --- a/esphome/components/debug/debug_libretiny.cpp +++ b/esphome/components/debug/debug_libretiny.cpp @@ -4,7 +4,7 @@ namespace esphome::debug { -static const char *const TAG = "debug"; +ESPHOME_LOG_TAG(TAG, "debug"); const char *DebugComponent::get_reset_reason_(std::span buffer) { // Return the static string directly diff --git a/esphome/components/debug/debug_rp2.cpp b/esphome/components/debug/debug_rp2.cpp index 4ace4be0a3d..7cedac287c0 100644 --- a/esphome/components/debug/debug_rp2.cpp +++ b/esphome/components/debug/debug_rp2.cpp @@ -15,7 +15,7 @@ #endif namespace esphome::debug { -static const char *const TAG = "debug"; +ESPHOME_LOG_TAG(TAG, "debug"); const char *DebugComponent::get_reset_reason_(std::span buffer) { char *buf = buffer.data(); diff --git a/esphome/components/debug/debug_zephyr.cpp b/esphome/components/debug/debug_zephyr.cpp index e23a0f668a0..f3a5f3d770d 100644 --- a/esphome/components/debug/debug_zephyr.cpp +++ b/esphome/components/debug/debug_zephyr.cpp @@ -1,6 +1,8 @@ #include "debug_component.h" #ifdef USE_ZEPHYR +#include #include +#include "esphome/core/alloc_helpers.h" #include "esphome/core/log.h" #include #include @@ -12,7 +14,7 @@ namespace esphome::debug { -static const char *const TAG = "debug"; +ESPHOME_LOG_TAG(TAG, "debug"); constexpr std::uintptr_t MBR_PARAM_PAGE_ADDR = 0xFFC; constexpr std::uintptr_t MBR_BOOTLOADER_ADDR = 0xFF8; @@ -399,18 +401,17 @@ size_t DebugComponent::get_device_info_(std::span #endif } #endif - auto uicr = [](volatile uint32_t *data, uint8_t size) { - std::string res; + // 13 words of 8 hex digits, separated by spaces + char uicr_buf[13 * 9]; + auto uicr = [&uicr_buf](volatile uint32_t *data, uint8_t size) { + size_t len = 0; for (size_t i = 0; i < size; i++) { - if (i > 0) { - res += ' '; - } - res += format_hex_pretty(data[i], '\0', false); + len = buf_append_printf(uicr_buf, sizeof(uicr_buf), len, i > 0 ? " %08" PRIX32 : "%08" PRIX32, data[i]); } - return res; + return uicr_buf; }; - ESP_LOGD(TAG, " NRFFW %s", uicr(NRF_UICR->NRFFW, 13).c_str()); - ESP_LOGD(TAG, " NRFHW %s", uicr(NRF_UICR->NRFHW, 12).c_str()); + ESP_LOGD(TAG, " NRFFW %s", uicr(NRF_UICR->NRFFW, 13)); + ESP_LOGD(TAG, " NRFHW %s", uicr(NRF_UICR->NRFHW, 12)); #ifdef ESPHOME_LOG_HAS_VERBOSE log_peripherals_info(); #endif diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 06286933b90..2b820c9be6c 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -491,6 +491,7 @@ for _name, _call in ( ) +DOMAIN = "deep_sleep" FILTER_SOURCE_FILES = filter_source_files_from_platform( { "deep_sleep_esp32.cpp": { diff --git a/esphome/components/deep_sleep/deep_sleep_bk72xx.cpp b/esphome/components/deep_sleep/deep_sleep_bk72xx.cpp index a955095875b..47ebd3641e4 100644 --- a/esphome/components/deep_sleep/deep_sleep_bk72xx.cpp +++ b/esphome/components/deep_sleep/deep_sleep_bk72xx.cpp @@ -5,7 +5,7 @@ namespace esphome::deep_sleep { -static const char *const TAG = "deep_sleep"; +ESPHOME_LOG_TAG(TAG, "deep_sleep"); #ifdef USE_DEEP_SLEEP_ON_WAKE WakeupCause get_wakeup_cause() { diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp index d33102bf4fa..f363a0f0acb 100644 --- a/esphome/components/deep_sleep/deep_sleep_component.cpp +++ b/esphome/components/deep_sleep/deep_sleep_component.cpp @@ -4,7 +4,7 @@ namespace esphome::deep_sleep { -static const char *const TAG = "deep_sleep"; +ESPHOME_LOG_TAG(TAG, "deep_sleep"); // 5 seconds for deep sleep to ensure clean disconnect from Home Assistant static const uint32_t TEARDOWN_TIMEOUT_DEEP_SLEEP_MS = 5000; diff --git a/esphome/components/deep_sleep/deep_sleep_esp32.cpp b/esphome/components/deep_sleep/deep_sleep_esp32.cpp index 20297028b2c..7cf90bc33e3 100644 --- a/esphome/components/deep_sleep/deep_sleep_esp32.cpp +++ b/esphome/components/deep_sleep/deep_sleep_esp32.cpp @@ -28,7 +28,7 @@ namespace esphome::deep_sleep { // - Touch: Touch pad wakeup (esp_sleep_enable_touchpad_wakeup) // - GPIO wakeup: GPIO wakeup for RTC pins -static const char *const TAG = "deep_sleep"; +ESPHOME_LOG_TAG(TAG, "deep_sleep"); #ifdef USE_DEEP_SLEEP_ON_WAKE WakeupCause get_wakeup_cause() { @@ -111,7 +111,17 @@ void DeepSleepComponent::deep_sleep_() { if (this->sleep_duration_.has_value()) esp_sleep_enable_timer_wakeup(*this->sleep_duration_); - // Single pin wakeup (ext0) - ESP32, S2, S3 only +#ifndef SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP +#ifdef USE_GPIO_HOLD + // Variants without per-pin deep sleep hold need the global enable + gpio_deep_sleep_hold_en(); +#else + if (this->wakeup_pin_ != nullptr) + gpio_deep_sleep_hold_en(); +#endif +#endif + + // Single pin wakeup (ext0) - ESP32, S2, S3 only #if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3) && \ !defined(USE_ESP32_VARIANT_ESP32C5) && !defined(USE_ESP32_VARIANT_ESP32C6) && \ !defined(USE_ESP32_VARIANT_ESP32C61) && !defined(USE_ESP32_VARIANT_ESP32H2) @@ -124,11 +134,6 @@ void DeepSleepComponent::deep_sleep_() { } gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT); gpio_hold_en(gpio_pin); -#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP - // Some ESP32 variants support holding a single GPIO during deep sleep without this function - // For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep - gpio_deep_sleep_hold_en(); -#endif bool level = !this->wakeup_pin_->is_inverted(); if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) { level = !level; diff --git a/esphome/components/deep_sleep/deep_sleep_esp8266.cpp b/esphome/components/deep_sleep/deep_sleep_esp8266.cpp index 2b98f4b8555..408d5a6fe60 100644 --- a/esphome/components/deep_sleep/deep_sleep_esp8266.cpp +++ b/esphome/components/deep_sleep/deep_sleep_esp8266.cpp @@ -11,7 +11,7 @@ extern "C" { namespace esphome::deep_sleep { -static const char *const TAG = "deep_sleep"; +ESPHOME_LOG_TAG(TAG, "deep_sleep"); #ifdef USE_DEEP_SLEEP_ON_WAKE WakeupCause get_wakeup_cause() { diff --git a/esphome/components/deep_sleep/deep_sleep_zephyr.cpp b/esphome/components/deep_sleep/deep_sleep_zephyr.cpp index cadf7bf42d9..aff5c0e9c7d 100644 --- a/esphome/components/deep_sleep/deep_sleep_zephyr.cpp +++ b/esphome/components/deep_sleep/deep_sleep_zephyr.cpp @@ -8,7 +8,7 @@ namespace esphome::deep_sleep { -static const char *const TAG = "deep_sleep"; +ESPHOME_LOG_TAG(TAG, "deep_sleep"); // The Zephyr watchdog has a short window (2s, or 10s with Zigbee) and // WDT_OPT_PAUSE_IN_SLEEP only pauses it during true hardware sleep — not while a diff --git a/esphome/components/delonghi/__init__.py b/esphome/components/delonghi/__init__.py index 0a81eb2da75..ba66077c840 100644 --- a/esphome/components/delonghi/__init__.py +++ b/esphome/components/delonghi/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@grob6000"] +DOMAIN = "delonghi" diff --git a/esphome/components/delonghi/delonghi.cpp b/esphome/components/delonghi/delonghi.cpp index fab2d68347b..eec4d7f55b1 100644 --- a/esphome/components/delonghi/delonghi.cpp +++ b/esphome/components/delonghi/delonghi.cpp @@ -3,7 +3,7 @@ namespace esphome::delonghi { -static const char *const TAG = "delonghi.climate"; +ESPHOME_LOG_TAG(TAG, "delonghi.climate"); void DelonghiClimate::transmit_state() { uint8_t remote_state[DELONGHI_STATE_FRAME_SIZE] = {0}; diff --git a/esphome/components/demo/__init__.py b/esphome/components/demo/__init__.py index 75feaa65af1..8f2d5ded203 100644 --- a/esphome/components/demo/__init__.py +++ b/esphome/components/demo/__init__.py @@ -76,6 +76,7 @@ AUTO_LOAD = [ "text_sensor", "valve", ] +DOMAIN = "demo" demo_ns = cg.esphome_ns.namespace("demo") DemoAlarmControlPanel = demo_ns.class_( diff --git a/esphome/components/demo/demo_alarm_control_panel.h b/esphome/components/demo/demo_alarm_control_panel.h index e85d2a17ba6..79cfc8f8ca4 100644 --- a/esphome/components/demo/demo_alarm_control_panel.h +++ b/esphome/components/demo/demo_alarm_control_panel.h @@ -33,7 +33,7 @@ class DemoAlarmControlPanel final : public AlarmControlPanel, public Component { case ACP_STATE_ARMED_AWAY: if (this->get_requires_code_to_arm()) { if (!code.has_value() || *code != "1234") { - this->status_momentary_error("invalid_code", 5000); + this->status_momentary_error(5000); return; } } @@ -42,7 +42,7 @@ class DemoAlarmControlPanel final : public AlarmControlPanel, public Component { case ACP_STATE_DISARMED: if (this->get_requires_code()) { if (!code.has_value() || *code != "1234") { - this->status_momentary_error("invalid_code", 5000); + this->status_momentary_error(5000); return; } } diff --git a/esphome/components/demo/demo_cover.h b/esphome/components/demo/demo_cover.h index aa12c885f43..eeb188a4631 100644 --- a/esphome/components/demo/demo_cover.h +++ b/esphome/components/demo/demo_cover.h @@ -36,6 +36,8 @@ class DemoCover final : public cover::Cover, public Component { } protected: + static constexpr uint32_t MOVE_TIMEOUT_ID = 0; + void control(const cover::CoverCall &call) override { auto pos = call.get_position(); if (pos.has_value()) { @@ -43,7 +45,7 @@ class DemoCover final : public cover::Cover, public Component { this->current_operation = target > this->position ? cover::COVER_OPERATION_OPENING : cover::COVER_OPERATION_CLOSING; - this->set_timeout("move", 2000, [this, target]() { + this->set_timeout(MOVE_TIMEOUT_ID, 2000, [this, target]() { this->current_operation = cover::COVER_OPERATION_IDLE; this->position = target; this->publish_state(); @@ -54,7 +56,7 @@ class DemoCover final : public cover::Cover, public Component { this->tilt = *tilt; } if (call.get_stop()) { - this->cancel_timeout("move"); + this->cancel_timeout(MOVE_TIMEOUT_ID); } this->publish_state(); diff --git a/esphome/components/dew_point/__init__.py b/esphome/components/dew_point/__init__.py index 3b852436c3c..ab61f55f0b2 100644 --- a/esphome/components/dew_point/__init__.py +++ b/esphome/components/dew_point/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@CFlix"] +DOMAIN = "dew_point" diff --git a/esphome/components/dew_point/dew_point.cpp b/esphome/components/dew_point/dew_point.cpp index 04ac305e3d2..7012bcbb755 100644 --- a/esphome/components/dew_point/dew_point.cpp +++ b/esphome/components/dew_point/dew_point.cpp @@ -3,7 +3,7 @@ namespace esphome::dew_point { -static const char *const TAG = "dew_point.sensor"; +ESPHOME_LOG_TAG(TAG, "dew_point.sensor"); void DewPointComponent::setup() { // Register callbacks for sensor updates diff --git a/esphome/components/dfplayer/__init__.py b/esphome/components/dfplayer/__init__.py index 296afcd3183..d4ec9d6590a 100644 --- a/esphome/components/dfplayer/__init__.py +++ b/esphome/components/dfplayer/__init__.py @@ -9,6 +9,7 @@ from esphome.const import CONF_DEVICE, CONF_FILE, CONF_ID, CONF_VOLUME DEPENDENCIES = ["uart"] CODEOWNERS = ["@glmnet"] +DOMAIN = "dfplayer" dfplayer_ns = cg.esphome_ns.namespace("dfplayer") DFPlayer = dfplayer_ns.class_("DFPlayer", cg.Component) diff --git a/esphome/components/dfplayer/dfplayer.cpp b/esphome/components/dfplayer/dfplayer.cpp index 1195f595c8c..c45e895b385 100644 --- a/esphome/components/dfplayer/dfplayer.cpp +++ b/esphome/components/dfplayer/dfplayer.cpp @@ -4,7 +4,7 @@ namespace esphome::dfplayer { -static const char *const TAG = "dfplayer"; +ESPHOME_LOG_TAG(TAG, "dfplayer"); void DFPlayer::next() { this->ack_set_is_playing_ = true; diff --git a/esphome/components/dfrobot_sen0395/__init__.py b/esphome/components/dfrobot_sen0395/__init__.py index 6334e10ac5f..d4e7aed91d8 100644 --- a/esphome/components/dfrobot_sen0395/__init__.py +++ b/esphome/components/dfrobot_sen0395/__init__.py @@ -12,6 +12,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@niklasweber"] DEPENDENCIES = ["uart"] +DOMAIN = "dfrobot_sen0395" MULTI_CONF = True dfrobot_sen0395_ns = cg.esphome_ns.namespace("dfrobot_sen0395") diff --git a/esphome/components/dfrobot_sen0395/commands.cpp b/esphome/components/dfrobot_sen0395/commands.cpp index 570bfef9439..f5aee549435 100644 --- a/esphome/components/dfrobot_sen0395/commands.cpp +++ b/esphome/components/dfrobot_sen0395/commands.cpp @@ -8,7 +8,7 @@ namespace esphome::dfrobot_sen0395 { -static const char *const TAG = "dfrobot_sen0395.commands"; +ESPHOME_LOG_TAG(TAG, "dfrobot_sen0395.commands"); uint8_t Command::execute(DfrobotSen0395Component *parent) { this->parent_ = parent; diff --git a/esphome/components/dfrobot_sen0395/dfrobot_sen0395.cpp b/esphome/components/dfrobot_sen0395/dfrobot_sen0395.cpp index 42cfd5dff68..9c092982e55 100644 --- a/esphome/components/dfrobot_sen0395/dfrobot_sen0395.cpp +++ b/esphome/components/dfrobot_sen0395/dfrobot_sen0395.cpp @@ -5,7 +5,7 @@ namespace esphome::dfrobot_sen0395 { -static const char *const TAG = "dfrobot_sen0395"; +ESPHOME_LOG_TAG(TAG, "dfrobot_sen0395"); const char ASCII_CR = 0x0D; const char ASCII_LF = 0x0A; diff --git a/esphome/components/dht/__init__.py b/esphome/components/dht/__init__.py index 71a87b6ae50..9def4211030 100644 --- a/esphome/components/dht/__init__.py +++ b/esphome/components/dht/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@OttoWinter"] +DOMAIN = "dht" diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index 2196f3a982c..12b8b0feba4 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -4,7 +4,7 @@ namespace esphome::dht { -static const char *const TAG = "dht"; +ESPHOME_LOG_TAG(TAG, "dht"); void DHT::setup() { this->t_pin_->digital_write(true); diff --git a/esphome/components/dht12/__init__.py b/esphome/components/dht12/__init__.py index e69de29bb2d..0078da33000 100644 --- a/esphome/components/dht12/__init__.py +++ b/esphome/components/dht12/__init__.py @@ -0,0 +1 @@ +DOMAIN = "dht12" diff --git a/esphome/components/dht12/dht12.cpp b/esphome/components/dht12/dht12.cpp index 78f9140929b..e099b65d5b7 100644 --- a/esphome/components/dht12/dht12.cpp +++ b/esphome/components/dht12/dht12.cpp @@ -7,7 +7,7 @@ namespace esphome::dht12 { -static const char *const TAG = "dht12"; +ESPHOME_LOG_TAG(TAG, "dht12"); void DHT12Component::update() { uint8_t data[5]; diff --git a/esphome/components/display/display.cpp b/esphome/components/display/display.cpp index 66fadf12ece..49cd2fc7c66 100644 --- a/esphome/components/display/display.cpp +++ b/esphome/components/display/display.cpp @@ -8,7 +8,7 @@ #include "esphome/core/log.h" namespace esphome::display { -static const char *const TAG = "display"; +ESPHOME_LOG_TAG(TAG, "display"); // COLOR_OFF and COLOR_ON are now inline constexpr in display.h diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index d564ea67bd5..45580927027 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -6,7 +6,7 @@ namespace esphome::display { -static const char *const TAG = "display"; +ESPHOME_LOG_TAG(TAG, "display"); void DisplayBuffer::init_internal_(uint32_t buffer_length) { RAMAllocator allocator; diff --git a/esphome/components/display/rect.cpp b/esphome/components/display/rect.cpp index 3ecf6d1cf15..8eb9288a5ad 100644 --- a/esphome/components/display/rect.cpp +++ b/esphome/components/display/rect.cpp @@ -4,7 +4,7 @@ namespace esphome::display { -static const char *const TAG = "display"; +ESPHOME_LOG_TAG(TAG, "display"); void Rect::expand(int16_t horizontal, int16_t vertical) { if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) { diff --git a/esphome/components/display_menu_base/__init__.py b/esphome/components/display_menu_base/__init__.py index bb493ae8d33..791242cda12 100644 --- a/esphome/components/display_menu_base/__init__.py +++ b/esphome/components/display_menu_base/__init__.py @@ -20,11 +20,13 @@ from esphome.const import ( CONF_ON_VALUE, CONF_SWITCH, CONF_TEXT, - CONF_TRIGGER_ID, CONF_TYPE, ) +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType CODEOWNERS = ["@numo68"] +DOMAIN = "display_menu_base" display_menu_base_ns = cg.esphome_ns.namespace("display_menu_base") @@ -85,26 +87,6 @@ MENU_MODES = { CONF_JOYSTICK: MenuMode.MENU_MODE_JOYSTICK, } -DisplayMenuOnEnterTrigger = display_menu_base_ns.class_( - "DisplayMenuOnEnterTrigger", automation.Trigger -) - -DisplayMenuOnLeaveTrigger = display_menu_base_ns.class_( - "DisplayMenuOnLeaveTrigger", automation.Trigger -) - -DisplayMenuOnValueTrigger = display_menu_base_ns.class_( - "DisplayMenuOnValueTrigger", automation.Trigger -) - -DisplayMenuOnNextTrigger = display_menu_base_ns.class_( - "DisplayMenuOnNextTrigger", automation.Trigger -) - -DisplayMenuOnPrevTrigger = display_menu_base_ns.class_( - "DisplayMenuOnPrevTrigger", automation.Trigger -) - def validate_format(format): if re.search(r"^%[+-]*(\d+)?(\.\d+)?[fg]$", format) is None: @@ -128,44 +110,20 @@ MENU_ITEM_COMMON_SCHEMA = cv.Schema( MENU_ITEM_ENTER_LEAVE_SCHEMA = MENU_ITEM_COMMON_SCHEMA.extend( { - cv.Optional(CONF_ON_ENTER): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - DisplayMenuOnEnterTrigger - ), - } - ), - cv.Optional(CONF_ON_LEAVE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - DisplayMenuOnLeaveTrigger - ), - } - ), + cv.Optional(CONF_ON_ENTER): automation.validate_automation({}), + cv.Optional(CONF_ON_LEAVE): automation.validate_automation({}), } ) MENU_ITEM_VALUE_SCHEMA = MENU_ITEM_COMMON_SCHEMA.extend( { - cv.Optional(CONF_ON_VALUE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - DisplayMenuOnValueTrigger - ), - } - ), + cv.Optional(CONF_ON_VALUE): automation.validate_automation({}), } ) MENU_ITEM_ENTER_LEAVE_VALUE_SCHEMA = MENU_ITEM_ENTER_LEAVE_SCHEMA.extend( { - cv.Optional(CONF_ON_VALUE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - DisplayMenuOnValueTrigger - ), - } - ), + cv.Optional(CONF_ON_VALUE): automation.validate_automation({}), } ) @@ -229,20 +187,8 @@ MENU_ITEM_SCHEMA = cv.typed_schema( cv.GenerateID(CONF_ID): cv.declare_id(MenuItemCustom), cv.Optional(CONF_IMMEDIATE_EDIT, default=False): cv.boolean, cv.Optional(CONF_VALUE_LAMBDA): cv.returning_lambda, - cv.Optional(CONF_ON_NEXT): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - DisplayMenuOnNextTrigger - ), - } - ), - cv.Optional(CONF_ON_PREV): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - DisplayMenuOnPrevTrigger - ), - } - ), + cv.Optional(CONF_ON_NEXT): automation.validate_automation({}), + cv.Optional(CONF_ON_PREV): automation.validate_automation({}), } ), }, @@ -255,20 +201,8 @@ DISPLAY_MENU_BASE_SCHEMA = cv.Schema( cv.Optional(CONF_ACTIVE, default=True): cv.boolean, cv.GenerateID(CONF_ROOT_ITEM_ID): cv.declare_id(MenuItemMenu), cv.Optional(CONF_MODE, default=CONF_ROTARY): cv.enum(MENU_MODES), - cv.Optional(CONF_ON_ENTER): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - DisplayMenuOnEnterTrigger - ), - } - ), - cv.Optional(CONF_ON_LEAVE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - DisplayMenuOnLeaveTrigger - ), - } - ), + cv.Optional(CONF_ON_ENTER): automation.validate_automation({}), + cv.Optional(CONF_ON_LEAVE): automation.validate_automation({}), cv.Required(CONF_ITEMS): cv.All( cv.ensure_list(MENU_ITEM_SCHEMA), cv.Length(min=1) ), @@ -301,6 +235,24 @@ automation.register_apply_condition( ) +# Each item callback carries nothing; the automation receives the item itself. +_ITEM_CALLBACKS = ( + (CONF_ON_ENTER, "add_on_enter_callback"), + (CONF_ON_LEAVE, "add_on_leave_callback"), + (CONF_ON_VALUE, "add_on_value_callback"), + (CONF_ON_NEXT, "add_on_next_callback"), + (CONF_ON_PREV, "add_on_prev_callback"), +) + + +async def _build_item_automations(item: MockObj, config: ConfigType) -> None: + for conf_key, callback_method in _ITEM_CALLBACKS: + for conf in config.get(conf_key, []): + await automation.build_parent_callback_automation( + item, callback_method, (MenuItemConstPtr, "it"), conf + ) + + async def menu_item_to_code(menu, config, parent): if config[CONF_TYPE] in MENU_ITEMS_WITH_SPECIALIZED_CLASSES: item = cg.new_Pvariable(config[CONF_ID]) @@ -339,21 +291,7 @@ async def menu_item_to_code(menu, config, parent): cg.add(item.set_switch_variable(var)) cg.add(item.set_on_text(config[CONF_ON_TEXT])) cg.add(item.set_off_text(config[CONF_OFF_TEXT])) - for conf in config.get(CONF_ON_ENTER, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item) - await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf) - for conf in config.get(CONF_ON_LEAVE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item) - await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf) - for conf in config.get(CONF_ON_VALUE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item) - await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf) - for conf in config.get(CONF_ON_NEXT, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item) - await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf) - for conf in config.get(CONF_ON_PREV, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], item) - await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf) + await _build_item_automations(item, config) async def display_menu_to_code(menu, config): @@ -363,9 +301,4 @@ async def display_menu_to_code(menu, config): cg.add(menu.set_mode(config[CONF_MODE])) for c in config[CONF_ITEMS]: await menu_item_to_code(menu, c, root_item) - for conf in config.get(CONF_ON_ENTER, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], root_item) - await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf) - for conf in config.get(CONF_ON_LEAVE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], root_item) - await automation.build_automation(trigger, [(MenuItemConstPtr, "it")], conf) + await _build_item_automations(root_item, config) diff --git a/esphome/components/display_menu_base/automation.h b/esphome/components/display_menu_base/automation.h deleted file mode 100644 index 477a971d479..00000000000 --- a/esphome/components/display_menu_base/automation.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include "esphome/core/automation.h" -#include "display_menu_base.h" - -namespace esphome::display_menu_base { - -class DisplayMenuOnEnterTrigger final : public Trigger { - public: - explicit DisplayMenuOnEnterTrigger(MenuItem *parent) : parent_(parent) { - parent->add_on_enter_callback([this]() { this->trigger(this->parent_); }); - } - - protected: - MenuItem *parent_; -}; - -class DisplayMenuOnLeaveTrigger final : public Trigger { - public: - explicit DisplayMenuOnLeaveTrigger(MenuItem *parent) : parent_(parent) { - parent->add_on_leave_callback([this]() { this->trigger(this->parent_); }); - } - - protected: - MenuItem *parent_; -}; - -class DisplayMenuOnValueTrigger final : public Trigger { - public: - explicit DisplayMenuOnValueTrigger(MenuItem *parent) : parent_(parent) { - parent->add_on_value_callback([this]() { this->trigger(this->parent_); }); - } - - protected: - MenuItem *parent_; -}; - -class DisplayMenuOnNextTrigger final : public Trigger { - public: - explicit DisplayMenuOnNextTrigger(MenuItemCustom *parent) : parent_(parent) { - parent->add_on_next_callback([this]() { this->trigger(this->parent_); }); - } - - protected: - MenuItemCustom *parent_; -}; - -class DisplayMenuOnPrevTrigger final : public Trigger { - public: - explicit DisplayMenuOnPrevTrigger(MenuItemCustom *parent) : parent_(parent) { - parent->add_on_prev_callback([this]() { this->trigger(this->parent_); }); - } - - protected: - MenuItemCustom *parent_; -}; - -} // namespace esphome::display_menu_base diff --git a/esphome/components/dlms_meter/__init__.py b/esphome/components/dlms_meter/__init__.py index 00a1694cc32..eed702b6f1a 100644 --- a/esphome/components/dlms_meter/__init__.py +++ b/esphome/components/dlms_meter/__init__.py @@ -1,4 +1,3 @@ -import logging import re from typing import Any @@ -13,12 +12,12 @@ from esphome.const import ( CONF_RECEIVE_TIMEOUT, ) from esphome.core import CORE +from esphome.cpp_generator import MockObj from esphome.types import ConfigType -_LOGGER = logging.getLogger(__name__) - CODEOWNERS = ["@SimonFischer04", "@Tomer27cz", "@latonita", "@PolarGoose"] DEPENDENCIES = ["uart"] +DOMAIN = "dlms_meter" CONF_DLMS_METER_ID = "dlms_meter_id" CONF_DECRYPTION_KEY = "decryption_key" @@ -29,19 +28,17 @@ CONF_SKIP_CRC = "skip_crc" CONF_DEFAULT_OBIS = "default_obis" CONF_PROVIDER = "provider" +DLMS_PARSER_VERSION = "2.2.0" + dlms_meter_component_ns = cg.esphome_ns.namespace("dlms_meter") DlmsMeterComponent = dlms_meter_component_ns.class_( "DlmsMeterComponent", cg.Component, uart.UARTDevice ) +CustomPattern = dlms_meter_component_ns.struct("CustomPattern") +ObisId = cg.global_ns.namespace("dlms_parser").class_("ObisId") -def obis_code(value: Any) -> str: - # Normalize the OBIS code to the strict A.B.C.D.E.F format - bytes_list = parse_obis_code_bytes(value) - return ".".join(str(b) for b in bytes_list) - - -def parse_obis_code_bytes(value: Any) -> list[int]: +def obis_string_to_byte_list(value: Any) -> list[int]: value = cv.string(value) normalized = re.sub(r"[\-\:\*]", ".", value) parts = normalized.split(".") @@ -59,82 +56,51 @@ def parse_obis_code_bytes(value: Any) -> list[int]: return bytes_list +def to_obis_id_struct(value: list[int]) -> cg.Expression: + return ObisId(*value) + + +_request_sensor_slot = cg.slot_counter("DLMS_MAX_SENSORS") +_request_text_sensor_slot = cg.slot_counter("DLMS_MAX_TEXT_SENSORS") +_request_binary_sensor_slot = cg.slot_counter("DLMS_MAX_BINARY_SENSORS") + + +def register_sensor(hub: MockObj, obis: list[int], var: MockObj) -> None: + _request_sensor_slot(str(hub)) + cg.add(hub.register_sensor(to_obis_id_struct(obis), var)) + + +def register_text_sensor(hub: MockObj, obis: list[int], var: MockObj) -> None: + _request_text_sensor_slot(str(hub)) + cg.add(hub.register_text_sensor(to_obis_id_struct(obis), var)) + + +def register_binary_sensor(hub: MockObj, obis: list[int], var: MockObj) -> None: + _request_binary_sensor_slot(str(hub)) + cg.add(hub.register_binary_sensor(to_obis_id_struct(obis), var)) + + def custom_pattern_dict(value: Any) -> ConfigType: if isinstance(value, str): return {CONF_PATTERN: value} return value -def validate_custom_pattern(value: ConfigType) -> ConfigType: - if CONF_DEFAULT_OBIS in value and CONF_NAME not in value: - raise cv.Invalid(f"'{CONF_DEFAULT_OBIS}' requires '{CONF_NAME}' to be set") - return value - - -def validate_provider_deprecation(config: ConfigType) -> ConfigType: - if CONF_PROVIDER in config: - provider = str(config[CONF_PROVIDER]).lower() - if provider == "netznoe": - _LOGGER.warning( - "The 'provider: netznoe' option is deprecated and will be removed in 2026.11.0. " - "The required custom patterns have been added automatically for this release, but you must update your configuration.\n" - "Please remove the 'provider' key and explicitly replace it with the following:\n\n" - "custom_patterns:\n" - ' - pattern: "L, TSTR"\n' - ' name: "MeterID"\n' - ' default_obis: "0.0.96.1.0.255"\n' - ' - pattern: "F, TDTM"\n' - ' name: "DateTime"\n' - ' default_obis: "0.0.1.0.0.255"\n' - ) - patterns = config.get(CONF_CUSTOM_PATTERNS, []) - - # Ensure "L, TSTR" for MeterID is present - if not any(p.get(CONF_PATTERN) == "L, TSTR" for p in patterns): - patterns.append( - { - CONF_PATTERN: "L, TSTR", - CONF_NAME: "MeterID", - CONF_DEFAULT_OBIS: [0, 0, 96, 1, 0, 255], - CONF_PRIORITY: 0, - } - ) - - # Ensure "F, TDTM" for DateTime is present - if not any(p.get(CONF_PATTERN) == "F, TDTM" for p in patterns): - patterns.append( - { - CONF_PATTERN: "F, TDTM", - CONF_NAME: "DateTime", - CONF_DEFAULT_OBIS: [0, 0, 1, 0, 0, 255], - CONF_PRIORITY: 0, - } - ) - - config[CONF_CUSTOM_PATTERNS] = patterns - else: - _LOGGER.warning( - "The 'provider' option is deprecated and will be removed in 2026.11.0. " - "The dlms_parser library now handles quirks dynamically. " - "Please remove this option from your configuration." - ) - return config - - CUSTOM_PATTERN_SCHEMA = cv.All( custom_pattern_dict, cv.Schema( { cv.Required(CONF_PATTERN): cv.string, - cv.Optional(CONF_NAME): cv.string, + cv.Optional(CONF_NAME, default="CUSTOM"): cv.string, cv.Optional(CONF_PRIORITY, default=0): cv.int_, - cv.Optional(CONF_DEFAULT_OBIS): parse_obis_code_bytes, + cv.Optional( + CONF_DEFAULT_OBIS, default="0.0.0.0.0.0" + ): obis_string_to_byte_list, } ), - validate_custom_pattern, ) -CONFIG_SCHEMA = cv.All( +CONFIG_SCHEMA = ( cv.Schema( { cv.GenerateID(): cv.declare_id(DlmsMeterComponent), @@ -142,115 +108,57 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_AUTH_KEY): cv.bind_key(name="Authentication key"), cv.Optional(CONF_CUSTOM_PATTERNS): cv.ensure_list(CUSTOM_PATTERN_SCHEMA), cv.Optional(CONF_SKIP_CRC, default=False): cv.boolean, - cv.Optional(CONF_PROVIDER): cv.string, + # Removed in 2026.11.0 - kept to provide helpful error message + # Remove before 2027.5.0 + cv.Optional(CONF_PROVIDER): cv.invalid( + "The 'provider' option has been removed in ESPHome 2026.11.0.\n" + "For 'provider: netznoe', replace it with:\n\n" + "custom_patterns:\n" + ' - pattern: "L, TSTR"\n' + ' name: "MeterID"\n' + ' default_obis: "0.0.96.1.0.255"\n' + ' - pattern: "F, TDTM"\n' + ' name: "DateTime"\n' + ' default_obis: "0.0.1.0.0.255"\n\n' + "For any other provider, remove the option" + ), cv.Optional( CONF_RECEIVE_TIMEOUT, default="1000ms" ): cv.positive_time_period_milliseconds, } ) .extend(uart.UART_DEVICE_SCHEMA) - .extend(cv.COMPONENT_SCHEMA), - validate_provider_deprecation, + .extend(cv.COMPONENT_SCHEMA) ) FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema("dlms_meter", require_rx=True) async def to_code(config: ConfigType) -> None: - dec_key_expr = cg.RawExpression("std::nullopt") - if dec_key := config.get(CONF_DECRYPTION_KEY): - key_bytes = [str(int(dec_key[i : i + 2], 16)) for i in range(0, 32, 2)] - dec_key_expr = cg.RawExpression( - f"std::array{{{', '.join(key_bytes)}}}" + custom_patterns = [ + cg.StructInitializer( + CustomPattern, + (CONF_PATTERN, pattern[CONF_PATTERN]), + (CONF_NAME, pattern[CONF_NAME]), + (CONF_PRIORITY, pattern[CONF_PRIORITY]), + (CONF_DEFAULT_OBIS, to_obis_id_struct(pattern[CONF_DEFAULT_OBIS])), ) - - auth_key_expr = cg.RawExpression("std::nullopt") - if auth_key := config.get(CONF_AUTH_KEY): - key_bytes = [str(int(auth_key[i : i + 2], 16)) for i in range(0, 32, 2)] - auth_key_expr = cg.RawExpression( - f"std::array{{{', '.join(key_bytes)}}}" - ) - - patterns = [] - if custom_patterns := config.get(CONF_CUSTOM_PATTERNS): - for p in custom_patterns: - name_expr = cg.RawExpression("std::nullopt") - if name_val := p.get(CONF_NAME): - name_expr = name_val - - if obis_vals := p.get(CONF_DEFAULT_OBIS): - obis_expr = cg.RawExpression( - f"std::array{{{obis_vals[0]}, {obis_vals[1]}, {obis_vals[2]}, {obis_vals[3]}, {obis_vals[4]}, {obis_vals[5]}}}" - ) - else: - obis_expr = cg.RawExpression("std::nullopt") - - patterns.append( - cg.ArrayInitializer( - p[CONF_PATTERN], - name_expr, - p.get(CONF_PRIORITY, 0), - obis_expr, - ) - ) - - patterns_expr = ( - cg.ArrayInitializer(*patterns) if patterns else cg.RawExpression("{}") - ) + for pattern in config.get(CONF_CUSTOM_PATTERNS, []) + ] var = cg.new_Pvariable( config[CONF_ID], config[CONF_RECEIVE_TIMEOUT], config[CONF_SKIP_CRC], - dec_key_expr, - auth_key_expr, - patterns_expr, + config.get(CONF_DECRYPTION_KEY, cg.nullptr), + config.get(CONF_AUTH_KEY, cg.nullptr), + cg.ArrayInitializer(*custom_patterns, multiline=True), ) - hub_id = config[CONF_ID].id - - sensor_count = 0 - for sens_conf in CORE.config.get("sensor", []): - if ( - sens_conf.get("platform") == "dlms_meter" - and sens_conf.get(CONF_DLMS_METER_ID).id == hub_id - ): - if CONF_OBIS_CODE in sens_conf: - sensor_count += 1 - else: - from .sensor import NUMERIC_KEYS - - sensor_count += sum(1 for key in NUMERIC_KEYS if key in sens_conf) - - text_sensor_count = 0 - for sens_conf in CORE.config.get("text_sensor", []): - if ( - sens_conf.get("platform") == "dlms_meter" - and sens_conf.get(CONF_DLMS_METER_ID).id == hub_id - ): - if CONF_OBIS_CODE in sens_conf: - text_sensor_count += 1 - else: - from .text_sensor import TEXT_KEYS - - text_sensor_count += sum(1 for key in TEXT_KEYS if key in sens_conf) - - binary_sensor_count = 0 - for sens_conf in CORE.config.get("binary_sensor", []): - if ( - sens_conf.get("platform") == "dlms_meter" - and sens_conf.get(CONF_DLMS_METER_ID).id == hub_id - ): - binary_sensor_count += 1 - - cg.add_define("DLMS_MAX_SENSORS", sensor_count) - cg.add_define("DLMS_MAX_TEXT_SENSORS", text_sensor_count) - cg.add_define("DLMS_MAX_BINARY_SENSORS", binary_sensor_count) - await cg.register_component(var, config) await uart.register_uart_device(var, config) if CORE.is_esp32: - esp32.add_idf_component(name="esphome/dlms_parser", ref="1.1.0") + esp32.add_idf_component(name="esphome/dlms_parser", ref=DLMS_PARSER_VERSION) else: - cg.add_library("esphome/dlms_parser", "1.1.0") + cg.add_library("esphome/dlms_parser", DLMS_PARSER_VERSION) diff --git a/esphome/components/dlms_meter/binary_sensor/__init__.py b/esphome/components/dlms_meter/binary_sensor/__init__.py index a15e58b9571..2a51ad09909 100644 --- a/esphome/components/dlms_meter/binary_sensor/__init__.py +++ b/esphome/components/dlms_meter/binary_sensor/__init__.py @@ -3,14 +3,20 @@ from esphome.components import binary_sensor import esphome.config_validation as cv from esphome.types import ConfigType -from .. import CONF_DLMS_METER_ID, CONF_OBIS_CODE, DlmsMeterComponent, obis_code +from .. import ( + CONF_DLMS_METER_ID, + CONF_OBIS_CODE, + DlmsMeterComponent, + obis_string_to_byte_list, + register_binary_sensor, +) DEPENDENCIES = ["dlms_meter"] CONFIG_SCHEMA = binary_sensor.binary_sensor_schema().extend( { cv.GenerateID(CONF_DLMS_METER_ID): cv.use_id(DlmsMeterComponent), - cv.Required(CONF_OBIS_CODE): obis_code, + cv.Required(CONF_OBIS_CODE): obis_string_to_byte_list, } ) @@ -18,4 +24,4 @@ CONFIG_SCHEMA = binary_sensor.binary_sensor_schema().extend( async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_DLMS_METER_ID]) var = await binary_sensor.new_binary_sensor(config) - cg.add(hub.register_binary_sensor(config[CONF_OBIS_CODE], var)) + register_binary_sensor(hub, config[CONF_OBIS_CODE], var) diff --git a/esphome/components/dlms_meter/dlms_meter.cpp b/esphome/components/dlms_meter/dlms_meter.cpp index e672fb200fc..32d1817e0e7 100644 --- a/esphome/components/dlms_meter/dlms_meter.cpp +++ b/esphome/components/dlms_meter/dlms_meter.cpp @@ -1,11 +1,16 @@ +// Ignore Zephyr. It doesn't have any encryption library. +#if defined(USE_ESP32) || defined(USE_ARDUINO) || defined(USE_HOST) + #include "dlms_meter.h" +#include "esphome/core/application.h" #include "esphome/core/log.h" +#include #include namespace esphome::dlms_meter { -static const char *const TAG = "dlms_meter"; +ESPHOME_LOG_TAG(TAG, "dlms_meter"); static void log_callback(dlms_parser::LogLevel level, const char *fmt, va_list args) { std::array buf; vsnprintf(buf.data(), buf.size(), fmt, args); @@ -31,54 +36,40 @@ static void log_callback(dlms_parser::LogLevel level, const char *fmt, va_list a } } -DlmsMeterComponent::DlmsMeterComponent(uint32_t receive_timeout_ms, bool skip_crc_check, - std::optional> decryption_key, - std::optional> authentication_key, +DlmsMeterComponent::DlmsMeterComponent(const uint32_t receive_timeout_ms, const bool skip_crc_check, + const char *decryption_key, const char *authentication_key, std::vector custom_patterns) : receive_timeout_ms_(receive_timeout_ms), skip_crc_check_(skip_crc_check), custom_patterns_(std::move(custom_patterns)), - parser_(&decryptor_) { + parser_([this](const dlms_parser::AxdrCapture &capture) { this->on_data_(capture); }, &this->decryptor_) { dlms_parser::Logger::set_log_function(log_callback); - if (decryption_key.has_value()) { -#ifdef DLMS_METER_NO_CRYPTO - ESP_LOGE(TAG, "Decryption is not supported on this platform (no compatible crypto library found)"); -#else - auto opt_key = dlms_parser::Aes128GcmDecryptionKey::from_bytes(decryption_key.value()); - if (opt_key) { - this->parser_.set_decryption_key(*opt_key); + if (decryption_key != nullptr) { + const auto key = dlms_parser::Aes128GcmDecryptionKey::from_hex(decryption_key); + if (key.has_value()) { + this->parser_.set_decryption_key(*key); } else { ESP_LOGE(TAG, "Failed to set decryption key: invalid key format"); } -#endif } - if (authentication_key.has_value()) { -#ifdef DLMS_METER_NO_CRYPTO - ESP_LOGE(TAG, "Authentication is not supported on this platform (no compatible crypto library found)"); -#else - auto opt_key = dlms_parser::Aes128GcmAuthenticationKey::from_bytes(authentication_key.value()); - if (opt_key) { - this->parser_.set_authentication_key(*opt_key); + if (authentication_key != nullptr) { + const auto key = dlms_parser::Aes128GcmAuthenticationKey::from_hex(authentication_key); + if (key.has_value()) { + this->parser_.set_authentication_key(*key); } else { ESP_LOGE(TAG, "Failed to set authentication key: invalid key format"); } -#endif } this->parser_.set_skip_crc_check(this->skip_crc_check_); this->parser_.load_default_patterns(); - for (const auto &pattern : this->custom_patterns_) { - if (pattern.default_obis.has_value() && pattern.name.has_value()) { - this->parser_.register_pattern(pattern.name->c_str(), pattern.pattern.c_str(), pattern.priority, - pattern.default_obis.value()); - } else if (pattern.name.has_value()) { - this->parser_.register_pattern(pattern.name->c_str(), pattern.pattern.c_str(), pattern.priority); - } else { - this->parser_.register_pattern(pattern.pattern.c_str()); - } + + for (const auto &custom_pattern : this->custom_patterns_) { + this->parser_.register_pattern(custom_pattern.name, custom_pattern.pattern, custom_pattern.priority, + custom_pattern.default_obis); } } @@ -89,36 +80,36 @@ void DlmsMeterComponent::dump_config() { ESP_LOGCONFIG(TAG, " Receive Timeout: %" PRIu32 " ms", this->receive_timeout_ms_); ESP_LOGCONFIG(TAG, " Skip CRC Check: %s", YESNO(this->skip_crc_check_)); - for (const auto &pattern : this->custom_patterns_) { - if (pattern.default_obis.has_value() && pattern.name.has_value()) { - const auto &obis = pattern.default_obis.value(); - ESP_LOGCONFIG(TAG, " Custom Pattern: '%s' (name: %s, priority: %d, default_obis: %d.%d.%d.%d.%d.%d)", - pattern.pattern.c_str(), pattern.name->c_str(), pattern.priority, obis[0], obis[1], obis[2], - obis[3], obis[4], obis[5]); - } else if (pattern.name.has_value()) { - ESP_LOGCONFIG(TAG, " Custom Pattern: '%s' (name: %s, priority: %d)", pattern.pattern.c_str(), - pattern.name->c_str(), pattern.priority); - } else { - ESP_LOGCONFIG(TAG, " Custom Pattern: '%s'", pattern.pattern.c_str()); - } + std::array obis_buf; + + for (const auto &custom_pattern : this->custom_patterns_) { + const auto default_obis = custom_pattern.default_obis.to_string(obis_buf); + ESP_LOGCONFIG(TAG, " Custom Pattern: '%s' (name: %s, priority: %d, default_obis: %.*s)", custom_pattern.pattern, + custom_pattern.name, custom_pattern.priority, static_cast(default_obis.size()), + default_obis.data()); } #ifdef USE_SENSOR - for (const auto &entry : this->sensors_) { - LOG_SENSOR(" ", "Numeric Sensor (OBIS)", entry.sensor); - ESP_LOGCONFIG(TAG, " OBIS: %s", entry.obis_code.c_str()); + for (const auto &item : this->sensors_) { + LOG_SENSOR(" ", "Numeric Sensor (OBIS)", item.sensor); + const auto obis = item.obis.to_string(obis_buf); + ESP_LOGCONFIG(TAG, " OBIS: %.*s", static_cast(obis.size()), obis.data()); } #endif + #ifdef USE_TEXT_SENSOR - for (const auto &entry : this->text_sensors_) { - LOG_TEXT_SENSOR(" ", "Text Sensor (OBIS)", entry.sensor); - ESP_LOGCONFIG(TAG, " OBIS: %s", entry.obis_code.c_str()); + for (const auto &item : this->text_sensors_) { + LOG_TEXT_SENSOR(" ", "Text Sensor (OBIS)", item.sensor); + const auto obis = item.obis.to_string(obis_buf); + ESP_LOGCONFIG(TAG, " OBIS: %.*s", static_cast(obis.size()), obis.data()); } #endif + #ifdef USE_BINARY_SENSOR - for (const auto &entry : this->binary_sensors_) { - LOG_BINARY_SENSOR(" ", "Binary Sensor (OBIS)", entry.sensor); - ESP_LOGCONFIG(TAG, " OBIS: %s", entry.obis_code.c_str()); + for (const auto &item : this->binary_sensors_) { + LOG_BINARY_SENSOR(" ", "Binary Sensor (OBIS)", item.sensor); + const auto obis = item.obis.to_string(obis_buf); + ESP_LOGCONFIG(TAG, " OBIS: %.*s", static_cast(obis.size()), obis.data()); } #endif } @@ -166,71 +157,73 @@ void DlmsMeterComponent::read_rx_buffer_() { void DlmsMeterComponent::process_frame_() { ESP_LOGV(TAG, "Processing frame of size: %zu bytes", this->bytes_accumulated_); - auto callback = [this](const char *obis_code, float float_val, const char *str_val, bool is_numeric) { - this->on_data_(obis_code, float_val, str_val, is_numeric); - }; - - this->parser_.parse({this->rx_buffer_.data(), this->bytes_accumulated_}, callback); + this->parser_.parse({this->rx_buffer_.data(), this->bytes_accumulated_}); this->bytes_accumulated_ = 0; } -void DlmsMeterComponent::on_data_(const char *obis_code, float float_val, const char *str_val, bool is_numeric) { +void DlmsMeterComponent::on_data_(const dlms_parser::AxdrCapture &capture) { int updated_count = 0; + if (capture.is_numeric()) { +#if defined(USE_SENSOR) || defined(USE_BINARY_SENSOR) + const float value = capture.value_as_float_with_scaler_applied(); +#endif #ifdef USE_SENSOR - if (is_numeric) { - for (auto &item : this->sensors_) { - if (item.obis_code == obis_code) { - item.sensor->publish_state(float_val); + for (const auto &item : this->sensors_) { + if (item.obis == capture.obis) { + item.sensor->publish_state(value); updated_count++; } } - } #endif - -#ifdef USE_TEXT_SENSOR - if (!is_numeric && str_val != nullptr) { - for (auto &item : this->text_sensors_) { - if (item.obis_code == obis_code) { - item.sensor->publish_state(str_val); - updated_count++; - } - } - } -#endif - #ifdef USE_BINARY_SENSOR - if (is_numeric) { - bool state = float_val != 0.0f; - for (auto &item : this->binary_sensors_) { - if (item.obis_code == obis_code) { - item.sensor->publish_state(state); + for (const auto &item : this->binary_sensors_) { + if (item.obis == capture.obis) { + item.sensor->publish_state(value != 0.0f); updated_count++; } } - } #endif + } else { +#ifdef USE_TEXT_SENSOR + for (const auto &item : this->text_sensors_) { + if (item.obis == capture.obis) { + std::array value_buf; + const auto value = capture.value_as_string(value_buf); + item.sensor->publish_state(value.data(), value.size()); + updated_count++; + } + } +#endif + } if (updated_count == 0) { - ESP_LOGV(TAG, "Received OBIS %s, but no sensors are registered for it.", obis_code); + std::array obis_buf; + const auto obis = capture.obis.to_string(obis_buf); + ESP_LOGV(TAG, "Received OBIS %.*s, but no sensors are registered for it.", static_cast(obis.size()), + obis.data()); } } #ifdef USE_SENSOR -void DlmsMeterComponent::register_sensor(const std::string &obis_code, sensor::Sensor *sensor) { - this->sensors_.push_back({obis_code, sensor}); +void DlmsMeterComponent::register_sensor(const dlms_parser::ObisId &obis, sensor::Sensor *sensor) { + this->sensors_.push_back({obis, sensor}); } #endif + #ifdef USE_TEXT_SENSOR -void DlmsMeterComponent::register_text_sensor(const std::string &obis_code, text_sensor::TextSensor *sensor) { - this->text_sensors_.push_back({obis_code, sensor}); +void DlmsMeterComponent::register_text_sensor(const dlms_parser::ObisId &obis, text_sensor::TextSensor *sensor) { + this->text_sensors_.push_back({obis, sensor}); } #endif + #ifdef USE_BINARY_SENSOR -void DlmsMeterComponent::register_binary_sensor(const std::string &obis_code, binary_sensor::BinarySensor *sensor) { - this->binary_sensors_.push_back({obis_code, sensor}); +void DlmsMeterComponent::register_binary_sensor(const dlms_parser::ObisId &obis, binary_sensor::BinarySensor *sensor) { + this->binary_sensors_.push_back({obis, sensor}); } #endif } // namespace esphome::dlms_meter + +#endif diff --git a/esphome/components/dlms_meter/dlms_meter.h b/esphome/components/dlms_meter/dlms_meter.h index fc4721843fd..2d7898f4d3e 100644 --- a/esphome/components/dlms_meter/dlms_meter.h +++ b/esphome/components/dlms_meter/dlms_meter.h @@ -1,9 +1,13 @@ #pragma once +// Ignore Zephyr. It doesn't have any encryption library. +#if defined(USE_ESP32) || defined(USE_ARDUINO) || defined(USE_HOST) + +#include +#include + #include "esphome/core/component.h" #include "esphome/core/defines.h" -#include "esphome/core/application.h" -#include "esphome/core/log.h" #include "esphome/core/helpers.h" #include "esphome/components/uart/uart.h" @@ -19,12 +23,6 @@ #include -#include -#include -#include -#include -#include - #if __has_include() #include #elif !defined(USE_ESP8266) && __has_include() @@ -35,9 +33,12 @@ #elif __has_include() #include #else -#define DLMS_METER_NO_CRYPTO +#error "The platform doesn't provide a compatible encryption library for dlms_parser" #endif +namespace esphome::dlms_meter { + +// Entity storage is sized by codegen; a platform with no entities emits no define #ifndef DLMS_MAX_SENSORS static constexpr uint8_t DLMS_MAX_SENSORS = 0; #endif @@ -48,82 +49,64 @@ static constexpr uint8_t DLMS_MAX_TEXT_SENSORS = 0; static constexpr uint8_t DLMS_MAX_BINARY_SENSORS = 0; #endif -namespace esphome::dlms_meter { - -#ifdef DLMS_METER_NO_CRYPTO -// Fallback dummy decryptor for platforms without supported crypto (e.g., Zephyr during clang-tidy) -class Aes128GcmDecryptorDummy : public dlms_parser::Aes128GcmDecryptor { - public: - void set_decryption_key(const dlms_parser::Aes128GcmDecryptionKey &key) override {} - bool decrypt_in_place(std::span iv, std::span ciphertext_and_plaintext, - std::span aad, std::span tag) override { - return false; - } -}; -#endif - -#if __has_include() -using Aes128GcmDecryptorImpl = dlms_parser::Aes128GcmDecryptorTfPsa; -#elif !defined(USE_ESP8266) && __has_include() -using Aes128GcmDecryptorImpl = dlms_parser::Aes128GcmDecryptorMbedTls; -#elif __has_include() -using Aes128GcmDecryptorImpl = dlms_parser::Aes128GcmDecryptorBearSsl; -#else -using Aes128GcmDecryptorImpl = Aes128GcmDecryptorDummy; -#endif - #ifdef USE_SENSOR struct SensorItem { - std::string obis_code; + dlms_parser::ObisId obis; sensor::Sensor *sensor; }; #endif #ifdef USE_TEXT_SENSOR struct TextSensorItem { - std::string obis_code; + dlms_parser::ObisId obis; text_sensor::TextSensor *sensor; }; #endif #ifdef USE_BINARY_SENSOR struct BinarySensorItem { - std::string obis_code; + dlms_parser::ObisId obis; binary_sensor::BinarySensor *sensor; }; #endif struct CustomPattern { - std::string pattern; - std::optional name; - int priority{0}; - std::optional> default_obis; + const char *pattern; + const char *name; + int priority; + dlms_parser::ObisId default_obis; }; +#if __has_include() +using Aes128GcmDecryptorImpl = dlms_parser::Aes128GcmDecryptorTfPsa; +#elif !defined(USE_ESP8266) && __has_include() +using Aes128GcmDecryptorImpl = dlms_parser::Aes128GcmDecryptorMbedTls; +#else +using Aes128GcmDecryptorImpl = dlms_parser::Aes128GcmDecryptorBearSsl; +#endif + class DlmsMeterComponent final : public Component, public uart::UARTDevice { public: - DlmsMeterComponent(uint32_t receive_timeout_ms, bool skip_crc_check, - std::optional> decryption_key, - std::optional> authentication_key, - std::vector custom_patterns); + DlmsMeterComponent(uint32_t receive_timeout_ms, bool skip_crc_check, const char *decryption_key, + const char *authentication_key, std::vector custom_patterns); void setup() override; void dump_config() override; void loop() override; #ifdef USE_SENSOR - void register_sensor(const std::string &obis_code, sensor::Sensor *sensor); + void register_sensor(const dlms_parser::ObisId &obis, sensor::Sensor *sensor); #endif #ifdef USE_TEXT_SENSOR - void register_text_sensor(const std::string &obis_code, text_sensor::TextSensor *sensor); + void register_text_sensor(const dlms_parser::ObisId &obis, text_sensor::TextSensor *sensor); #endif #ifdef USE_BINARY_SENSOR - void register_binary_sensor(const std::string &obis_code, binary_sensor::BinarySensor *sensor); + void register_binary_sensor(const dlms_parser::ObisId &obis, binary_sensor::BinarySensor *sensor); #endif protected: void read_rx_buffer_(); void flush_rx_buffer_(); void process_frame_(); - void on_data_(const char *obis_code, float float_val, const char *str_val, bool is_numeric); + void on_data_(const dlms_parser::AxdrCapture &capture); std::array rx_buffer_; size_t bytes_accumulated_{0}; @@ -149,3 +132,5 @@ class DlmsMeterComponent final : public Component, public uart::UARTDevice { }; } // namespace esphome::dlms_meter + +#endif diff --git a/esphome/components/dlms_meter/sensor/__init__.py b/esphome/components/dlms_meter/sensor/__init__.py index 8ded150cd09..15b9deb012c 100644 --- a/esphome/components/dlms_meter/sensor/__init__.py +++ b/esphome/components/dlms_meter/sensor/__init__.py @@ -1,159 +1,65 @@ -import logging - import esphome.codegen as cg from esphome.components import sensor import esphome.config_validation as cv -from esphome.const import ( - DEVICE_CLASS_CURRENT, - DEVICE_CLASS_ENERGY, - DEVICE_CLASS_POWER, - DEVICE_CLASS_POWER_FACTOR, - DEVICE_CLASS_VOLTAGE, - STATE_CLASS_MEASUREMENT, - STATE_CLASS_TOTAL_INCREASING, - UNIT_AMPERE, - UNIT_VOLT, - UNIT_WATT, - UNIT_WATT_HOURS, -) from esphome.types import ConfigType -from .. import CONF_DLMS_METER_ID, CONF_OBIS_CODE, DlmsMeterComponent, obis_code - -_LOGGER = logging.getLogger(__name__) +from .. import ( + CONF_DLMS_METER_ID, + CONF_OBIS_CODE, + DlmsMeterComponent, + obis_string_to_byte_list, + register_sensor, +) DEPENDENCIES = ["dlms_meter"] -NUMERIC_KEYS = { - "voltage_l1": "1.0.32.7.0.255", - "voltage_l2": "1.0.52.7.0.255", - "voltage_l3": "1.0.72.7.0.255", - "current_l1": "1.0.31.7.0.255", - "current_l2": "1.0.51.7.0.255", - "current_l3": "1.0.71.7.0.255", - "active_power_plus": "1.0.1.7.0.255", - "active_power_minus": "1.0.2.7.0.255", - "active_energy_plus": "1.0.1.8.0.255", - "active_energy_minus": "1.0.2.8.0.255", - "reactive_energy_plus": "1.0.3.8.0.255", - "reactive_energy_minus": "1.0.4.8.0.255", - "power_factor": "1.0.13.7.0.255", +# Removed in 2026.11.0 - kept to provide helpful error message +# Remove before 2027.5.0 +_MEASUREMENT = "state_class: measurement" +_VOLTAGE = f"unit_of_measurement: V, accuracy_decimals: 1, device_class: voltage, {_MEASUREMENT}" +_CURRENT = f"unit_of_measurement: A, accuracy_decimals: 2, device_class: current, {_MEASUREMENT}" +_POWER = ( + f"unit_of_measurement: W, accuracy_decimals: 0, device_class: power, {_MEASUREMENT}" +) +_ENERGY = ( + "unit_of_measurement: Wh, accuracy_decimals: 0, device_class: energy, " + "state_class: total_increasing" +) +_POWER_FACTOR = f"accuracy_decimals: 3, device_class: power_factor, {_MEASUREMENT}" +REMOVED_KEYS = { + "voltage_l1": ("1.0.32.7.0.255", _VOLTAGE), + "voltage_l2": ("1.0.52.7.0.255", _VOLTAGE), + "voltage_l3": ("1.0.72.7.0.255", _VOLTAGE), + "current_l1": ("1.0.31.7.0.255", _CURRENT), + "current_l2": ("1.0.51.7.0.255", _CURRENT), + "current_l3": ("1.0.71.7.0.255", _CURRENT), + "active_power_plus": ("1.0.1.7.0.255", _POWER), + "active_power_minus": ("1.0.2.7.0.255", _POWER), + "active_energy_plus": ("1.0.1.8.0.255", _ENERGY), + "active_energy_minus": ("1.0.2.8.0.255", _ENERGY), + "reactive_energy_plus": ("1.0.3.8.0.255", _ENERGY), + "reactive_energy_minus": ("1.0.4.8.0.255", _ENERGY), + "power_factor": ("1.0.13.7.0.255", _POWER_FACTOR), } -DYNAMIC_SCHEMA = sensor.sensor_schema().extend( + +CONFIG_SCHEMA = sensor.sensor_schema().extend( { cv.GenerateID(CONF_DLMS_METER_ID): cv.use_id(DlmsMeterComponent), - cv.Required(CONF_OBIS_CODE): obis_code, + cv.Required(CONF_OBIS_CODE): obis_string_to_byte_list, + **{ + cv.Optional(key): cv.invalid( + f"The predefined '{key}' key was removed in ESPHome 2026.11.0. " + f"Add a separate '- platform: dlms_meter' sensor with " + f'obis_code: "{obis}" and {settings}' + ) + for key, (obis, settings) in REMOVED_KEYS.items() + }, } ) -def deprecation_warning(config: ConfigType) -> ConfigType: - _LOGGER.warning( - "The dlms_meter sensor schema using predefined keys (e.g., 'voltage_l1') is deprecated and will be removed in 2026.11.0. " - "Please update your configuration to use the new schema with 'obis_code'." - ) - return config - - -OLD_SCHEMA = cv.All( - cv.Schema( - { - cv.GenerateID(CONF_DLMS_METER_ID): cv.use_id(DlmsMeterComponent), - cv.Optional("voltage_l1"): sensor.sensor_schema( - unit_of_measurement=UNIT_VOLT, - accuracy_decimals=1, - device_class=DEVICE_CLASS_VOLTAGE, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional("voltage_l2"): sensor.sensor_schema( - unit_of_measurement=UNIT_VOLT, - accuracy_decimals=1, - device_class=DEVICE_CLASS_VOLTAGE, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional("voltage_l3"): sensor.sensor_schema( - unit_of_measurement=UNIT_VOLT, - accuracy_decimals=1, - device_class=DEVICE_CLASS_VOLTAGE, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional("current_l1"): sensor.sensor_schema( - unit_of_measurement=UNIT_AMPERE, - accuracy_decimals=2, - device_class=DEVICE_CLASS_CURRENT, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional("current_l2"): sensor.sensor_schema( - unit_of_measurement=UNIT_AMPERE, - accuracy_decimals=2, - device_class=DEVICE_CLASS_CURRENT, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional("current_l3"): sensor.sensor_schema( - unit_of_measurement=UNIT_AMPERE, - accuracy_decimals=2, - device_class=DEVICE_CLASS_CURRENT, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional("active_power_plus"): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT, - accuracy_decimals=0, - device_class=DEVICE_CLASS_POWER, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional("active_power_minus"): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT, - accuracy_decimals=0, - device_class=DEVICE_CLASS_POWER, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional("active_energy_plus"): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT_HOURS, - accuracy_decimals=0, - device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_TOTAL_INCREASING, - ), - cv.Optional("active_energy_minus"): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT_HOURS, - accuracy_decimals=0, - device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_TOTAL_INCREASING, - ), - cv.Optional("reactive_energy_plus"): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT_HOURS, - accuracy_decimals=0, - device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_TOTAL_INCREASING, - ), - cv.Optional("reactive_energy_minus"): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT_HOURS, - accuracy_decimals=0, - device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_TOTAL_INCREASING, - ), - cv.Optional("power_factor"): sensor.sensor_schema( - accuracy_decimals=3, - device_class=DEVICE_CLASS_POWER_FACTOR, - state_class=STATE_CLASS_MEASUREMENT, - ), - } - ).extend(cv.COMPONENT_SCHEMA), - deprecation_warning, -) - - -CONFIG_SCHEMA = cv.Any(DYNAMIC_SCHEMA, OLD_SCHEMA) - - async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_DLMS_METER_ID]) - - if obis := config.get(CONF_OBIS_CODE): - var = await sensor.new_sensor(config) - cg.add(hub.register_sensor(obis, var)) - else: - for key, obis_val in NUMERIC_KEYS.items(): - if sensor_config := config.get(key): - sens = await sensor.new_sensor(sensor_config) - cg.add(hub.register_sensor(obis_val, sens)) + var = await sensor.new_sensor(config) + register_sensor(hub, config[CONF_OBIS_CODE], var) diff --git a/esphome/components/dlms_meter/text_sensor/__init__.py b/esphome/components/dlms_meter/text_sensor/__init__.py index c2ff0779eee..4e2e48487a8 100644 --- a/esphome/components/dlms_meter/text_sensor/__init__.py +++ b/esphome/components/dlms_meter/text_sensor/__init__.py @@ -1,60 +1,43 @@ -import logging - import esphome.codegen as cg from esphome.components import text_sensor import esphome.config_validation as cv from esphome.types import ConfigType -from .. import CONF_DLMS_METER_ID, CONF_OBIS_CODE, DlmsMeterComponent, obis_code - -_LOGGER = logging.getLogger(__name__) +from .. import ( + CONF_DLMS_METER_ID, + CONF_OBIS_CODE, + DlmsMeterComponent, + obis_string_to_byte_list, + register_text_sensor, +) DEPENDENCIES = ["dlms_meter"] -TEXT_KEYS = { +# Removed in 2026.11.0 - kept to provide helpful error message +# Remove before 2027.5.0 +REMOVED_KEYS = { "timestamp": "0.0.1.0.0.255", "meternumber": "0.0.96.1.0.255", } -DYNAMIC_SCHEMA = text_sensor.text_sensor_schema().extend( + +CONFIG_SCHEMA = text_sensor.text_sensor_schema().extend( { cv.GenerateID(CONF_DLMS_METER_ID): cv.use_id(DlmsMeterComponent), - cv.Required(CONF_OBIS_CODE): obis_code, + cv.Required(CONF_OBIS_CODE): obis_string_to_byte_list, + **{ + cv.Optional(key): cv.invalid( + f"The predefined '{key}' key was removed in ESPHome 2026.11.0. " + f"Add a separate '- platform: dlms_meter' text sensor with " + f'obis_code: "{obis}" instead' + ) + for key, obis in REMOVED_KEYS.items() + }, } ) -def deprecation_warning(config: ConfigType) -> ConfigType: - _LOGGER.warning( - "The dlms_meter text_sensor schema using predefined keys (e.g., 'timestamp') is deprecated and will be removed in 2026.11.0. " - "Please update your configuration to use the new schema with 'obis_code'." - ) - return config - - -OLD_SCHEMA = cv.All( - cv.Schema( - { - cv.GenerateID(CONF_DLMS_METER_ID): cv.use_id(DlmsMeterComponent), - cv.Optional("timestamp"): text_sensor.text_sensor_schema(), - cv.Optional("meternumber"): text_sensor.text_sensor_schema(), - } - ).extend(cv.COMPONENT_SCHEMA), - deprecation_warning, -) - - -CONFIG_SCHEMA = cv.Any(DYNAMIC_SCHEMA, OLD_SCHEMA) - - async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_DLMS_METER_ID]) - - if obis := config.get(CONF_OBIS_CODE): - var = await text_sensor.new_text_sensor(config) - cg.add(hub.register_text_sensor(obis, var)) - else: - for key, obis_val in TEXT_KEYS.items(): - if text_sensor_config := config.get(key): - sens = await text_sensor.new_text_sensor(text_sensor_config) - cg.add(hub.register_text_sensor(obis_val, sens)) + var = await text_sensor.new_text_sensor(config) + register_text_sensor(hub, config[CONF_OBIS_CODE], var) diff --git a/esphome/components/dps310/__init__.py b/esphome/components/dps310/__init__.py index e69de29bb2d..52f2a4105e0 100644 --- a/esphome/components/dps310/__init__.py +++ b/esphome/components/dps310/__init__.py @@ -0,0 +1 @@ +DOMAIN = "dps310" diff --git a/esphome/components/dps310/dps310.cpp b/esphome/components/dps310/dps310.cpp index 3b4693139d3..080f101747f 100644 --- a/esphome/components/dps310/dps310.cpp +++ b/esphome/components/dps310/dps310.cpp @@ -4,7 +4,9 @@ namespace esphome::dps310 { -static const char *const TAG = "dps310"; +ESPHOME_LOG_TAG(TAG, "dps310"); + +static constexpr uint32_t READ_RETRY_TIMEOUT_ID = 0; void DPS310Component::setup() { uint8_t coef_data_raw[DPS310_NUM_COEF_REGS]; @@ -126,7 +128,7 @@ void DPS310Component::read_() { this->update_in_progress_ = false; this->status_clear_warning(); } else { - this->set_timeout("dps310", 10, [this]() { this->read_(); }); + this->set_timeout(READ_RETRY_TIMEOUT_ID, 10, [this]() { this->read_(); }); } } diff --git a/esphome/components/ds1307/__init__.py b/esphome/components/ds1307/__init__.py index e69de29bb2d..fa275ded95e 100644 --- a/esphome/components/ds1307/__init__.py +++ b/esphome/components/ds1307/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ds1307" diff --git a/esphome/components/ds1307/ds1307.cpp b/esphome/components/ds1307/ds1307.cpp index 1d04b52168e..4ddef04f413 100644 --- a/esphome/components/ds1307/ds1307.cpp +++ b/esphome/components/ds1307/ds1307.cpp @@ -6,7 +6,7 @@ namespace esphome::ds1307 { -static const char *const TAG = "ds1307"; +ESPHOME_LOG_TAG(TAG, "ds1307"); void DS1307Component::setup() { if (!this->read_rtc_()) { diff --git a/esphome/components/ds1603l/__init__.py b/esphome/components/ds1603l/__init__.py index e69de29bb2d..81c65f4fd3d 100644 --- a/esphome/components/ds1603l/__init__.py +++ b/esphome/components/ds1603l/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ds1603l" diff --git a/esphome/components/ds1603l/ds1603l.cpp b/esphome/components/ds1603l/ds1603l.cpp index b0b0ef8175d..7762a593f11 100644 --- a/esphome/components/ds1603l/ds1603l.cpp +++ b/esphome/components/ds1603l/ds1603l.cpp @@ -7,7 +7,7 @@ namespace esphome::ds1603l { -static const char *const TAG = "ds1603l.sensor"; +ESPHOME_LOG_TAG(TAG, "ds1603l.sensor"); void DS1603L::loop() { // Assemble frames one byte at a time so a stream that starts mid-frame can realign diff --git a/esphome/components/ds2484/__init__.py b/esphome/components/ds2484/__init__.py index 3d9f24ff197..f101dedc012 100644 --- a/esphome/components/ds2484/__init__.py +++ b/esphome/components/ds2484/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@mrk-its"] +DOMAIN = "ds2484" diff --git a/esphome/components/ds2484/ds2484.cpp b/esphome/components/ds2484/ds2484.cpp index 69103ccbd22..55198481960 100644 --- a/esphome/components/ds2484/ds2484.cpp +++ b/esphome/components/ds2484/ds2484.cpp @@ -1,7 +1,7 @@ #include "ds2484.h" namespace esphome::ds2484 { -static const char *const TAG = "ds2484.onewire"; +ESPHOME_LOG_TAG(TAG, "ds2484.onewire"); void DS2484OneWireBus::setup() { this->reset_device(); diff --git a/esphome/components/ds248x/__init__.py b/esphome/components/ds248x/__init__.py index a2e2a87ed0c..3a2cb34747a 100644 --- a/esphome/components/ds248x/__init__.py +++ b/esphome/components/ds248x/__init__.py @@ -6,6 +6,7 @@ from esphome.const import CONF_ID, CONF_SLEEP_PIN, CONF_TYPE from esphome.types import ConfigType CODEOWNERS = ["@tomwellnitz"] +DOMAIN = "ds248x" MULTI_CONF = True DEPENDENCIES = ["i2c"] diff --git a/esphome/components/ds248x/ds248x.cpp b/esphome/components/ds248x/ds248x.cpp index c8f7395119c..db104b3d154 100644 --- a/esphome/components/ds248x/ds248x.cpp +++ b/esphome/components/ds248x/ds248x.cpp @@ -4,7 +4,7 @@ namespace esphome::ds248x { -static const char *const TAG = "ds248x"; +ESPHOME_LOG_TAG(TAG, "ds248x"); void DS248xComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up DS248x..."); diff --git a/esphome/components/ds248x/ds248x_one_wire_bus.cpp b/esphome/components/ds248x/ds248x_one_wire_bus.cpp index ed5b05bab70..201261d4bf6 100644 --- a/esphome/components/ds248x/ds248x_one_wire_bus.cpp +++ b/esphome/components/ds248x/ds248x_one_wire_bus.cpp @@ -4,7 +4,7 @@ namespace esphome::ds248x { -static const char *const TAG = "ds248x.one_wire"; +ESPHOME_LOG_TAG(TAG, "ds248x.one_wire"); void DS248xOneWireBus::setup() { ESP_LOGCONFIG(TAG, "Setting up DS248x 1-Wire Bus (Channel %d)...", this->channel_); diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index 96a1e756685..07dbf558310 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -3,6 +3,7 @@ import logging from esphome import pins import esphome.codegen as cg from esphome.components import uart +from esphome.components.uart import DOMAIN as UART_DOMAIN import esphome.config_validation as cv from esphome.const import ( CONF_ID, @@ -16,6 +17,7 @@ from esphome.types import ConfigType _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@glmnet", "@PolarGoose"] +DOMAIN = "dsmr" MULTI_CONF = True @@ -91,7 +93,7 @@ async def to_code(config: ConfigType) -> None: def final_validate(config: ConfigType) -> None: full_config = fv.full_config.get() - for uart_conf in full_config["uart"]: + for uart_conf in full_config[UART_DOMAIN]: if uart_conf[CONF_ID] == config[CONF_UART_ID]: rx_buffer_size = uart_conf[CONF_RX_BUFFER_SIZE] if rx_buffer_size < 1500: diff --git a/esphome/components/duty_cycle/__init__.py b/esphome/components/duty_cycle/__init__.py index e69de29bb2d..8d11f044883 100644 --- a/esphome/components/duty_cycle/__init__.py +++ b/esphome/components/duty_cycle/__init__.py @@ -0,0 +1 @@ +DOMAIN = "duty_cycle" diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.cpp b/esphome/components/duty_cycle/duty_cycle_sensor.cpp index fd0a48b9351..69a41eb548d 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.cpp +++ b/esphome/components/duty_cycle/duty_cycle_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::duty_cycle { -static const char *const TAG = "duty_cycle"; +ESPHOME_LOG_TAG(TAG, "duty_cycle"); void DutyCycleSensor::setup() { this->pin_->setup(); diff --git a/esphome/components/duty_time/__init__.py b/esphome/components/duty_time/__init__.py index b708cee80b2..258253e4eb0 100644 --- a/esphome/components/duty_time/__init__.py +++ b/esphome/components/duty_time/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@dudanov"] +DOMAIN = "duty_time" diff --git a/esphome/components/duty_time/duty_time_sensor.cpp b/esphome/components/duty_time/duty_time_sensor.cpp index 697a4e96f3c..646c2febc6c 100644 --- a/esphome/components/duty_time/duty_time_sensor.cpp +++ b/esphome/components/duty_time/duty_time_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::duty_time_sensor { -static const char *const TAG = "duty_time_sensor"; +ESPHOME_LOG_TAG(TAG, "duty_time_sensor"); #ifdef USE_BINARY_SENSOR void DutyTimeSensor::set_sensor(binary_sensor::BinarySensor *const sensor) { diff --git a/esphome/components/e131/__init__.py b/esphome/components/e131/__init__.py index 3b1eb99e60b..dbe9b1e45c1 100644 --- a/esphome/components/e131/__init__.py +++ b/esphome/components/e131/__init__.py @@ -9,6 +9,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["socket"] DEPENDENCIES = ["network"] +DOMAIN = "e131" e131_ns = cg.esphome_ns.namespace("e131") E131AddressableLightEffect = e131_ns.class_( diff --git a/esphome/components/e131/e131.cpp b/esphome/components/e131/e131.cpp index 061af4c4c0e..09c854c6495 100644 --- a/esphome/components/e131/e131.cpp +++ b/esphome/components/e131/e131.cpp @@ -7,7 +7,7 @@ namespace esphome::e131 { -static const char *const TAG = "e131"; +ESPHOME_LOG_TAG(TAG, "e131"); static const int PORT = 5568; E131Component::E131Component() {} diff --git a/esphome/components/e131/e131_addressable_light_effect.cpp b/esphome/components/e131/e131_addressable_light_effect.cpp index 6157eba4e93..c8940052fde 100644 --- a/esphome/components/e131/e131_addressable_light_effect.cpp +++ b/esphome/components/e131/e131_addressable_light_effect.cpp @@ -5,7 +5,7 @@ namespace esphome::e131 { -static const char *const TAG = "e131_addressable_light_effect"; +ESPHOME_LOG_TAG(TAG, "e131_addressable_light_effect"); static const int MAX_DATA_SIZE = (sizeof(E131Packet::values) - 1); E131AddressableLightEffect::E131AddressableLightEffect(const char *name) : AddressableLightEffect(name) {} diff --git a/esphome/components/e131/e131_packet.cpp b/esphome/components/e131/e131_packet.cpp index afed2abe311..e3f2e3940af 100644 --- a/esphome/components/e131/e131_packet.cpp +++ b/esphome/components/e131/e131_packet.cpp @@ -14,7 +14,7 @@ namespace esphome::e131 { -static const char *const TAG = "e131"; +ESPHOME_LOG_TAG(TAG, "e131"); static const uint8_t ACN_ID[12] = {0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00}; static const uint32_t VECTOR_ROOT = 4; diff --git a/esphome/components/ee895/__init__.py b/esphome/components/ee895/__init__.py index e69de29bb2d..82043233c31 100644 --- a/esphome/components/ee895/__init__.py +++ b/esphome/components/ee895/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ee895" diff --git a/esphome/components/ee895/ee895.cpp b/esphome/components/ee895/ee895.cpp index 2dd7405ff5f..71ce5b0c706 100644 --- a/esphome/components/ee895/ee895.cpp +++ b/esphome/components/ee895/ee895.cpp @@ -4,7 +4,7 @@ namespace esphome::ee895 { -static const char *const TAG = "ee895"; +ESPHOME_LOG_TAG(TAG, "ee895"); // Serial number is 16 bytes static constexpr size_t EE895_SERIAL_NUMBER_SIZE = 16; diff --git a/esphome/components/ektf2232/__init__.py b/esphome/components/ektf2232/__init__.py index e69de29bb2d..03f83e2a58e 100644 --- a/esphome/components/ektf2232/__init__.py +++ b/esphome/components/ektf2232/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ektf2232" diff --git a/esphome/components/ektf2232/touchscreen/ektf2232.cpp b/esphome/components/ektf2232/touchscreen/ektf2232.cpp index 51532548c1f..81434c9db62 100644 --- a/esphome/components/ektf2232/touchscreen/ektf2232.cpp +++ b/esphome/components/ektf2232/touchscreen/ektf2232.cpp @@ -6,7 +6,7 @@ namespace esphome::ektf2232 { -static const char *const TAG = "ektf2232"; +ESPHOME_LOG_TAG(TAG, "ektf2232"); static const uint8_t SOFT_RESET_CMD[4] = {0x77, 0x77, 0x77, 0x77}; static const uint8_t HELLO[4] = {0x55, 0x55, 0x55, 0x55}; diff --git a/esphome/components/emc2101/__init__.py b/esphome/components/emc2101/__init__.py index 6cd122fb96e..024ff3c8937 100644 --- a/esphome/components/emc2101/__init__.py +++ b/esphome/components/emc2101/__init__.py @@ -6,6 +6,7 @@ from esphome.const import CONF_ID, CONF_INVERTED, CONF_RESOLUTION from esphome.types import ConfigType CODEOWNERS = ["@ellull"] +DOMAIN = "emc2101" DEPENDENCIES = ["i2c"] diff --git a/esphome/components/emc2101/emc2101.cpp b/esphome/components/emc2101/emc2101.cpp index bb041bfd6d5..6a21b4e7b8e 100644 --- a/esphome/components/emc2101/emc2101.cpp +++ b/esphome/components/emc2101/emc2101.cpp @@ -7,7 +7,7 @@ namespace esphome::emc2101 { -static const char *const TAG = "EMC2101"; +ESPHOME_LOG_TAG(TAG, "EMC2101"); static const uint8_t EMC2101_CHIP_ID = 0x16; // EMC2101 default device id from part id static const uint8_t EMC2101_ALT_CHIP_ID = 0x28; // EMC2101 alternate device id from part id diff --git a/esphome/components/emc2101/sensor/emc2101_sensor.cpp b/esphome/components/emc2101/sensor/emc2101_sensor.cpp index bb5eea21f3b..bcc8c01c4ff 100644 --- a/esphome/components/emc2101/sensor/emc2101_sensor.cpp +++ b/esphome/components/emc2101/sensor/emc2101_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::emc2101 { -static const char *const TAG = "EMC2101.sensor"; +ESPHOME_LOG_TAG(TAG, "EMC2101.sensor"); void EMC2101Sensor::dump_config() { ESP_LOGCONFIG(TAG, "Emc2101 sensor:"); diff --git a/esphome/components/emmeti/__init__.py b/esphome/components/emmeti/__init__.py index e69de29bb2d..ec6d2835b0d 100644 --- a/esphome/components/emmeti/__init__.py +++ b/esphome/components/emmeti/__init__.py @@ -0,0 +1 @@ +DOMAIN = "emmeti" diff --git a/esphome/components/emmeti/emmeti.cpp b/esphome/components/emmeti/emmeti.cpp index 82991aeac75..5cf0fd8ae1d 100644 --- a/esphome/components/emmeti/emmeti.cpp +++ b/esphome/components/emmeti/emmeti.cpp @@ -3,7 +3,7 @@ namespace esphome::emmeti { -static const char *const TAG = "emmeti.climate"; +ESPHOME_LOG_TAG(TAG, "emmeti.climate"); // setters uint8_t EmmetiClimate::set_temp_() { diff --git a/esphome/components/emontx/__init__.py b/esphome/components/emontx/__init__.py index 7eae375cd6f..0262f8681f1 100644 --- a/esphome/components/emontx/__init__.py +++ b/esphome/components/emontx/__init__.py @@ -3,6 +3,8 @@ from dataclasses import dataclass, field from esphome import automation import esphome.codegen as cg from esphome.components import uart +from esphome.components.sensor import DOMAIN as SENSOR_DOMAIN +from esphome.components.uart import DOMAIN as UART_DOMAIN import esphome.config_validation as cv from esphome.const import ( CONF_COMMAND, @@ -63,13 +65,13 @@ def final_validate(config: ConfigType) -> None: hub_id = str(config[CONF_ID]) sensor_count = sum( 1 - for s in full_config.get("sensor", []) - if s.get("platform") == "emontx" and str(s.get(CONF_EMONTX_ID)) == hub_id + for s in full_config.get(SENSOR_DOMAIN, []) + if s.get("platform") == DOMAIN and str(s.get(CONF_EMONTX_ID)) == hub_id ) _get_data().sensor_counts[hub_id] = sensor_count # Ensure UART RX buffer size is large enough to handle data bursts from firmware - for uart_conf in full_config["uart"]: + for uart_conf in full_config[UART_DOMAIN]: if uart_conf[CONF_ID] == config[CONF_UART_ID]: current_buffer_size = uart_conf[CONF_RX_BUFFER_SIZE] if current_buffer_size < MINIMUM_RX_BUFFER_SIZE: diff --git a/esphome/components/emontx/emontx.cpp b/esphome/components/emontx/emontx.cpp index 8efc2de7538..ae92b737316 100644 --- a/esphome/components/emontx/emontx.cpp +++ b/esphome/components/emontx/emontx.cpp @@ -4,7 +4,7 @@ namespace esphome::emontx { -static const char *const TAG = "emontx"; +ESPHOME_LOG_TAG(TAG, "emontx"); void EmonTx::setup() { this->buffer_pos_ = 0; } diff --git a/esphome/components/emontx/sensor/__init__.py b/esphome/components/emontx/sensor/__init__.py index 56a7fb8b559..eadba1ea3f8 100644 --- a/esphome/components/emontx/sensor/__init__.py +++ b/esphome/components/emontx/sensor/__init__.py @@ -16,6 +16,7 @@ from esphome.const import ( DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL, STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, UNIT_CELSIUS, @@ -70,7 +71,7 @@ SENSOR_CONFIGS = { "E": { CONF_UNIT_OF_MEASUREMENT: UNIT_WATT_HOURS, CONF_DEVICE_CLASS: DEVICE_CLASS_ENERGY, - CONF_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING, + CONF_STATE_CLASS: STATE_CLASS_TOTAL, CONF_ACCURACY_DECIMALS: 0, }, "V": { diff --git a/esphome/components/emontx/sensor/emontx_sensor.cpp b/esphome/components/emontx/sensor/emontx_sensor.cpp index 142df0150e7..d6f3200e993 100644 --- a/esphome/components/emontx/sensor/emontx_sensor.cpp +++ b/esphome/components/emontx/sensor/emontx_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::emontx { -static const char *const TAG = "emontx_sensor"; +ESPHOME_LOG_TAG(TAG, "emontx_sensor"); void EmonTxSensor::dump_config() { LOG_SENSOR(" ", "EmonTx Sensor", this); } diff --git a/esphome/components/endstop/__init__.py b/esphome/components/endstop/__init__.py index e69de29bb2d..c064448409e 100644 --- a/esphome/components/endstop/__init__.py +++ b/esphome/components/endstop/__init__.py @@ -0,0 +1 @@ +DOMAIN = "endstop" diff --git a/esphome/components/endstop/endstop_cover.cpp b/esphome/components/endstop/endstop_cover.cpp index b3c210a8d8a..25b5b542092 100644 --- a/esphome/components/endstop/endstop_cover.cpp +++ b/esphome/components/endstop/endstop_cover.cpp @@ -5,7 +5,7 @@ namespace esphome::endstop { -static const char *const TAG = "endstop.cover"; +ESPHOME_LOG_TAG(TAG, "endstop.cover"); using namespace esphome::cover; diff --git a/esphome/components/ens160/__init__.py b/esphome/components/ens160/__init__.py index e69de29bb2d..e59458427d9 100644 --- a/esphome/components/ens160/__init__.py +++ b/esphome/components/ens160/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ens160" diff --git a/esphome/components/ens160_base/__init__.py b/esphome/components/ens160_base/__init__.py index 6da0d10af90..b41575c7171 100644 --- a/esphome/components/ens160_base/__init__.py +++ b/esphome/components/ens160_base/__init__.py @@ -22,6 +22,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@vincentscode", "@latonita"] +DOMAIN = "ens160_base" ens160_ns = cg.esphome_ns.namespace("ens160_base") diff --git a/esphome/components/ens160_base/ens160_base.cpp b/esphome/components/ens160_base/ens160_base.cpp index 20c67c34505..b06182e59c0 100644 --- a/esphome/components/ens160_base/ens160_base.cpp +++ b/esphome/components/ens160_base/ens160_base.cpp @@ -20,7 +20,7 @@ namespace esphome::ens160_base { -static const char *const TAG = "ens160"; +ESPHOME_LOG_TAG(TAG, "ens160"); // Datasheet specifies 10ms, but some users report that 10ms is not sufficient for the // sensor to boot and be ready for commands. 11ms seems to be a safe value. diff --git a/esphome/components/ens160_i2c/__init__.py b/esphome/components/ens160_i2c/__init__.py index e69de29bb2d..605acf0f537 100644 --- a/esphome/components/ens160_i2c/__init__.py +++ b/esphome/components/ens160_i2c/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ens160_i2c" diff --git a/esphome/components/ens160_i2c/ens160_i2c.cpp b/esphome/components/ens160_i2c/ens160_i2c.cpp index 1f02ddb7182..b65855b3733 100644 --- a/esphome/components/ens160_i2c/ens160_i2c.cpp +++ b/esphome/components/ens160_i2c/ens160_i2c.cpp @@ -7,7 +7,7 @@ namespace esphome::ens160_i2c { -static const char *const TAG = "ens160_i2c.sensor"; +ESPHOME_LOG_TAG(TAG, "ens160_i2c.sensor"); bool ENS160I2CComponent::read_byte(uint8_t a_register, uint8_t *data) { return I2CDevice::read_byte(a_register, data); diff --git a/esphome/components/ens160_spi/__init__.py b/esphome/components/ens160_spi/__init__.py index e69de29bb2d..88ad91e6328 100644 --- a/esphome/components/ens160_spi/__init__.py +++ b/esphome/components/ens160_spi/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ens160_spi" diff --git a/esphome/components/ens160_spi/ens160_spi.cpp b/esphome/components/ens160_spi/ens160_spi.cpp index 41ab8298dbb..fe46c22c604 100644 --- a/esphome/components/ens160_spi/ens160_spi.cpp +++ b/esphome/components/ens160_spi/ens160_spi.cpp @@ -6,7 +6,7 @@ namespace esphome::ens160_spi { -static const char *const TAG = "ens160_spi.sensor"; +ESPHOME_LOG_TAG(TAG, "ens160_spi.sensor"); inline uint8_t reg_read(uint8_t reg) { return (reg << 1) | 0x01; } diff --git a/esphome/components/ens210/__init__.py b/esphome/components/ens210/__init__.py index e69de29bb2d..58f5d4f25f3 100644 --- a/esphome/components/ens210/__init__.py +++ b/esphome/components/ens210/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ens210" diff --git a/esphome/components/ens210/ens210.cpp b/esphome/components/ens210/ens210.cpp index 11b73afe377..5815fc90382 100644 --- a/esphome/components/ens210/ens210.cpp +++ b/esphome/components/ens210/ens210.cpp @@ -12,7 +12,9 @@ namespace esphome::ens210 { -static const char *const TAG = "ens210"; +ESPHOME_LOG_TAG(TAG, "ens210"); + +static constexpr uint32_t DATA_TIMEOUT_ID = 0; // ENS210 chip constants static const uint8_t ENS210_BOOTING_MS = 2; // Booting time in ms (also after reset, or going to high power) @@ -149,7 +151,7 @@ void ENS210Component::update() { return; } // Wait for measurement to complete - this->set_timeout("data", uint32_t(ENS210_SINGLE_MEASURMENT_CONVERSION_TIME_MS), [this]() { + this->set_timeout(DATA_TIMEOUT_ID, uint32_t(ENS210_SINGLE_MEASURMENT_CONVERSION_TIME_MS), [this]() { int temperature_data, temperature_status, humidity_data, humidity_status; uint8_t data[6]; uint32_t h_val_data, t_val_data; diff --git a/esphome/components/epaper_spi/__init__.py b/esphome/components/epaper_spi/__init__.py index f70ffa95208..da3685c263e 100644 --- a/esphome/components/epaper_spi/__init__.py +++ b/esphome/components/epaper_spi/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@esphome/core"] +DOMAIN = "epaper_spi" diff --git a/esphome/components/epaper_spi/display.py b/esphome/components/epaper_spi/display.py index 3fa504539be..487e8e5ef40 100644 --- a/esphome/components/epaper_spi/display.py +++ b/esphome/components/epaper_spi/display.py @@ -62,6 +62,12 @@ automation.register_apply_action( automation.ApplyCall("request_full_update()"), ) +automation.register_apply_condition( + "epaper_spi.is_updating", + automation.maybe_simple_id({cv.Required(CONF_ID): cv.use_id(EPaperBase)}), + "is_updating()", +) + # Import all models dynamically from the models package for module_info in pkgutil.iter_modules(models.__path__): importlib.import_module(f".models.{module_info.name}", package=__package__) diff --git a/esphome/components/epaper_spi/epaper_spi.cpp b/esphome/components/epaper_spi/epaper_spi.cpp index 1fab038c283..0c1d9b0fafb 100644 --- a/esphome/components/epaper_spi/epaper_spi.cpp +++ b/esphome/components/epaper_spi/epaper_spi.cpp @@ -6,7 +6,7 @@ namespace esphome::epaper_spi { -static const char *const TAG = "epaper_spi"; +ESPHOME_LOG_TAG(TAG, "epaper_spi"); static constexpr size_t EPAPER_MAX_CMD_LOG_BYTES = 128; static constexpr const char *const EPAPER_STATE_STRINGS[] = { diff --git a/esphome/components/epaper_spi/epaper_spi.h b/esphome/components/epaper_spi/epaper_spi.h index 0040c867b20..96755a404ec 100644 --- a/esphome/components/epaper_spi/epaper_spi.h +++ b/esphome/components/epaper_spi/epaper_spi.h @@ -109,6 +109,8 @@ class EPaperBase : public Display, void draw_pixel_at(int x, int y, Color color) override; // Make the next update a full one. Applied when that update starts, so one in progress is not affected. void request_full_update() { this->full_update_requested_ = true; } + // True from the start of an update until the display has refreshed and gone back to sleep. + bool is_updating() const { return this->state_ != EPaperState::IDLE; } protected: int get_height_internal() override { return this->height_; }; diff --git a/esphome/components/epaper_spi/epaper_spi_4bpp.cpp b/esphome/components/epaper_spi/epaper_spi_4bpp.cpp index 820dc3c77ae..0f7f7549d88 100644 --- a/esphome/components/epaper_spi/epaper_spi_4bpp.cpp +++ b/esphome/components/epaper_spi/epaper_spi_4bpp.cpp @@ -4,7 +4,7 @@ namespace esphome::epaper_spi { -static constexpr const char *const TAG = "epaper_spi.4bpp"; +ESPHOME_LOG_TAG(TAG, "epaper_spi.4bpp"); void EPaper4bpp::fill(Color color) { // If clipping is active, fall back to base implementation diff --git a/esphome/components/epaper_spi/epaper_spi_inkplate2.cpp b/esphome/components/epaper_spi/epaper_spi_inkplate2.cpp index fc0d674246f..60e3245deff 100644 --- a/esphome/components/epaper_spi/epaper_spi_inkplate2.cpp +++ b/esphome/components/epaper_spi/epaper_spi_inkplate2.cpp @@ -6,7 +6,7 @@ namespace esphome::epaper_spi { -static constexpr const char *const TAG = "epaper_spi.inkplate2"; +ESPHOME_LOG_TAG(TAG, "epaper_spi.inkplate2"); // Map RGB to the panel's black/white/red via the shared converter. enum class Inkplate2Color : uint8_t { BLACK, WHITE, RED }; diff --git a/esphome/components/epaper_spi/epaper_spi_inkplate6color.cpp b/esphome/components/epaper_spi/epaper_spi_inkplate6color.cpp index b81542b6c14..5bffba35c99 100644 --- a/esphome/components/epaper_spi/epaper_spi_inkplate6color.cpp +++ b/esphome/components/epaper_spi/epaper_spi_inkplate6color.cpp @@ -7,7 +7,7 @@ namespace esphome::epaper_spi { -static constexpr const char *const TAG = "epaper_spi.inkplate6color"; +ESPHOME_LOG_TAG(TAG, "epaper_spi.inkplate6color"); // Native hardware color codes for this panel's 4-bit color values. enum Inkplate6ColorHex : uint8_t { diff --git a/esphome/components/epaper_spi/epaper_spi_jd79660.cpp b/esphome/components/epaper_spi/epaper_spi_jd79660.cpp index 1cd1087c6b6..0de044e8d76 100644 --- a/esphome/components/epaper_spi/epaper_spi_jd79660.cpp +++ b/esphome/components/epaper_spi/epaper_spi_jd79660.cpp @@ -4,7 +4,7 @@ #include "esphome/core/log.h" namespace esphome::epaper_spi { -static constexpr const char *const TAG = "epaper_spi.jd79660"; +ESPHOME_LOG_TAG(TAG, "epaper_spi.jd79660"); /** Pixel color as 2bpp. Must match IC LUT values. */ enum JD79660Color : uint8_t { diff --git a/esphome/components/epaper_spi/epaper_spi_mono.cpp b/esphome/components/epaper_spi/epaper_spi_mono.cpp index fffb2b5e842..b6653038c33 100644 --- a/esphome/components/epaper_spi/epaper_spi_mono.cpp +++ b/esphome/components/epaper_spi/epaper_spi_mono.cpp @@ -5,7 +5,7 @@ #include "esphome/core/log.h" namespace esphome::epaper_spi { -static constexpr const char *const TAG = "epaper_spi.mono"; +ESPHOME_LOG_TAG(TAG, "epaper_spi.mono"); void EPaperMono::refresh_screen(bool partial) { ESP_LOGV(TAG, "Refresh screen"); diff --git a/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp b/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp index f47d37d5504..a2fe9bd6418 100644 --- a/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp +++ b/esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp @@ -4,7 +4,7 @@ #include "esphome/core/log.h" namespace esphome::epaper_spi { -static constexpr const char *const TAG = "epaper_spi.6c"; +ESPHOME_LOG_TAG(TAG, "epaper_spi.6c"); // Native hardware color codes for this panel's 4-bit color values. enum E6Color : uint8_t { diff --git a/esphome/components/epaper_spi/epaper_spi_ssd1677.cpp b/esphome/components/epaper_spi/epaper_spi_ssd1677.cpp index e845a5d5858..a9245fa1c2b 100644 --- a/esphome/components/epaper_spi/epaper_spi_ssd1677.cpp +++ b/esphome/components/epaper_spi/epaper_spi_ssd1677.cpp @@ -6,7 +6,7 @@ #include "esphome/core/log.h" namespace esphome::epaper_spi { -static constexpr const char *const TAG = "epaper_spi.ssd1677"; +ESPHOME_LOG_TAG(TAG, "epaper_spi.ssd1677"); void EPaperSSD1677::setup() { EPaperMono::setup(); diff --git a/esphome/components/epaper_spi/epaper_spi_ssd1677_gray4.cpp b/esphome/components/epaper_spi/epaper_spi_ssd1677_gray4.cpp index 18fbb016f3b..34120c908b6 100644 --- a/esphome/components/epaper_spi/epaper_spi_ssd1677_gray4.cpp +++ b/esphome/components/epaper_spi/epaper_spi_ssd1677_gray4.cpp @@ -4,7 +4,7 @@ namespace esphome::epaper_spi { -static constexpr const char *const TAG = "epaper_spi.ssd1677_gray4"; +ESPHOME_LOG_TAG(TAG, "epaper_spi.ssd1677_gray4"); // Combine two source bytes (4 pixels each, 2 bits per pixel, most significant pixel first) into // one plane byte covering the same 8 pixels (1 bit per pixel), choosing high or low bit diff --git a/esphome/components/epaper_spi/epaper_spi_ssd1683.cpp b/esphome/components/epaper_spi/epaper_spi_ssd1683.cpp index 6fb7e1ac1a6..9b6acf412a3 100644 --- a/esphome/components/epaper_spi/epaper_spi_ssd1683.cpp +++ b/esphome/components/epaper_spi/epaper_spi_ssd1683.cpp @@ -5,7 +5,7 @@ #include "esphome/core/log.h" namespace esphome::epaper_spi { -static constexpr const char *const TAG = "epaper_spi.mono"; +ESPHOME_LOG_TAG(TAG, "epaper_spi.mono"); void EPaperSSD1683::refresh_screen(bool partial) { ESP_LOGV(TAG, "Refresh screen"); diff --git a/esphome/components/epaper_spi/epaper_spi_t133a01.cpp b/esphome/components/epaper_spi/epaper_spi_t133a01.cpp index 95d1fcb4846..bd5a4c9b6e2 100644 --- a/esphome/components/epaper_spi/epaper_spi_t133a01.cpp +++ b/esphome/components/epaper_spi/epaper_spi_t133a01.cpp @@ -6,7 +6,7 @@ namespace esphome::epaper_spi { -static constexpr const char *const TAG = "epaper_spi.t133a01"; +ESPHOME_LOG_TAG(TAG, "epaper_spi.t133a01"); // Color indices used in the 4bpp buffer (sprite-side) // These MUST match the Arduino GFX TFT_eSPI.h color definitions and diff --git a/esphome/components/epaper_spi/epaper_spi_uc8179.cpp b/esphome/components/epaper_spi/epaper_spi_uc8179.cpp index 2a4ff2969aa..328cd833299 100644 --- a/esphome/components/epaper_spi/epaper_spi_uc8179.cpp +++ b/esphome/components/epaper_spi/epaper_spi_uc8179.cpp @@ -6,7 +6,7 @@ namespace esphome::epaper_spi { -static constexpr const char *const TAG = "epaper_spi.uc8179"; +ESPHOME_LOG_TAG(TAG, "epaper_spi.uc8179"); bool EPaperUC8179::initialise(bool partial) { EPaperBase::initialise(partial); // send the model init sequence diff --git a/esphome/components/epaper_spi/epaper_waveshare.cpp b/esphome/components/epaper_spi/epaper_waveshare.cpp index 7a7b4e22d3a..d4c7ba5fdb5 100644 --- a/esphome/components/epaper_spi/epaper_waveshare.cpp +++ b/esphome/components/epaper_spi/epaper_waveshare.cpp @@ -2,7 +2,7 @@ namespace esphome::epaper_spi { -static const char *const TAG = "epaper_spi.waveshare"; +ESPHOME_LOG_TAG(TAG, "epaper_spi.waveshare"); bool EpaperWaveshare::initialise(bool partial) { EPaperBase::initialise(partial); diff --git a/esphome/components/epaper_spi/epaper_weact_3c.cpp b/esphome/components/epaper_spi/epaper_weact_3c.cpp index ad2021ed64f..90cf6af7b2f 100644 --- a/esphome/components/epaper_spi/epaper_weact_3c.cpp +++ b/esphome/components/epaper_spi/epaper_weact_3c.cpp @@ -3,7 +3,7 @@ namespace esphome::epaper_spi { -static constexpr const char *const TAG = "epaper_weact_3c"; +ESPHOME_LOG_TAG(TAG, "epaper_weact_3c"); enum class BwrState : uint8_t { BWR_BLACK, diff --git a/esphome/components/epaper_spi/models/ssd1683.py b/esphome/components/epaper_spi/models/ssd1683.py index b43168a336e..eb51ba1f2af 100644 --- a/esphome/components/epaper_spi/models/ssd1683.py +++ b/esphome/components/epaper_spi/models/ssd1683.py @@ -26,3 +26,15 @@ goodisplay_gdey042t81 = ssd1683.extend( width=400, height=300, ) + +# The SSD1681 shares the SSD1683 command set for everything this driver uses: +# the same 0x01/0x11/0x18 initialisation, the same 0x44/0x45 window and +# 0x4E/0x4F cursor registers with a byte-addressed X and a 16-bit Y, and the +# same 0x22/0x20 update. It only differs in the panel sizes it drives. +ssd1681 = SSD1683("ssd1681") + +waveshare_1_54in_v2 = ssd1681.extend( + "waveshare-1.54in-v2", + width=200, + height=200, +) diff --git a/esphome/components/es7210/__init__.py b/esphome/components/es7210/__init__.py index e69de29bb2d..0038d290408 100644 --- a/esphome/components/es7210/__init__.py +++ b/esphome/components/es7210/__init__.py @@ -0,0 +1 @@ +DOMAIN = "es7210" diff --git a/esphome/components/es7210/es7210.cpp b/esphome/components/es7210/es7210.cpp index 5afc22aec4f..0354f719ef5 100644 --- a/esphome/components/es7210/es7210.cpp +++ b/esphome/components/es7210/es7210.cpp @@ -6,7 +6,7 @@ namespace esphome::es7210 { -static const char *const TAG = "es7210"; +ESPHOME_LOG_TAG(TAG, "es7210"); static const size_t MCLK_DIV_FRE = 256; diff --git a/esphome/components/es7243e/__init__.py b/esphome/components/es7243e/__init__.py index e69de29bb2d..6968b84c933 100644 --- a/esphome/components/es7243e/__init__.py +++ b/esphome/components/es7243e/__init__.py @@ -0,0 +1 @@ +DOMAIN = "es7243e" diff --git a/esphome/components/es7243e/es7243e.cpp b/esphome/components/es7243e/es7243e.cpp index fc3cba7ae42..1bfa6933ddc 100644 --- a/esphome/components/es7243e/es7243e.cpp +++ b/esphome/components/es7243e/es7243e.cpp @@ -8,7 +8,7 @@ namespace esphome::es7243e { -static const char *const TAG = "es7243e"; +ESPHOME_LOG_TAG(TAG, "es7243e"); // Mark the component as failed; use only in setup #define ES7243E_ERROR_FAILED(func) \ diff --git a/esphome/components/es8156/__init__.py b/esphome/components/es8156/__init__.py index e69de29bb2d..f1e7f4d4e7b 100644 --- a/esphome/components/es8156/__init__.py +++ b/esphome/components/es8156/__init__.py @@ -0,0 +1 @@ +DOMAIN = "es8156" diff --git a/esphome/components/es8156/audio_dac.py b/esphome/components/es8156/audio_dac.py index 305aa921255..ebba75e70dd 100644 --- a/esphome/components/es8156/audio_dac.py +++ b/esphome/components/es8156/audio_dac.py @@ -1,6 +1,7 @@ import esphome.codegen as cg from esphome.components import i2c from esphome.components.audio_dac import AudioDac +from esphome.components.speaker import DOMAIN as SPEAKER_DOMAIN import esphome.config_validation as cv from esphome.const import CONF_AUDIO_DAC, CONF_BITS_PER_SAMPLE, CONF_ID import esphome.final_validate as fv @@ -27,7 +28,7 @@ def _final_validate(config: ConfigType) -> None: full_config = fv.full_config.get() # Check all speaker configurations for ones that reference this es8156 - speaker_configs = full_config.get("speaker", []) + speaker_configs = full_config.get(SPEAKER_DOMAIN, []) for speaker_config in speaker_configs: audio_dac_id = speaker_config.get(CONF_AUDIO_DAC) if ( diff --git a/esphome/components/es8156/es8156.cpp b/esphome/components/es8156/es8156.cpp index 03d9713df95..467402e704c 100644 --- a/esphome/components/es8156/es8156.cpp +++ b/esphome/components/es8156/es8156.cpp @@ -6,7 +6,7 @@ namespace esphome::es8156 { -static const char *const TAG = "es8156"; +ESPHOME_LOG_TAG(TAG, "es8156"); // Mark the component as failed; use only in setup #define ES8156_ERROR_FAILED(func) \ diff --git a/esphome/components/es8311/__init__.py b/esphome/components/es8311/__init__.py index e69de29bb2d..975291c4a7e 100644 --- a/esphome/components/es8311/__init__.py +++ b/esphome/components/es8311/__init__.py @@ -0,0 +1 @@ +DOMAIN = "es8311" diff --git a/esphome/components/es8311/es8311.cpp b/esphome/components/es8311/es8311.cpp index 9a4da3744a1..96b644ee780 100644 --- a/esphome/components/es8311/es8311.cpp +++ b/esphome/components/es8311/es8311.cpp @@ -6,7 +6,7 @@ namespace esphome::es8311 { -static const char *const TAG = "es8311"; +ESPHOME_LOG_TAG(TAG, "es8311"); // Mark the component as failed; use only in setup #define ES8311_ERROR_FAILED(func) \ diff --git a/esphome/components/es8388/__init__.py b/esphome/components/es8388/__init__.py index e69de29bb2d..e8e2e361bac 100644 --- a/esphome/components/es8388/__init__.py +++ b/esphome/components/es8388/__init__.py @@ -0,0 +1 @@ +DOMAIN = "es8388" diff --git a/esphome/components/es8388/es8388.cpp b/esphome/components/es8388/es8388.cpp index 0b972402308..b57949ccd0a 100644 --- a/esphome/components/es8388/es8388.cpp +++ b/esphome/components/es8388/es8388.cpp @@ -6,7 +6,7 @@ namespace esphome::es8388 { -static const char *const TAG = "es8388"; +ESPHOME_LOG_TAG(TAG, "es8388"); // Mark the component as failed; use only in setup #define ES8388_ERROR_FAILED(func) \ @@ -231,7 +231,7 @@ bool ES8388::set_dac_output(DacOutputLine line) { optional ES8388::get_dac_power() { uint8_t dac_power; if (!this->read_byte(ES8388_DACPOWER, &dac_power)) { - this->status_momentary_warning("dacpower_read"); + this->status_momentary_warning(); return {}; } switch (dac_power) { @@ -274,7 +274,7 @@ bool ES8388::set_adc_input_mic(AdcInputMicLine line) { optional ES8388::get_mic_input() { uint8_t mic_input; if (!this->read_byte(ES8388_ADCCONTROL2, &mic_input)) { - this->status_momentary_warning("adccontrol2_read"); + this->status_momentary_warning(); return {}; } switch (mic_input) { diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 6a693eff89d..aa4f78de69d 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -1,6 +1,6 @@ from collections.abc import Callable, Iterable import contextlib -from dataclasses import dataclass +from dataclasses import dataclass, field import itertools import logging import os @@ -15,7 +15,6 @@ from esphome.components.const import ( CONF_ENABLE_OTA_DOWNGRADE_PROTECTION, CONF_IGNORE_NOT_FOUND, ) -from esphome.config_helpers import filter_source_files_from_defines import esphome.config_validation as cv from esphome.const import ( CONF_ADVANCED, @@ -57,7 +56,7 @@ from esphome.const import ( from esphome.core import CORE, EsphomeError, HexInt from esphome.core.config import BOARD_MAX_LENGTH from esphome.coroutine import CoroPriority, coroutine_with_priority -from esphome.espidf.component import generate_idf_components +from esphome.espidf.component import IDFComponent, generate_idf_components import esphome.final_validate as fv from esphome.helpers import copy_file_if_changed, rmtree, write_file_if_changed from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor @@ -76,6 +75,7 @@ from .const import ( KEY_FLASH_SIZE, KEY_FULL_CERT_BUNDLE, KEY_IDF_VERSION, + KEY_MBEDTLS_SDKCONFIG, KEY_NETWORK_SDKCONFIG, KEY_PATH, KEY_REF, @@ -105,6 +105,7 @@ from .gpio import esp32_pin_to_code # noqa: F401 _LOGGER = logging.getLogger(__name__) AUTO_LOAD = ["preferences"] CODEOWNERS = ["@esphome/core"] +DOMAIN = "esp32" IS_TARGET_PLATFORM = True CONF_ASSERTION_LEVEL = "assertion_level" @@ -237,7 +238,7 @@ DEFAULT_EXCLUDED_IDF_COMPONENTS = ( "cmock", # Unit testing mock framework - ESPHome doesn't use IDF's testing "console", # Console REPL - unused by ESPHome; espressif/mdns pulls it back when configured "driver", # Legacy driver shim - only needed by esp32_touch, esp32_can for legacy headers - "esp-tls", # TLS wrapper - re-included by http_request, mqtt, web_server_idf + "esp-tls", # TLS wrapper - re-included by request_tls() "esp_adc", # ADC driver - only needed by adc component "esp_coex", # WiFi/BT coexistence - re-included by esp32_ble_tracker, zigbee; esp_wifi/bt pull it back "esp_driver_cam", # Camera driver - the esp32-camera managed component pulls it back @@ -742,9 +743,10 @@ def is_idf_sdkconfig_option_enabled(name: str) -> bool: def set_idf_sdkconfig_default(name: str, value: SdkconfigValueType) -> None: """Set an sdkconfig option unless it is already set. - For the FINAL priority reconcile jobs: they run after every to_code, - including the user's sdkconfig_options, and must not override an - existing value. + User sdkconfig_options take precedence regardless of to_code order: + esp32.to_code applies them unconditionally, and this helper preserves + values that are already set. FINAL priority reconcile jobs use the same + guard because they run after every to_code, including the user's options. """ if name not in CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS]: add_idf_sdkconfig_option(name, value) @@ -811,6 +813,97 @@ def request_software_coexistence() -> None: include_builtin_idf_component("esp_coex") +@dataclass +class MbedtlsSdkconfigData: + """Inputs for the mbedTLS sdkconfig flags, reconciled at FINAL. + + Components call the require_mbedtls_*() helpers (and request_tls(), which + sets tls_required and also un-excludes the esp-tls component) rather than + writing the CONFIG_MBEDTLS_* flags directly; _reconcile_mbedtls_sdkconfig() + decides the final values once every to_code has run. + """ + + ecp_required: bool = False # ECDH/ECDSA without TLS (openthread SRP host key) + tls_required: bool = False # mbedTLS TLS role needed without the esp-tls wrapper + tls_server_required: bool = False # server-side TLS/DTLS handshake + tls_extras_required: set[str] = field(default_factory=set) # kept TLS extras + peer_cert_required: bool = False # keep the peer certificate after the handshake + pkcs7_required: bool = False # PKCS#7 parsing + sha512_required: bool = False # SHA-384/SHA-512 + # esp32 advanced disable_mbedtls_* options + disable_tls: bool = True + disable_tls_server: bool = True + disable_tls_extras: bool = True + disable_peer_cert: bool = True + disable_pkcs7: bool = True + + +def _mbedtls_sdkconfig() -> MbedtlsSdkconfigData: + data = CORE.data[KEY_ESP32] + if KEY_MBEDTLS_SDKCONFIG not in data: + data[KEY_MBEDTLS_SDKCONFIG] = MbedtlsSdkconfigData() + return data[KEY_MBEDTLS_SDKCONFIG] + + +# IDF components that reference esp_tls symbols from their own code, so +# re-including any of them is an implicit TLS request. +_ESP_TLS_LINKING_COMPONENTS = ( + "esp-tls", + "esp_http_client", + "esp_https_ota", + "esp_https_server", + "esp_local_ctrl", + "mqtt", +) + + +def _mbedtls_tls_required() -> bool: + """TLS stays in the build: requested, or an esp_tls-linking component was re-included. + + The exclusion-set signal keeps external components working whose only + obligation before request_tls() existed was include_builtin_idf_component() + of esp-tls or of a component that links it (esp_http_client, IDF mqtt). + """ + if _mbedtls_sdkconfig().tls_required: + return True + excluded = CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS] + return any(name not in excluded for name in _ESP_TLS_LINKING_COMPONENTS) + + +def _mbedtls_tls_compiled_out() -> bool: + """True when this build removes the TLS stack from mbedTLS entirely.""" + return ( + not CORE.using_arduino + and _mbedtls_sdkconfig().disable_tls + and not _mbedtls_tls_required() + ) + + +def require_mbedtls_tls() -> None: + """Keep the mbedTLS TLS stack without compiling the esp-tls wrapper. + + For code that talks to mbedTLS directly (wpa_supplicant's EAP client). + Components that use esp_tls call request_tls() instead. + """ + _mbedtls_sdkconfig().tls_required = True + + +def request_tls() -> None: + """Request the mbedTLS TLS stack and the esp-tls wrapper. + + Without a request TLS and its ECP/PEM-write/CRL/CSR crypto compile out; + hashes, AES and RSA stay available. + """ + require_mbedtls_tls() + include_builtin_idf_component("esp-tls") + + +def request_http_client() -> None: + """Request ESP-IDF's HTTP client; it links esp_tls even for plain http.""" + include_builtin_idf_component("esp_http_client") + request_tls() + + def add_idf_component( *, name: str, @@ -1846,6 +1939,7 @@ CONF_DISABLE_OCD_AWARE = "disable_ocd_aware" 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_TLS = "disable_mbedtls_tls" 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" @@ -1861,12 +1955,7 @@ KEY_VFS_TERMIOS_REQUIRED = "vfs_termios_required" # Feature requirement tracking - components can call require_* functions to re-enable # These are stored in CORE.data[KEY_ESP32] dict 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" KEY_LIBC_PICOLIBC_NEWLIB_COMPAT_REQUIRED = "libc_picolibc_newlib_compat_required" @@ -1905,6 +1994,9 @@ def require_certificate_bundle() -> None: certificates (http_request, audio streaming) call this so the bundle is compiled and gen_crt_bundle runs only when something uses it. """ + # esp_crt_bundle.c lives in the mbedtls component and calls + # mbedtls_ssl_conf_*, so a bundle needs the TLS role but not esp-tls. + require_mbedtls_tls() CORE.data[KEY_ESP32][KEY_CERT_BUNDLE] = True @@ -1930,33 +2022,37 @@ def require_usb_serial_jtag_secondary() -> None: CORE.data[KEY_ESP32][KEY_USB_SERIAL_JTAG_SECONDARY_REQUIRED] = True -def require_mbedtls_peer_cert() -> None: - """Mark that mbedTLS peer certificate retention is required by a component. +def require_mbedtls_ecp() -> None: + """Keep mbedTLS elliptic curve support (ECDH/ECDSA) without requesting TLS. - Call this from components that need access to the peer certificate after - the TLS handshake is complete. This prevents CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE - from being disabled. + Call this from components that sign or verify with ECDSA outside a TLS + handshake (openthread's SRP host key). WiFi, Bluetooth and secure boot + select it through Kconfig on their own. """ - CORE.data[KEY_ESP32][KEY_MBEDTLS_PEER_CERT_REQUIRED] = True + _mbedtls_sdkconfig().ecp_required = True + + +def require_mbedtls_peer_cert() -> None: + """Keep the peer certificate after the TLS handshake (CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE). + + A user sdkconfig_options value takes precedence. + """ + _mbedtls_sdkconfig().peer_cert_required = True def require_mbedtls_pkcs7() -> None: - """Mark that mbedTLS PKCS#7 support is required by a component. - - Call this from components that need PKCS#7 certificate validation. - This prevents CONFIG_MBEDTLS_PKCS7_C from being disabled. - """ - CORE.data[KEY_ESP32][KEY_MBEDTLS_PKCS7_REQUIRED] = True + """Keep mbedTLS PKCS#7 support (CONFIG_MBEDTLS_PKCS7_C). A user sdkconfig_options value takes precedence.""" + _mbedtls_sdkconfig().pkcs7_required = True def require_mbedtls_tls_server() -> None: - """Mark that the mbedTLS server-side TLS/DTLS handshake is required. + """Widen the TLS role to include the server-side handshake. - Call this from components that accept TLS connections (OpenThread's DTLS - commissioner does). This prevents CONFIG_MBEDTLS_TLS_CLIENT_ONLY from - being selected. + Only affects builds where TLS is compiled in; it prevents + CONFIG_MBEDTLS_TLS_CLIENT_ONLY from being selected. A component that + actually opens or accepts TLS/DTLS sessions must also call request_tls(). """ - CORE.data[KEY_ESP32][KEY_MBEDTLS_TLS_SERVER_REQUIRED] = True + _mbedtls_sdkconfig().tls_server_required = True def require_mbedtls_tls_extras(options: Iterable[str] | None = None) -> None: @@ -1969,18 +2065,14 @@ def require_mbedtls_tls_extras(options: Iterable[str] | None = None) -> None: 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) + _mbedtls_sdkconfig().tls_extras_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. - - Call this from components that need to verify TLS certificates or signatures - using SHA-384 or SHA-512 algorithms. This prevents CONFIG_MBEDTLS_SHA384_C - and CONFIG_MBEDTLS_SHA512_C from being disabled. - """ - CORE.data[KEY_ESP32][KEY_MBEDTLS_SHA512_REQUIRED] = True + """Keep mbedTLS SHA-384/SHA-512 (CONFIG_MBEDTLS_SHA384_C / CONFIG_MBEDTLS_SHA512_C).""" + _mbedtls_sdkconfig().sha512_required = True def idf_version() -> cv.Version: @@ -2134,6 +2226,7 @@ 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, 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, @@ -2499,34 +2592,103 @@ MBEDTLS_TLS_ROLE_OPTIONS = ( ) -@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. +# User sdkconfig_options that mean "keep TLS on" when set to y. The +# OpenThread entries compile its DTLS secure transport in, which links +# mbedtls_ssl_*. +_MBEDTLS_TLS_ON_OPTIONS = ( + "CONFIG_MBEDTLS_TLS_ENABLED", + "CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT", + "CONFIG_MBEDTLS_TLS_SERVER_ONLY", + "CONFIG_MBEDTLS_TLS_CLIENT_ONLY", + "CONFIG_OPENTHREAD_COMMISSIONER", + "CONFIG_OPENTHREAD_JOINER", + "CONFIG_OPENTHREAD_BORDER_AGENT_ENABLE", + # Border router defaults the border agent (and its DTLS) on. + "CONFIG_OPENTHREAD_BORDER_ROUTER", + # Enterprise WiFi selects TLS back on (see the TLS-off block). + "CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT", +) +# Any user option under these prefixes only makes sense with TLS compiled in. +_TLS_OPTION_PREFIXES = ("CONFIG_ESP_TLS_", "CONFIG_MBEDTLS_SSL_", "CONFIG_ESP_HTTPS_") - 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. + +def _user_sdkconfig_wants_tls(options: dict[str, Any]) -> bool: + """True when sdkconfig_options turn TLS on or tune something under it; an `n` is never a request.""" + return any( + (name in _MBEDTLS_TLS_ON_OPTIONS and value == "y") + or (name == "CONFIG_MBEDTLS_TLS_DISABLED" and value == "n") + or (name.startswith(_TLS_OPTION_PREFIXES) and value != "n") + for name, value in options.items() + ) + + +@coroutine_with_priority(CoroPriority.FINAL) +async def _reconcile_mbedtls_sdkconfig() -> None: + """Reconcile the mbedTLS sdkconfig flags after every request_tls() / require_mbedtls_*() call. + + mbedtls cannot be excluded from an IDF build (bootloader_support needs its + SHA-256), but with no TLS user the ssl_*.c sources and the TLS-only crypto + compile to empty objects. When TLS stays in, it is trimmed to the client + role and the legacy handshake extras are dropped. User sdkconfig_options + win; a user-chosen TLS role leaves the whole choice alone. """ - 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) + data = _mbedtls_sdkconfig() + idf6 = idf_version() >= cv.Version(6, 0, 0) + opts = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] + + if _mbedtls_tls_compiled_out(): + # IDF 6 made CONFIG_MBEDTLS_TLS_ENABLED a normal bool; on IDF 5 it has + # no prompt and is only reachable through the "None" TLS role choice. + if idf6: + set_idf_sdkconfig_default("CONFIG_MBEDTLS_TLS_ENABLED", False) + else: + set_idf_sdkconfig_default("CONFIG_MBEDTLS_TLS_DISABLED", True) + # Enterprise WiFi selects TLS back on; wifi writes this itself, but + # esp_wifi can also be in the build without a wifi: block (openthread). + set_idf_sdkconfig_default("CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT", False) + # WiFi (ESP_WIFI_MBEDTLS_CRYPTO) and Bluetooth deliberately stay on + # the select-wins path: an unconditional request from request_wifi() + # would defeat the ECP trim for users who disable that select. + if not data.ecp_required: + set_idf_sdkconfig_default("CONFIG_MBEDTLS_ECP_C", False) + set_idf_sdkconfig_default("CONFIG_MBEDTLS_PEM_WRITE_C", False) + set_idf_sdkconfig_default("CONFIG_MBEDTLS_X509_CRL_PARSE_C", False) + set_idf_sdkconfig_default("CONFIG_MBEDTLS_X509_CSR_PARSE_C", False) + elif ( + # TLS stays in: trim it to the client role unless a component accepts + # TLS connections or the user already chose a role. + data.disable_tls_server + and not data.tls_server_required + and not any(option in opts 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()) + + # The extras run either way: CCM and deterministic ECDSA are plain + # crypto, not TLS-gated, so they matter even with TLS compiled out. + if data.disable_tls_extras: for option in MBEDTLS_TLS_EXTRA_OPTIONS: - if option not in required: + if option not in data.tls_extras_required: set_idf_sdkconfig_default(option, False) + # Keeping the peer certificate costs ~4KB heap per connection. + if data.peer_cert_required: + set_idf_sdkconfig_default("CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE", True) + elif data.disable_peer_cert: + set_idf_sdkconfig_default("CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE", False) + + if data.pkcs7_required: + set_idf_sdkconfig_default("CONFIG_MBEDTLS_PKCS7_C", True) + elif data.disable_pkcs7: + set_idf_sdkconfig_default("CONFIG_MBEDTLS_PKCS7_C", False) + + # SHA-384 shares the SHA-512 compression function, so both go together. + # Only IDF 6.0's PSA engine links a ~3KB software fallback for them; on + # IDF 5 they are a single hardware-only option with no code size cost. + if idf6 and not data.sha512_required: + set_idf_sdkconfig_default("CONFIG_MBEDTLS_SHA384_C", False) + set_idf_sdkconfig_default("CONFIG_MBEDTLS_SHA512_C", False) + @coroutine_with_priority(CoroPriority.FINAL) async def _reconcile_network_sdkconfig() -> None: @@ -2763,6 +2925,11 @@ async def to_code(config): # produce reproducible outputs and downstream tooling can reuse artifacts. add_idf_sdkconfig_option("CONFIG_APP_REPRODUCIBLE_BUILD", True) + # Static destructors never run, so skip registering them. See atexit_stubs.cpp. + # --undefined: libsrc.a is scanned before the IDF libraries that also register them. + cg.add_build_flag("-Wl,--wrap=__cxa_atexit") + cg.add_build_flag("-Wl,--undefined=__wrap___cxa_atexit") + if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: cg.add_build_flag("-DUSE_ESP_IDF") cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF") @@ -2873,6 +3040,8 @@ async def to_code(config): "CONFIG_ESP32P4_SELECTS_REV_LESS_V3", config.get(CONF_ENGINEERING_SAMPLE, False), ) + # Work around ESP-IDF bug: see https://github.com/espressif/esp-idf/issues/19020 + add_idf_sdkconfig_option("CONFIG_ESP_MAIN_TASK_STACK_SIZE", 8192) # ESP32-C2 defaults to the ROM's newlib "nano" printf, which does not # understand %zu or %lld and crashes on any %s that follows one. @@ -2910,6 +3079,10 @@ async def to_code(config): # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000) + # Main loop wakes use notification index 1; index 0 stays free for ESP-IDF waits + # such as pthread_join(), which a stray main-loop wake would otherwise end early. + add_idf_sdkconfig_option("CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES", 2) + # Place non-ISR FreeRTOS functions into flash instead of IRAM # This saves up to 8KB of IRAM. ISR-safe functions (FromISR variants) stay in IRAM. # In ESP-IDF 6.0 this becomes the default and CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH @@ -3147,6 +3320,10 @@ async def to_code(config): for key, flag in SIGNING_SCHEMES.items(): add_idf_sdkconfig_option(flag, scheme == key) + if scheme in (SIGNING_SCHEME_ECDSA256, SIGNING_SCHEME_ECDSA_V1): + # SECURE_SIGNED_APPS selects ECP in Kconfig anyway; requesting it + # keeps the resolved sdkconfig consistent with what ESPHome wrote. + require_mbedtls_ecp() if CONF_SIGNING_KEY in signed_ota: # Private key mode — auto-sign binaries during build @@ -3217,38 +3394,6 @@ async def to_code(config): if advanced[CONF_DISABLE_DEV_NULL_VFS]: add_idf_sdkconfig_option("CONFIG_VFS_INITIALIZE_DEV_NULL", False) - # Disable keeping peer certificate after TLS handshake - # Saves ~4KB heap per connection, but prevents certificate inspection after handshake - # Components that need it can call require_mbedtls_peer_cert() - if CORE.data[KEY_ESP32].get(KEY_MBEDTLS_PEER_CERT_REQUIRED, False): - add_idf_sdkconfig_option("CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE", True) - elif advanced[CONF_DISABLE_MBEDTLS_PEER_CERT]: - add_idf_sdkconfig_option("CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE", False) - - # Disable PKCS#7 support in mbedTLS - # Only needed for specific certificate validation scenarios - # Components that need it can call require_mbedtls_pkcs7() - if CORE.data[KEY_ESP32].get(KEY_MBEDTLS_PKCS7_REQUIRED, False): - # Component called require_mbedtls_pkcs7() - enable regardless of user setting - add_idf_sdkconfig_option("CONFIG_MBEDTLS_PKCS7_C", True) - elif advanced[CONF_DISABLE_MBEDTLS_PKCS7]: - add_idf_sdkconfig_option("CONFIG_MBEDTLS_PKCS7_C", False) - - # Disable SHA-384 and SHA-512 in mbedTLS - # ESPHome doesn't use either algorithm. SHA-384 shares the same - # compression function as SHA-512 (mbedtls_internal_sha512_process), - # so both must be disabled to eliminate the ~3KB software fallback - # that IDF 6.0's PSA parallel engine always links in. - # On IDF < 6.0 these are a single config and hardware-only (no - # software fallback), so there was no code size cost to leaving - # them enabled. - # Components that need SHA-384/SHA-512 can call require_mbedtls_sha512() - if idf_version() >= cv.Version(6, 0, 0) and not CORE.data[KEY_ESP32].get( - KEY_MBEDTLS_SHA512_REQUIRED, False - ): - add_idf_sdkconfig_option("CONFIG_MBEDTLS_SHA384_C", False) - add_idf_sdkconfig_option("CONFIG_MBEDTLS_SHA512_C", False) - # FINAL priority: runs after every require_libc_picolibc_newlib_compat() call CORE.add_job(_set_libc_picolibc_newlib_compat) @@ -3258,12 +3403,14 @@ 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 priority: runs after every request_tls() / require_mbedtls_*() call + mbedtls = _mbedtls_sdkconfig() + mbedtls.disable_tls = advanced[CONF_DISABLE_MBEDTLS_TLS] + mbedtls.disable_tls_server = advanced[CONF_DISABLE_MBEDTLS_TLS_SERVER] + mbedtls.disable_tls_extras = advanced[CONF_DISABLE_MBEDTLS_TLS_EXTRAS] + mbedtls.disable_peer_cert = advanced[CONF_DISABLE_MBEDTLS_PEER_CERT] + mbedtls.disable_pkcs7 = advanced[CONF_DISABLE_MBEDTLS_PKCS7] + CORE.add_job(_reconcile_mbedtls_sdkconfig) # 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. @@ -3296,6 +3443,8 @@ async def to_code(config): # so it still gets the CMN variant pinned. if conf[CONF_SDKCONFIG_OPTIONS].get("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE") == "y": require_certificate_bundle() + if _user_sdkconfig_wants_tls(conf[CONF_SDKCONFIG_OPTIONS]): + request_tls() # Components from YAML are added in a separate coroutine with FINAL priority # Schedule it to run after all other components @@ -3537,6 +3686,30 @@ def _write_idf_component_yml(): yml_path = CORE.relative_build_path("src/idf_component.yml") dependencies: dict[str, dict] = {} + converted: list[IDFComponent] = [] + if CORE.using_toolchain_esp_idf: + # Convert the PlatformIO libraries to ESP-IDF components as a batch so + # PlatformIO resolves the whole dependency tree at once -- deduplicating + # shared transitive deps (e.g. esphome/libsodium pulled by both noise-c + # and esp_wireguard) to a single version instead of clashing + # override_path entries. + libraries = [ + library + for name, library in CORE.platformio_libraries.items() + # Don't process arduino libraries + if name not in ARDUINO_DISABLED_LIBRARIES + ] + # A library also declared as a managed component is not converted too, or + # IDF sees the same requirement twice; converted components reach it through + # ${ESPHOME_PROJECT_MANAGED_COMPONENTS}. + managed = set(CORE.data[KEY_ESP32].get(KEY_COMPONENTS, {})) + converted = generate_idf_components(libraries, managed=managed) + # IDF names a component after its directory and a later registration of the + # same name replaces the earlier one, so a stub beside a converted library of + # the same name (espressif/libsodium vs esphome/libsodium) would win or lose + # on path order. Such a stub points at the converted library instead. + converted_by_name = {component.path.name: component for component in converted} + # For Arduino builds, override unused managed components from the Arduino framework # by pointing them to empty stub directories using override_path # This prevents the IDF component manager from downloading the real components @@ -3560,8 +3733,17 @@ def _write_idf_component_yml(): # always writes, and ninja keeps triggering CMake re-runs on # otherwise-cached rebuilds. for component_name in sorted(components_to_stub): + stub_name = _idf_component_stub_name(component_name) + stub_path = stubs_dir / stub_name + if (component := converted_by_name.get(stub_name)) is not None: + if stub_path.exists(): + rmtree(stub_path) + dependencies[_idf_component_dep_name(component_name)] = { + "version": "*", + "override_path": str(component.path), + } + continue # Create stub directory with minimal CMakeLists.txt - stub_path = stubs_dir / _idf_component_stub_name(component_name) stub_path.mkdir(exist_ok=True) stub_cmake = stub_path / "CMakeLists.txt" if not stub_cmake.exists(): @@ -3603,26 +3785,10 @@ def _write_idf_component_yml(): ref=str(CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]), ) - if CORE.using_toolchain_esp_idf: - # Convert the PlatformIO libraries to ESP-IDF components as a batch so - # PlatformIO resolves the whole dependency tree at once -- deduplicating - # shared transitive deps (e.g. esphome/libsodium pulled by both noise-c - # and esp_wireguard) to a single version instead of clashing - # override_path entries. - libraries = [ - library - for name, library in CORE.platformio_libraries.items() - # Don't process arduino libraries - if name not in ARDUINO_DISABLED_LIBRARIES - ] - # A library also declared as a managed component is not converted too, or - # IDF sees the same requirement twice; converted components reach it through - # ${ESPHOME_PROJECT_MANAGED_COMPONENTS}. - managed = set(CORE.data[KEY_ESP32].get(KEY_COMPONENTS, {})) - for component in generate_idf_components(libraries, managed=managed): - dependencies[component.get_sanitized_name()] = { - "override_path": str(component.path) - } + for component in converted: + dependencies[component.get_sanitized_name()] = { + "override_path": str(component.path) + } if CORE.data[KEY_ESP32][KEY_COMPONENTS]: components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS] @@ -3800,10 +3966,3 @@ def process_stacktrace(config, line, backtrace_state): _decode_pc(config, addr.group()) return backtrace_state - - -# gpio.cpp only implements ESP32InternalGPIOPin and its ISR helpers, which -# are instantiated solely by the pin schema codegen (esp32_pin_to_code) -FILTER_SOURCE_FILES = filter_source_files_from_defines( - {"gpio.cpp": "USE_ESP32_INTERNAL_GPIO"} -) diff --git a/esphome/components/esp32/atexit_stubs.cpp b/esphome/components/esp32/atexit_stubs.cpp new file mode 100644 index 00000000000..1230a566814 --- /dev/null +++ b/esphome/components/esp32/atexit_stubs.cpp @@ -0,0 +1,20 @@ +/* + * Linker wrap stub for __cxa_atexit(). + * + * GCC registers the destructor of every global and function local static + * object with __cxa_atexit(). The firmware never returns from main or calls + * exit(), so these destructors never run, but the first registration links + * newlib's exit handler tables (about 400 bytes of RAM on ESP32). + */ + +#include "esphome/core/defines.h" + +#ifdef USE_ESP32 + +namespace esphome::esp32 {} + +// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming) +extern "C" int __wrap___cxa_atexit(void (*)(void *), void *, void *) { return 0; } +// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming) + +#endif // USE_ESP32 diff --git a/esphome/components/esp32/const.py b/esphome/components/esp32/const.py index a0c9809c50c..33751ea7d52 100644 --- a/esphome/components/esp32/const.py +++ b/esphome/components/esp32/const.py @@ -23,6 +23,7 @@ KEY_EXTRA_BUILD_FILES = "extra_build_files" KEY_CERT_BUNDLE = "cert_bundle" KEY_FULL_CERT_BUNDLE = "full_cert_bundle" KEY_NETWORK_SDKCONFIG = "network_sdkconfig" +KEY_MBEDTLS_SDKCONFIG = "mbedtls_sdkconfig" VARIANT_ESP32 = "ESP32" VARIANT_ESP32C2 = "ESP32C2" diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index a6916fe7394..f718d16df46 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -17,8 +17,7 @@ extern "C" __attribute__((weak)) void initArduino() {} namespace esphome { // HAL functions live in hal.cpp. This file keeps only the loop task setup. -TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static StaticTask_t loop_task_tcb; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static StaticTask_t loop_task_tcb; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static StackType_t loop_task_stack[ESPHOME_LOOP_TASK_STACK_SIZE]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -40,11 +39,10 @@ extern "C" void app_main() { initArduino(); esp32::setup_preferences(); #if CONFIG_FREERTOS_UNICORE - loop_task_handle = xTaskCreateStatic(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, loop_task_stack, - &loop_task_tcb); + xTaskCreateStatic(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, loop_task_stack, &loop_task_tcb); #else - loop_task_handle = xTaskCreateStaticPinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, - loop_task_stack, &loop_task_tcb, 1); + xTaskCreateStaticPinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, loop_task_stack, + &loop_task_tcb, 1); #endif } diff --git a/esphome/components/esp32/crash_handler.cpp b/esphome/components/esp32/crash_handler.cpp index b72a2777c7a..182bc5f2d6c 100644 --- a/esphome/components/esp32/crash_handler.cpp +++ b/esphome/components/esp32/crash_handler.cpp @@ -161,7 +161,7 @@ static bool s_crash_data_valid = false; // NOLINT(cppcoreguidelines-avoid-non-c namespace esphome::esp32 { -static const char *const TAG = "esp32.crash"; +ESPHOME_LOG_TAG(TAG, "esp32.crash"); // RAM copy of the build timestamp. The generated constant lives in flash, // which the panic handler must not read (cache may be disabled during @@ -531,6 +531,9 @@ void IRAM_ATTR __wrap_esp_panic_handler(panic_info_t *info) { // Xtensa: walk the backtrace using the public API if (info->frame != nullptr) { auto *xt_frame = (XtExcFrame *) info->frame; + // IDF leaves addr unset for SoC-level panics (cache error, interrupt WDT) + if (info->pseudo_excause) + s_raw_crash_data.pc = xt_frame->pc; if (!g_panic_abort) { // Abort-class frames carry no useful cause/vaddr: TWDT task snapshots // never wrote them and abort() traps describe only the synthetic trap. diff --git a/esphome/components/esp32/gpio.cpp b/esphome/components/esp32/gpio.cpp index a00c984f764..59788e76dc7 100644 --- a/esphome/components/esp32/gpio.cpp +++ b/esphome/components/esp32/gpio.cpp @@ -1,9 +1,10 @@ #include "esphome/core/defines.h" -// Also defines the core ISRInternalGPIOPin methods; those are only reachable -// via ESP32InternalGPIOPin::to_isr(), so the same define gates both safely. -#if defined(USE_ESP32) && defined(USE_ESP32_INTERNAL_GPIO) +// Always built: InternalGPIOPin is this class, so components that merely hold a +// pointer to it reference these methods even in configs without a pin. +#ifdef USE_ESP32 #include "gpio.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" #include "driver/gpio.h" #include "driver/rtc_io.h" @@ -23,7 +24,7 @@ namespace esphome { namespace esp32 { -static const char *const TAG = "esp32"; +ESPHOME_LOG_TAG(TAG, "esp32"); static const gpio_hal_context_t GPIO_HAL = {.dev = GPIO_HAL_GET_HW(GPIO_PORT_0)}; @@ -49,17 +50,52 @@ struct ISRPinArg { gpio_num_t pin; gpio::Flags flags; bool inverted; +#ifdef USE_GPIO_HOLD + bool hold; +#endif #if defined(USE_ESP32_VARIANT_ESP32) bool use_rtc; int rtc_pin; #endif }; +#ifdef USE_GPIO_HOLD +// Re-latch a held pad onto the values just written to its registers. +static inline void refresh_hold(const ESP32InternalGPIOPin &pin) { + pin.set_hold(false); + pin.set_hold(true); +} + +static inline void IRAM_ATTR isr_refresh_hold(const ISRPinArg *arg) { + if (!arg->hold) + return; +#if defined(USE_ESP32_VARIANT_ESP32) + if (arg->use_rtc) { + rtcio_hal_hold_disable(arg->rtc_pin); + rtcio_hal_hold_enable(arg->rtc_pin); + return; + } +#elif defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) + // GPIO0-21 are held from the RTC domain; the digital hold mask is zero for them + if (arg->pin < SOC_RTCIO_PIN_COUNT) { + rtcio_hal_hold_disable(arg->pin); + rtcio_hal_hold_enable(arg->pin); + return; + } +#endif + gpio_hal_hold_dis(&GPIO_HAL, arg->pin); + gpio_hal_hold_en(&GPIO_HAL, arg->pin); +} +#endif + ISRInternalGPIOPin ESP32InternalGPIOPin::to_isr() const { auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) arg->pin = this->get_pin_num(); arg->flags = gpio::FLAG_NONE; arg->inverted = this->pin_flags_.inverted; +#ifdef USE_GPIO_HOLD + arg->hold = this->get_hold(); +#endif #if defined(USE_ESP32_VARIANT_ESP32) arg->use_rtc = rtc_gpio_is_valid_gpio(this->get_pin_num()); if (arg->use_rtc) @@ -68,7 +104,7 @@ ISRInternalGPIOPin ESP32InternalGPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } -void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { +void ESP32InternalGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE; switch (type) { case gpio::INTERRUPT_RISING_EDGE: @@ -104,7 +140,23 @@ size_t ESP32InternalGPIOPin::dump_summary(char *buffer, size_t len) const { return snprintf(buffer, len, "GPIO%" PRIu32, static_cast(this->pin_)); } +#ifdef USE_GPIO_HOLD +void ESP32InternalGPIOPin::set_hold(bool hold) const { + if (!this->get_hold()) + return; + if (hold) { + gpio_hold_en(this->get_pin_num()); + } else { + gpio_hold_dis(this->get_pin_num()); + } +} +#endif + void ESP32InternalGPIOPin::setup() { +#ifdef USE_GPIO_HOLD + // Hold before gpio_config so the pad keeps its state while the registers change + this->set_hold(true); +#endif gpio_config_t conf{}; conf.pin_bit_mask = 1ULL << static_cast(this->pin_); conf.mode = flags_to_mode(this->flags_); @@ -115,6 +167,18 @@ void ESP32InternalGPIOPin::setup() { if (this->flags_ & gpio::FLAG_OUTPUT) { gpio_set_drive_capability(this->get_pin_num(), this->get_drive_strength()); } +#ifdef USE_GPIO_HOLD + // gpio_hold_dis logs an error on input-only pads + if (GPIO_IS_VALID_OUTPUT_GPIO(this->get_pin_num())) { + if (!this->get_hold()) { + // Release a hold left behind by an earlier boot + gpio_hold_dis(this->get_pin_num()); + } else if (this->flags_ & gpio::FLAG_INPUT) { + // Inputs apply the config now so reads work; outputs wait for the first write + refresh_hold(*this); + } + } +#endif } void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) { @@ -129,6 +193,9 @@ void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) { pull_mode = GPIO_PULLDOWN_ONLY; } gpio_set_pull_mode(this->get_pin_num(), pull_mode); +#ifdef USE_GPIO_HOLD + refresh_hold(*this); +#endif } bool ESP32InternalGPIOPin::digital_read() { @@ -136,6 +203,9 @@ bool ESP32InternalGPIOPin::digital_read() { } void ESP32InternalGPIOPin::digital_write(bool value) { gpio_set_level(this->get_pin_num(), value != this->pin_flags_.inverted ? 1 : 0); +#ifdef USE_GPIO_HOLD + refresh_hold(*this); +#endif } void ESP32InternalGPIOPin::detach_interrupt() const { gpio_intr_disable(this->get_pin_num()); } @@ -143,6 +213,8 @@ void ESP32InternalGPIOPin::detach_interrupt() const { gpio_intr_disable(this->ge using namespace esp32; +// NOLINTBEGIN(clang-analyzer-core.FixedAddressDereference) -- some gpio_hal functions use MMIO at fixed addresses +// internally bool IRAM_ATTR ISRInternalGPIOPin::digital_read() { auto *arg = reinterpret_cast(this->arg_); return bool(gpio_hal_get_level(&GPIO_HAL, arg->pin)) != arg->inverted; @@ -151,6 +223,9 @@ bool IRAM_ATTR ISRInternalGPIOPin::digital_read() { void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { auto *arg = reinterpret_cast(this->arg_); gpio_hal_set_level(&GPIO_HAL, arg->pin, value != arg->inverted); +#ifdef USE_GPIO_HOLD + isr_refresh_hold(arg); +#endif } void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { @@ -202,9 +277,13 @@ void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { gpio_hal_input_disable(&GPIO_HAL, arg->pin); } } +#ifdef USE_GPIO_HOLD + isr_refresh_hold(arg); +#endif arg->flags = flags; } +// NOLINTEND(clang-analyzer-core.FixedAddressDereference) } // namespace esphome -#endif // USE_ESP32 && USE_ESP32_INTERNAL_GPIO +#endif // USE_ESP32 diff --git a/esphome/components/esp32/gpio.h b/esphome/components/esp32/gpio.h index aeff5af51c6..6ac16731271 100644 --- a/esphome/components/esp32/gpio.h +++ b/esphome/components/esp32/gpio.h @@ -1,7 +1,8 @@ #pragma once #ifdef USE_ESP32 -#include "esphome/core/hal.h" +#include "esphome/core/gpio_pin.h" +#include #include namespace esphome::esp32 { @@ -10,7 +11,7 @@ namespace esphome::esp32 { static_assert(GPIO_NUM_MAX <= 256, "gpio_num_t has too many values for uint8_t"); static_assert(GPIO_DRIVE_CAP_MAX <= 4, "gpio_drive_cap_t has too many values for 2-bit field"); -class ESP32InternalGPIOPin final : public InternalGPIOPin { +class ESP32InternalGPIOPin final : public GPIOPin { public: void set_pin(gpio_num_t pin) { this->pin_ = static_cast(pin); } void set_inverted(bool inverted) { this->pin_flags_.inverted = inverted; } @@ -24,16 +25,30 @@ class ESP32InternalGPIOPin final : public InternalGPIOPin { bool digital_read() override; void digital_write(bool value) override; size_t dump_summary(char *buffer, size_t len) const override; - void detach_interrupt() const override; - ISRInternalGPIOPin to_isr() const override; - uint8_t get_pin() const override { return this->pin_; } + void detach_interrupt() const; + template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { + this->attach_interrupt_(reinterpret_cast(func), arg, type); + } + ISRInternalGPIOPin to_isr() const; + uint8_t get_pin() const { return this->pin_; } gpio::Flags get_flags() const override { return this->flags_; } - bool is_inverted() const override { return this->pin_flags_.inverted; } + bool is_inverted() const { return this->pin_flags_.inverted; } + bool is_internal() override { return true; } gpio_num_t get_pin_num() const { return static_cast(this->pin_); } gpio_drive_cap_t get_drive_strength() const { return static_cast(this->pin_flags_.drive_strength); } +#ifdef USE_GPIO_HOLD + /// Apply or release the pad hold. Does nothing unless the pin is configured with hold_state. + void set_hold(bool hold) const; + bool is_held() const override { + if (!this->get_hold()) + return false; + esp_reset_reason_t reason = esp_reset_reason(); + return reason == ESP_RST_DEEPSLEEP || reason == ESP_RST_SW; + } +#endif protected: - void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const; // Memory layout: 8 bytes total on 32-bit systems // - 3 bytes for members below diff --git a/esphome/components/esp32/gpio.py b/esphome/components/esp32/gpio.py index 98aac209ec4..7753574bc85 100644 --- a/esphome/components/esp32/gpio.py +++ b/esphome/components/esp32/gpio.py @@ -5,6 +5,7 @@ from typing import Any from esphome import pins import esphome.codegen as cg +from esphome.components.const import CONF_HOLD_STATE import esphome.config_validation as cv from esphome.const import ( CONF_ID, @@ -248,6 +249,7 @@ ESP32_PIN_SCHEMA = cv.All( cv.float_with_unit("current", "mA", optional_unit=True), cv.enum(DRIVE_STRENGTHS), ), + cv.Optional(CONF_HOLD_STATE, default=False): cv.boolean, } ), validate_gpio_pin, @@ -257,7 +259,6 @@ ESP32_PIN_SCHEMA = cv.All( @pins.PIN_SCHEMA_REGISTRY.register(PLATFORM_ESP32, ESP32_PIN_SCHEMA) async def esp32_pin_to_code(config): - cg.add_define("USE_ESP32_INTERNAL_GPIO") var = cg.new_Pvariable(config[CONF_ID]) num = config[CONF_NUMBER] cg.add(var.set_pin(getattr(gpio_num_t, f"GPIO_NUM_{num}"))) @@ -267,7 +268,11 @@ async def esp32_pin_to_code(config): cg.add(var.set_inverted(True)) if CONF_DRIVE_STRENGTH in config: cg.add(var.set_drive_strength(config[CONF_DRIVE_STRENGTH])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + flags = pins.gpio_flags_expr(config[CONF_MODE]) + if config[CONF_HOLD_STATE]: + flags = flags | cg.gpio_Flags.FLAG_HOLD + cg.add_define("USE_GPIO_HOLD") + cg.add(var.set_flags(flags)) return var diff --git a/esphome/components/esp32/gpio_common.py b/esphome/components/esp32/gpio_common.py new file mode 100644 index 00000000000..a12bebf6e92 --- /dev/null +++ b/esphome/components/esp32/gpio_common.py @@ -0,0 +1,26 @@ +"""Pin checks shared by the ESP32 variant validators.""" + +from logging import Logger +from typing import Any + +from esphome.components.const import CONF_HOLD_STATE +from esphome.const import CONF_NUMBER + + +def check_usb_jtag_pin(num: int, usb_jtag_pins: set[int], logger: Logger) -> None: + if num in usb_jtag_pins: + logger.warning( + "GPIO%d is used by the USB-Serial-JTAG interface." + " Using this pin as GPIO will conflict with USB-Serial-JTAG.", + num, + ) + + +def check_usb_jtag_hold( + conf: dict[str, Any], usb_jtag_pins: set[int], logger: Logger +) -> None: + num = conf[CONF_NUMBER] + if conf.get(CONF_HOLD_STATE) and num in usb_jtag_pins: + logger.warning( + "GPIO%d cannot hold at low level during wakeup from deep sleep.", num + ) diff --git a/esphome/components/esp32/gpio_esp32.py b/esphome/components/esp32/gpio_esp32.py index fec257f90b8..11c62359c11 100644 --- a/esphome/components/esp32/gpio_esp32.py +++ b/esphome/components/esp32/gpio_esp32.py @@ -1,6 +1,7 @@ import logging from typing import Any +from esphome.components.const import CONF_HOLD_STATE import esphome.config_validation as cv from esphome.const import ( CONF_INPUT, @@ -53,6 +54,7 @@ def esp32_validate_supports(value: dict[str, Any]) -> dict[str, Any]: is_output = mode[CONF_OUTPUT] is_pullup = mode[CONF_PULLUP] is_pulldown = mode[CONF_PULLDOWN] + is_hold = value.get(CONF_HOLD_STATE) if is_input: # All ESP32 pins support input mode @@ -70,6 +72,14 @@ def esp32_validate_supports(value: dict[str, Any]) -> dict[str, Any]: raise cv.Invalid( f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN] ) + if is_hold and 34 <= num <= 39: + raise cv.Invalid( + f"GPIO{num} (34-39) is input-only and cannot be held.", + [CONF_HOLD_STATE], + ) + if is_hold and num == 20: + # Not in the digital hold mask and not an RTC pad, so the driver cannot hold it + raise cv.Invalid("GPIO20 has no hold function.", [CONF_HOLD_STATE]) check_strapping_pin(value, _ESP32_STRAPPING_PINS, _LOGGER) return value diff --git a/esphome/components/esp32/gpio_esp32_c3.py b/esphome/components/esp32/gpio_esp32_c3.py index 6eb002f3f0a..c4624a93a4e 100644 --- a/esphome/components/esp32/gpio_esp32_c3.py +++ b/esphome/components/esp32/gpio_esp32_c3.py @@ -5,6 +5,8 @@ import esphome.config_validation as cv from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER from esphome.pins import check_strapping_pin +from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin + _ESP32C3_SPI_PSRAM_PINS = { 12: "SPIHD", 13: "SPIWP", @@ -28,12 +30,7 @@ def esp32_c3_validate_gpio_pin(value: int) -> int: raise cv.Invalid( f"This pin cannot be used on ESP32-C3s and is already used by the SPI/PSRAM interface (function: {_ESP32C3_SPI_PSRAM_PINS[value]})" ) - if value in _ESP32C3_USB_JTAG_PINS: - _LOGGER.warning( - "GPIO%d is used by the USB-Serial-JTAG interface." - " Using this pin as GPIO will conflict with USB-Serial-JTAG.", - value, - ) + check_usb_jtag_pin(value, _ESP32C3_USB_JTAG_PINS, _LOGGER) return value @@ -49,6 +46,7 @@ def esp32_c3_validate_supports(value: dict[str, Any]) -> dict[str, Any]: if is_input: # All ESP32 pins support input mode pass + check_usb_jtag_hold(value, _ESP32C3_USB_JTAG_PINS, _LOGGER) check_strapping_pin(value, _ESP32C3_STRAPPING_PINS, _LOGGER) return value diff --git a/esphome/components/esp32/gpio_esp32_c5.py b/esphome/components/esp32/gpio_esp32_c5.py index 639ed64c9ea..b3f4c319c61 100644 --- a/esphome/components/esp32/gpio_esp32_c5.py +++ b/esphome/components/esp32/gpio_esp32_c5.py @@ -5,6 +5,8 @@ import esphome.config_validation as cv from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA from esphome.pins import check_strapping_pin +from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin + # https://github.com/espressif/esp-idf/blob/master/components/esp_hal_i2c/esp32c5/include/hal/i2c_ll.h _ESP32C5_I2C_LP_PINS = {"SDA": 2, "SCL": 3} @@ -20,6 +22,8 @@ _ESP32C5_SPI_PSRAM_PINS = { _ESP32C5_STRAPPING_PINS = {2, 7, 27, 28} +_ESP32C5_USB_JTAG_PINS = {13, 14} + _LOGGER = logging.getLogger(__name__) @@ -31,6 +35,7 @@ def esp32_c5_validate_gpio_pin(value: int) -> int: f"This pin cannot be used on ESP32-C5s and is already used by the SPI/PSRAM interface (function: {_ESP32C5_SPI_PSRAM_PINS[value]})" ) + check_usb_jtag_pin(value, _ESP32C5_USB_JTAG_PINS, _LOGGER) return value @@ -45,6 +50,8 @@ def esp32_c5_validate_supports(value: dict[str, Any]) -> dict[str, Any]: # All ESP32 pins support input mode pass + check_usb_jtag_hold(value, _ESP32C5_USB_JTAG_PINS, _LOGGER) + check_strapping_pin(value, _ESP32C5_STRAPPING_PINS, _LOGGER) return value diff --git a/esphome/components/esp32/gpio_esp32_c6.py b/esphome/components/esp32/gpio_esp32_c6.py index bd7bb9e2208..9eba375e26e 100644 --- a/esphome/components/esp32/gpio_esp32_c6.py +++ b/esphome/components/esp32/gpio_esp32_c6.py @@ -5,6 +5,8 @@ import esphome.config_validation as cv from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA from esphome.pins import check_strapping_pin +from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin + # https://github.com/espressif/esp-idf/blob/master/components/esp_hal_i2c/esp32c6/include/hal/i2c_ll.h _ESP32C6_I2C_LP_PINS = {"SDA": 6, "SCL": 7} @@ -32,12 +34,7 @@ def esp32_c6_validate_gpio_pin(value: int) -> int: raise cv.Invalid( f"This pin cannot be used on ESP32-C6s and is already used by the SPI/PSRAM interface (function: {_ESP32C6_SPI_PSRAM_PINS[value]})" ) - if value in _ESP32C6_USB_JTAG_PINS: - _LOGGER.warning( - "GPIO%d is used by the USB-Serial-JTAG interface." - " Using this pin as GPIO will conflict with USB-Serial-JTAG.", - value, - ) + check_usb_jtag_pin(value, _ESP32C6_USB_JTAG_PINS, _LOGGER) return value @@ -52,6 +49,7 @@ def esp32_c6_validate_supports(value: dict[str, Any]) -> dict[str, Any]: if is_input: # All ESP32 pins support input mode pass + check_usb_jtag_hold(value, _ESP32C6_USB_JTAG_PINS, _LOGGER) check_strapping_pin(value, _ESP32C6_STRAPPING_PINS, _LOGGER) return value diff --git a/esphome/components/esp32/gpio_esp32_c61.py b/esphome/components/esp32/gpio_esp32_c61.py index 2f3abe6a0f9..3ae8f1c2921 100644 --- a/esphome/components/esp32/gpio_esp32_c61.py +++ b/esphome/components/esp32/gpio_esp32_c61.py @@ -5,6 +5,8 @@ import esphome.config_validation as cv from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER from esphome.pins import check_strapping_pin +from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin + # GPIO14-17, GPIO19-21 are used for SPI flash/PSRAM _ESP32C61_SPI_PSRAM_PINS = { 14: "SPICS0", @@ -18,6 +20,8 @@ _ESP32C61_SPI_PSRAM_PINS = { _ESP32C61_STRAPPING_PINS = {8, 9} +_ESP32C61_USB_JTAG_PINS = {12, 13} + _LOGGER = logging.getLogger(__name__) @@ -29,6 +33,7 @@ def esp32_c61_validate_gpio_pin(value: int) -> int: f"This pin cannot be used on ESP32-C61s and is already used by the SPI/PSRAM interface (function: {_ESP32C61_SPI_PSRAM_PINS[value]})" ) + check_usb_jtag_pin(value, _ESP32C61_USB_JTAG_PINS, _LOGGER) return value @@ -43,5 +48,7 @@ def esp32_c61_validate_supports(value: dict[str, Any]) -> dict[str, Any]: # All ESP32-C61 pins support input mode pass + check_usb_jtag_hold(value, _ESP32C61_USB_JTAG_PINS, _LOGGER) + check_strapping_pin(value, _ESP32C61_STRAPPING_PINS, _LOGGER) return value diff --git a/esphome/components/esp32/gpio_esp32_h2.py b/esphome/components/esp32/gpio_esp32_h2.py index 9dd65376944..41520bb4b75 100644 --- a/esphome/components/esp32/gpio_esp32_h2.py +++ b/esphome/components/esp32/gpio_esp32_h2.py @@ -5,6 +5,8 @@ import esphome.config_validation as cv from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER from esphome.pins import check_strapping_pin +from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin + _ESP32H2_SPI_FLASH_PINS = {6, 7, 15, 16, 17, 18, 19, 20, 21} _ESP32H2_USB_JTAG_PINS = {26, 27} @@ -24,12 +26,7 @@ def esp32_h2_validate_gpio_pin(value: int) -> int: "See https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/api-reference/peripherals/gpio.html", value, ) - if value in _ESP32H2_USB_JTAG_PINS: - _LOGGER.warning( - "GPIO%d is used by the USB-Serial-JTAG interface." - " Using this pin as GPIO will conflict with USB-Serial-JTAG.", - value, - ) + check_usb_jtag_pin(value, _ESP32H2_USB_JTAG_PINS, _LOGGER) return value @@ -44,5 +41,6 @@ def esp32_h2_validate_supports(value: dict[str, Any]) -> dict[str, Any]: if is_input: # All ESP32 pins support input mode pass + check_usb_jtag_hold(value, _ESP32H2_USB_JTAG_PINS, _LOGGER) check_strapping_pin(value, _ESP32H2_STRAPPING_PINS, _LOGGER) return value diff --git a/esphome/components/esp32/gpio_esp32_h21.py b/esphome/components/esp32/gpio_esp32_h21.py index 5ab1b7c0740..a13fe3e4ccc 100644 --- a/esphome/components/esp32/gpio_esp32_h21.py +++ b/esphome/components/esp32/gpio_esp32_h21.py @@ -5,17 +5,22 @@ import esphome.config_validation as cv from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER from esphome.pins import check_strapping_pin +from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin + # Partial set from the ESP-IDF / esptool boot-mode docs: # https://docs.espressif.com/projects/esptool/en/latest/esp32h21/advanced-topics/boot-mode-selection.html # The full list awaits the ESP32-H21 datasheet's "Strapping Pins" section. _ESP32H21_STRAPPING_PINS: set[int] = {13, 14} +_ESP32H21_USB_JTAG_PINS = {17, 18} + _LOGGER = logging.getLogger(__name__) def esp32_h21_validate_gpio_pin(value: int) -> int: if value < 0 or value > 25: raise cv.Invalid(f"Invalid pin number: {value} (must be 0-25)") + check_usb_jtag_pin(value, _ESP32H21_USB_JTAG_PINS, _LOGGER) return value @@ -30,5 +35,7 @@ def esp32_h21_validate_supports(value: dict[str, Any]) -> dict[str, Any]: # All ESP32 pins support input mode pass + check_usb_jtag_hold(value, _ESP32H21_USB_JTAG_PINS, _LOGGER) + check_strapping_pin(value, _ESP32H21_STRAPPING_PINS, _LOGGER) return value diff --git a/esphome/components/esp32/gpio_esp32_h4.py b/esphome/components/esp32/gpio_esp32_h4.py index 86a4d558589..0cbd83c50cb 100644 --- a/esphome/components/esp32/gpio_esp32_h4.py +++ b/esphome/components/esp32/gpio_esp32_h4.py @@ -5,17 +5,22 @@ import esphome.config_validation as cv from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER from esphome.pins import check_strapping_pin +from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin + # Partial set from the ESP-IDF / esptool boot-mode docs: # https://docs.espressif.com/projects/esptool/en/latest/esp32h4/advanced-topics/boot-mode-selection.html # The full list awaits the ESP32-H4 datasheet's "Strapping Pins" section. _ESP32H4_STRAPPING_PINS: set[int] = {13, 14} +_ESP32H4_USB_JTAG_PINS = {13, 14} + _LOGGER = logging.getLogger(__name__) def esp32_h4_validate_gpio_pin(value: int) -> int: if value < 0 or value > 39: raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)") + check_usb_jtag_pin(value, _ESP32H4_USB_JTAG_PINS, _LOGGER) return value @@ -30,5 +35,7 @@ def esp32_h4_validate_supports(value: dict[str, Any]) -> dict[str, Any]: # All ESP32 pins support input mode pass + check_usb_jtag_hold(value, _ESP32H4_USB_JTAG_PINS, _LOGGER) + check_strapping_pin(value, _ESP32H4_STRAPPING_PINS, _LOGGER) return value diff --git a/esphome/components/esp32/gpio_esp32_p4.py b/esphome/components/esp32/gpio_esp32_p4.py index 6e9227c5015..69fdf5dd22c 100644 --- a/esphome/components/esp32/gpio_esp32_p4.py +++ b/esphome/components/esp32/gpio_esp32_p4.py @@ -5,6 +5,8 @@ import esphome.config_validation as cv from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA from esphome.pins import check_strapping_pin +from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin + # https://documentation.espressif.com/esp32-p4-chip-revision-v1.3_datasheet_en.pdf _ESP32P4_LP_PINS = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} @@ -18,12 +20,7 @@ _LOGGER = logging.getLogger(__name__) def esp32_p4_validate_gpio_pin(value: int) -> int: if value < 0 or value > 54: raise cv.Invalid(f"Invalid pin number: {value} (must be 0-54)") - if value in _ESP32P4_USB_JTAG_PINS: - _LOGGER.warning( - "GPIO%d is used by the USB-Serial-JTAG interface." - " Using this pin as GPIO will conflict with USB-Serial-JTAG.", - value, - ) + check_usb_jtag_pin(value, _ESP32P4_USB_JTAG_PINS, _LOGGER) return value @@ -38,6 +35,7 @@ def esp32_p4_validate_supports(value: dict[str, Any]) -> dict[str, Any]: if is_input: # All ESP32 pins support input mode pass + check_usb_jtag_hold(value, _ESP32P4_USB_JTAG_PINS, _LOGGER) check_strapping_pin(value, _ESP32P4_STRAPPING_PINS, _LOGGER) return value diff --git a/esphome/components/esp32/gpio_esp32_s2.py b/esphome/components/esp32/gpio_esp32_s2.py index 4978f48a1c2..0de621789d5 100644 --- a/esphome/components/esp32/gpio_esp32_s2.py +++ b/esphome/components/esp32/gpio_esp32_s2.py @@ -1,6 +1,7 @@ import logging from typing import Any +from esphome.components.const import CONF_HOLD_STATE import esphome.config_validation as cv from esphome.const import ( CONF_INPUT, @@ -70,6 +71,11 @@ def esp32_s2_validate_supports(value: dict[str, Any]) -> dict[str, Any]: raise cv.Invalid( f"GPIO{num} does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN] ) + if value.get(CONF_HOLD_STATE) and num == 46: + raise cv.Invalid( + f"GPIO{num} is input-only and cannot be held.", + [CONF_HOLD_STATE], + ) check_strapping_pin(value, _ESP32S2_STRAPPING_PINS, _LOGGER) return value diff --git a/esphome/components/esp32/gpio_esp32_s3.py b/esphome/components/esp32/gpio_esp32_s3.py index db8c5205336..d38c0a8011f 100644 --- a/esphome/components/esp32/gpio_esp32_s3.py +++ b/esphome/components/esp32/gpio_esp32_s3.py @@ -12,6 +12,8 @@ from esphome.const import ( from esphome.pins import PIN_SCHEMA_REGISTRY, check_strapping_pin from esphome.types import ConfigType +from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin + _ESP32S3_SPI_PSRAM_PINS = { 26: "SPICS1", 27: "SPIHD", @@ -53,12 +55,7 @@ def esp32_s3_validate_gpio_pin(value: int) -> int: # These pins are not exposed in GPIO mux (reason unknown) # but they're missing from IO_MUX list in datasheet raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S3s.") - if value in _ESP32S3_USB_JTAG_PINS: - _LOGGER.warning( - "GPIO%d is used by the USB-Serial-JTAG interface." - " Using this pin as GPIO will conflict with USB-Serial-JTAG.", - value, - ) + check_usb_jtag_pin(value, _ESP32S3_USB_JTAG_PINS, _LOGGER) return value @@ -73,6 +70,7 @@ def esp32_s3_validate_supports(value: dict[str, Any]) -> dict[str, Any]: if is_input: # All ESP32 pins support input mode pass + check_usb_jtag_hold(value, _ESP32S3_USB_JTAG_PINS, _LOGGER) check_strapping_pin(value, _ESP32S3_STRAPPING_PINS, _LOGGER) return value diff --git a/esphome/components/esp32/gpio_esp32_s31.py b/esphome/components/esp32/gpio_esp32_s31.py index 7ccb7cdb903..a6a3b39193e 100644 --- a/esphome/components/esp32/gpio_esp32_s31.py +++ b/esphome/components/esp32/gpio_esp32_s31.py @@ -5,6 +5,8 @@ import esphome.config_validation as cv from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA from esphome.pins import check_strapping_pin +from .gpio_common import check_usb_jtag_hold, check_usb_jtag_pin + # Per the ESP32-S31 IDF DOCS and datasheet: # https://docs.espressif.com/projects/esp-idf/en/v6.1/esp32s31/api-reference/peripherals/gpio.html # https://documentation.espressif.com/esp32-s31_datasheet_en.pdf @@ -16,6 +18,8 @@ _ESP32S31_STRAPPING_PINS: set[int] = {36, 37, 60, 61} # LP I2C is fixed to GPIO6 (SCL) / GPIO7 (SDA) per the datasheet IO MUX table. _ESP32S31_I2C_LP_PINS = {"SDA": 7, "SCL": 6} +_ESP32S31_USB_JTAG_PINS = {33, 34} + _LOGGER = logging.getLogger(__name__) @@ -28,6 +32,7 @@ def esp32_s31_validate_gpio_pin(value: int) -> int: raise cv.Invalid( f"GPIO{value} is reserved for the SPI flash interface on ESP32-S31 and cannot be used." ) + check_usb_jtag_pin(value, _ESP32S31_USB_JTAG_PINS, _LOGGER) return value @@ -46,6 +51,8 @@ def esp32_s31_validate_supports(value: dict[str, Any]) -> dict[str, Any]: # All ESP32 pins support input mode pass + check_usb_jtag_hold(value, _ESP32S31_USB_JTAG_PINS, _LOGGER) + check_strapping_pin(value, _ESP32S31_STRAPPING_PINS, _LOGGER) return value diff --git a/esphome/components/esp32/hal.h b/esphome/components/esp32/hal.h index 2180f07f6c3..c01dcc858ed 100644 --- a/esphome/components/esp32/hal.h +++ b/esphome/components/esp32/hal.h @@ -9,6 +9,7 @@ #include #include +#include "esphome/core/main_task.h" #include "esphome/core/time_conversion.h" #ifndef PROGMEM @@ -31,6 +32,10 @@ __attribute__((always_inline)) inline bool in_isr_context() { return xPortInIsrC // NOLINTNEXTLINE(readability-redundant-declaration) extern "C" int64_t esp_timer_get_time(void); +/// Before setup() stores the handle every task counts as background; the wake is a no-op until then. +__attribute__((always_inline)) inline bool is_main_loop_thread() { + return xTaskGetCurrentTaskHandle() == esphome_main_task_handle; +} __attribute__((always_inline)) inline void yield() { vPortYield(); } __attribute__((always_inline)) inline void delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); } __attribute__((always_inline)) inline uint32_t micros() { return static_cast(esp_timer_get_time()); } diff --git a/esphome/components/esp32/helpers.cpp b/esphome/components/esp32/helpers.cpp index 91b4241211d..88fccea0ca8 100644 --- a/esphome/components/esp32/helpers.cpp +++ b/esphome/components/esp32/helpers.cpp @@ -15,7 +15,7 @@ namespace esphome { -static const char *const TAG = "esp32"; +ESPHOME_LOG_TAG(TAG, "esp32"); bool random_bytes(uint8_t *data, size_t len) { esp_fill_random(data, len); diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index f3d5844cd7a..02978ae0f7a 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -12,7 +12,7 @@ namespace esphome::esp32 { -static const char *const TAG = "preferences"; +ESPHOME_LOG_TAG(TAG, "preferences"); struct NVSData { uint32_t key; diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index ef95243bc11..48f2e3c9460 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -22,6 +22,8 @@ from esphome.components.esp32 import ( request_bluetooth, ) from esphome.components.esp32.const import VARIANT_ESP32C2 +from esphome.components.esp32_hosted import DOMAIN as ESP32_HOSTED_DOMAIN +from esphome.components.psram import DOMAIN as PSRAM_DOMAIN from esphome.config_helpers import filter_source_files_from_defines import esphome.config_validation as cv from esphome.const import ( @@ -277,7 +279,7 @@ DEFAULT_MAX_CONNECTIONS = 3 IDF_MAX_CONNECTIONS = 9 # Connection slot tracking keys -KEY_ESP32_BLE = "esp32_ble" +KEY_ESP32_BLE = DOMAIN KEY_USED_CONNECTION_SLOTS = "used_connection_slots" # Export for use by other components (bluetooth_proxy, etc.) @@ -361,7 +363,7 @@ CONFIG_SCHEMA = cv.Schema( cv.positive_int, cv.Range(min=1, max=IDF_MAX_CONNECTIONS) ), cv.Optional(CONF_USE_PSRAM): cv.All( - cv.only_on_esp32, cv.requires_component("psram"), cv.boolean + cv.only_on_esp32, cv.requires_component(PSRAM_DOMAIN), cv.boolean ), } ).extend(cv.COMPONENT_SCHEMA) @@ -404,7 +406,7 @@ def consume_connection_slots( """ def _consume_connection_slots(config: MutableMapping) -> MutableMapping: - data: dict[str, Any] = CORE.data.setdefault(KEY_ESP32_BLE, {}) + data: dict[str, Any] = CORE.data.setdefault(DOMAIN, {}) slots: list[str] = data.setdefault(KEY_USED_CONNECTION_SLOTS, []) slots.extend([consumer] * value) return config @@ -418,7 +420,7 @@ def validate_connection_slots(max_connections: int) -> None: if CORE.testing_mode: return - ble_data = CORE.data.get(KEY_ESP32_BLE, {}) + ble_data = CORE.data.get(DOMAIN, {}) used_slots = ble_data.get(KEY_USED_CONNECTION_SLOTS, []) num_used = len(used_slots) @@ -473,7 +475,7 @@ def final_validation(config: ConfigType) -> None: validate_connection_slots(max_connections) # Check if hosted bluetooth is being used - if "esp32_hosted" in full_config: + if ESP32_HOSTED_DOMAIN in full_config: from esphome.components.esp32_hosted import uses_esp_hosted_3x add_idf_sdkconfig_option("CONFIG_BT_CLASSIC_ENABLED", False) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index a44886b8c81..b5051c63e17 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -38,7 +38,7 @@ extern "C" bool btInUse() { return true; } // NOLINT(readability-identifier-nam namespace esphome::esp32_ble { -static const char *const TAG = "esp32_ble"; +ESPHOME_LOG_TAG(TAG, "esp32_ble"); #ifdef CONFIG_BT_CONTROLLER_DISABLED // Bringing up the remote BT controller issues synchronous RPCs to the diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index 68704e49e2e..30d21ff9f40 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -11,7 +11,7 @@ namespace esphome::esp32_ble { -static const char *const TAG = "esp32_ble.advertising"; +ESPHOME_LOG_TAG(TAG, "esp32_ble.advertising"); BLEAdvertising::BLEAdvertising(uint32_t advertising_cycle_time) : advertising_cycle_time_(advertising_cycle_time) { this->advertising_data_.set_scan_rsp = false; diff --git a/esphome/components/esp32_ble_beacon/__init__.py b/esphome/components/esp32_ble_beacon/__init__.py index e9c44284e43..7498228f4c6 100644 --- a/esphome/components/esp32_ble_beacon/__init__.py +++ b/esphome/components/esp32_ble_beacon/__init__.py @@ -9,6 +9,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["esp32_ble"] DEPENDENCIES = ["esp32"] +DOMAIN = "esp32_ble_beacon" esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon") ESP32BLEBeacon = esp32_ble_beacon_ns.class_("ESP32BLEBeacon", cg.Component) diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp index 47e4b7ebb20..7481d2c612c 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp @@ -18,7 +18,7 @@ namespace esphome::esp32_ble_beacon { -static const char *const TAG = "esp32_ble_beacon"; +ESPHOME_LOG_TAG(TAG, "esp32_ble_beacon"); static const esp_ble_ibeacon_head_t IBEACON_COMMON_HEAD = { .flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = {0x4C, 0x00}, .beacon_type = {0x02, 0x15}}; diff --git a/esphome/components/esp32_ble_client/__init__.py b/esphome/components/esp32_ble_client/__init__.py index 55619f1fc0b..dbb409b17ec 100644 --- a/esphome/components/esp32_ble_client/__init__.py +++ b/esphome/components/esp32_ble_client/__init__.py @@ -4,6 +4,7 @@ from esphome.components import esp32_ble_tracker AUTO_LOAD = ["esp32_ble_tracker"] CODEOWNERS = ["@jesserockz", "@bdraco"] DEPENDENCIES = ["esp32"] +DOMAIN = "esp32_ble_client" esp32_ble_client_ns = cg.esphome_ns.namespace("esp32_ble_client") BLEClientBase = esp32_ble_client_ns.class_( diff --git a/esphome/components/esp32_ble_client/ble_characteristic.cpp b/esphome/components/esp32_ble_client/ble_characteristic.cpp index e830702f114..fceca5f558a 100644 --- a/esphome/components/esp32_ble_client/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_client/ble_characteristic.cpp @@ -9,7 +9,7 @@ namespace esphome::esp32_ble_client { -static const char *const TAG = "esp32_ble_client"; +ESPHOME_LOG_TAG(TAG, "esp32_ble_client"); BLECharacteristic::~BLECharacteristic() { for (auto &desc : this->descriptors) diff --git a/esphome/components/esp32_ble_client/ble_client_base.cpp b/esphome/components/esp32_ble_client/ble_client_base.cpp index 88454f7bdbf..dde47b0c5e4 100644 --- a/esphome/components/esp32_ble_client/ble_client_base.cpp +++ b/esphome/components/esp32_ble_client/ble_client_base.cpp @@ -11,7 +11,7 @@ namespace esphome::esp32_ble_client { -static const char *const TAG = "esp32_ble_client"; +ESPHOME_LOG_TAG(TAG, "esp32_ble_client"); // Connection parameters are shared with the other GATT client backends // (ble_device_base/ble_client_state.h) so the platforms cannot drift. @@ -150,6 +150,8 @@ void BLEClientBase::connect() { // A registration whose event never arrived must not block this connection's release. this->services_released_ = false; this->pending_notify_regs_ = 0; + // A CONNECT_EVT seen while idle can leave the id of a link this attempt does not own. + this->conn_id_ = UNSET_CONN_ID; // Enable loop for state processing this->enable_loop(); // Immediately transition to CONNECTING to prevent duplicate connection attempts @@ -350,7 +352,6 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ if (!this->check_addr(param->open.remote_bda)) return false; this->log_gattc_lifecycle_event_("OPEN"); - // conn_id was already set in ESP_GATTC_CONNECT_EVT this->service_count_ = 0; // ESP-IDF's BLE stack may send ESP_GATTC_OPEN_EVT after esp_ble_gattc_open() returns an @@ -362,19 +363,24 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } - if (this->state() != espbt::ClientState::CONNECTING) { - // This should not happen but lets log it in case it does - // because it means we have a bad assumption about how the - // ESP BT stack works. + const bool open_failed = param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN; + // Bluedroid reports a failed open as DISCONNECT_EVT then a failing OPEN_EVT, with no CONNECT_EVT, + // so DISCONNECTING with no id is expected for a failure. Anything else means a bad assumption + // about how the ESP BT stack works, so log it. + const bool expected_failure = + open_failed && this->state() == espbt::ClientState::DISCONNECTING && this->conn_id_ == UNSET_CONN_ID; + if (this->state() != espbt::ClientState::CONNECTING && !expected_failure) { ESP_LOGE(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in %s state (status=%d)", this->connection_index_, this->address_str_, espbt::client_state_to_string(this->state()), param->open.status); } - if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) { + if (open_failed) { this->log_gattc_warning_("Connection open", param->open.status); // Connection was never established so CLOSE_EVT may not follow this->set_idle_(); break; } + // An open on a link that is already up sends no CONNECT_EVT, so take the id here. + this->conn_id_ = param->open.conn_id; if (this->want_disconnect_) { // Disconnect was requested after connecting started, // but before the connection was established. Now that we have diff --git a/esphome/components/esp32_ble_client/ble_service.cpp b/esphome/components/esp32_ble_client/ble_service.cpp index 695f468c5b9..40e9f81d23b 100644 --- a/esphome/components/esp32_ble_client/ble_service.cpp +++ b/esphome/components/esp32_ble_client/ble_service.cpp @@ -8,7 +8,7 @@ namespace esphome::esp32_ble_client { -static const char *const TAG = "esp32_ble_client"; +ESPHOME_LOG_TAG(TAG, "esp32_ble_client"); BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) { if (!this->parsed) diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index 9355d588596..75a099a3c49 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -5,7 +5,7 @@ import esphome.codegen as cg from esphome.components import esp32_ble from esphome.components.const import CONF_DESCRIPTION, CONF_MANUFACTURER from esphome.components.esp32 import request_bluetooth -from esphome.components.esp32_ble import BTLoggers, bt_uuid +from esphome.components.esp32_ble import DOMAIN as ESP32_BLE_DOMAIN, BTLoggers, bt_uuid import esphome.config_validation as cv from esphome.config_validation import UNDEFINED from esphome.const import ( @@ -311,7 +311,7 @@ def final_validate_config(config) -> None: max_clients = config[CONF_MAX_CLIENTS] if max_clients > 1: full_config = fv.full_config.get() - ble_config = full_config.get("esp32_ble", {}) + ble_config = full_config.get(ESP32_BLE_DOMAIN, {}) max_connections = ble_config.get( "max_connections", esp32_ble.DEFAULT_MAX_CONNECTIONS ) diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index 4d364b46556..75a33993797 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -9,7 +9,7 @@ namespace esphome::esp32_ble_server { -static const char *const TAG = "esp32_ble_server.characteristic"; +ESPHOME_LOG_TAG(TAG, "esp32_ble_server.characteristic"); BLECharacteristic::~BLECharacteristic() { for (auto *descriptor : this->descriptors_) { diff --git a/esphome/components/esp32_ble_server/ble_descriptor.cpp b/esphome/components/esp32_ble_server/ble_descriptor.cpp index 3dcac3691c2..228313e2d8e 100644 --- a/esphome/components/esp32_ble_server/ble_descriptor.cpp +++ b/esphome/components/esp32_ble_server/ble_descriptor.cpp @@ -9,7 +9,7 @@ namespace esphome::esp32_ble_server { -static const char *const TAG = "esp32_ble_server.descriptor"; +ESPHOME_LOG_TAG(TAG, "esp32_ble_server.descriptor"); static RAMAllocator descriptor_allocator{}; // NOLINT diff --git a/esphome/components/esp32_ble_server/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp index e34a57cf37f..650a765b6a5 100644 --- a/esphome/components/esp32_ble_server/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -18,7 +18,7 @@ namespace esphome::esp32_ble_server { -static const char *const TAG = "esp32_ble_server"; +ESPHOME_LOG_TAG(TAG, "esp32_ble_server"); void BLEServer::setup() { if (this->parent_->is_failed()) { diff --git a/esphome/components/esp32_ble_server/ble_service.cpp b/esphome/components/esp32_ble_server/ble_service.cpp index ac8d7fa003c..c8f4ecbf332 100644 --- a/esphome/components/esp32_ble_server/ble_service.cpp +++ b/esphome/components/esp32_ble_server/ble_service.cpp @@ -6,7 +6,7 @@ namespace esphome::esp32_ble_server { -static const char *const TAG = "esp32_ble_server.service"; +ESPHOME_LOG_TAG(TAG, "esp32_ble_server.service"); BLEService::BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id, bool advertise) : uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id), advertise_(advertise) {} diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index fc52b92ee7b..59ac445cc06 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -8,6 +8,10 @@ from esphome import automation import esphome.codegen as cg from esphome.components import ble_device_base, esp32_ble, ota from esphome.components.ble_device_base import CONF_CONNECTION_SCAN_WINDOW +from esphome.components.ble_device_base.automation import ( + MAC_FILTER_LIST, + mac_filter_table, +) from esphome.components.const import CONF_ON_SCAN_END, CONF_SCAN_PARAMETERS, CONF_WINDOW from esphome.components.esp32 import ( add_idf_sdkconfig_option, @@ -277,7 +281,7 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( ESPBTAdvertiseTrigger ), - cv.Optional(CONF_MAC_ADDRESS): cv.ensure_list(cv.mac_address), + cv.Optional(CONF_MAC_ADDRESS): MAC_FILTER_LIST, } ), cv.Optional( @@ -381,9 +385,8 @@ async def to_code(config: ConfigType) -> None: for conf in config.get(CONF_ON_BLE_ADVERTISE, []): _request_listener_slot() trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - if CONF_MAC_ADDRESS in conf: - addr_list = [it.as_hex for it in conf[CONF_MAC_ADDRESS]] - cg.add(trigger.set_addresses(addr_list)) + if macs := conf.get(CONF_MAC_ADDRESS): + cg.add(trigger.set_addresses(mac_filter_table(macs))) await automation.build_automation(trigger, [(ESPBTDeviceConstRef, "x")], conf) for conf in config.get(CONF_ON_BLE_SERVICE_DATA_ADVERTISE, []): _request_listener_slot() diff --git a/esphome/components/esp32_ble_tracker/automation.h b/esphome/components/esp32_ble_tracker/automation.h index 9b4eb2c3b30..f30f63a5c87 100644 --- a/esphome/components/esp32_ble_tracker/automation.h +++ b/esphome/components/esp32_ble_tracker/automation.h @@ -1,10 +1,9 @@ #pragma once #include "esphome/core/automation.h" +#include "esphome/components/ble_device_base/automation.h" #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#include - #ifdef USE_ESP32 namespace esphome::esp32_ble_tracker { @@ -12,22 +11,18 @@ namespace esphome::esp32_ble_tracker { class ESPBTAdvertiseTrigger final : public Trigger, public ESPBTDeviceListener { public: explicit ESPBTAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); } - void set_addresses(std::initializer_list addresses) { this->address_vec_ = addresses; } + /// Table of MACs ended by 0; must outlive the trigger. + void set_addresses(const uint64_t *addresses) { this->addresses_ = addresses; } bool parse_device(const ESPBTDevice &device) override { - uint64_t u64_addr = device.address_uint64(); - if (!address_vec_.empty()) { - if (std::find(address_vec_.begin(), address_vec_.end(), u64_addr) == address_vec_.end()) { - return false; - } - } - + if (this->addresses_ != nullptr && !ble_device_base::mac_in_table(this->addresses_, device.address_uint64())) + return false; this->trigger(device); return true; } protected: - std::vector address_vec_; + const uint64_t *addresses_{nullptr}; }; class BLEServiceDataAdvertiseTrigger final : public Trigger, public ESPBTDeviceListener { diff --git a/esphome/components/esp32_ble_tracker/bluedroid_stubs.cpp b/esphome/components/esp32_ble_tracker/bluedroid_stubs.cpp index 5cedc22d069..cffa57a518f 100644 --- a/esphome/components/esp32_ble_tracker/bluedroid_stubs.cpp +++ b/esphome/components/esp32_ble_tracker/bluedroid_stubs.cpp @@ -15,7 +15,7 @@ #include "esphome/core/log.h" namespace esphome::esp32_ble_tracker { -static const char *const TAG = "esp32_ble_tracker"; +ESPHOME_LOG_TAG(TAG, "esp32_ble_tracker"); } // namespace esphome::esp32_ble_tracker static_assert(ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 2, 0), diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 9bb90026e3f..8d3148c2fc7 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -32,7 +32,7 @@ namespace esphome::esp32_ble_tracker { -static const char *const TAG = "esp32_ble_tracker"; +ESPHOME_LOG_TAG(TAG, "esp32_ble_tracker"); ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 3c41d22903f..2d4e1ed7e99 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -33,6 +33,7 @@ _LOGGER = logging.getLogger(__name__) AUTO_LOAD = ["camera"] DEPENDENCIES = ["esp32"] +DOMAIN = "esp32_camera" esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 03fdbc4de7c..ef29fc9b25d 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -9,7 +9,7 @@ namespace esphome::esp32_camera { -static const char *const TAG = "esp32_camera"; +ESPHOME_LOG_TAG(TAG, "esp32_camera"); static constexpr size_t FRAMEBUFFER_TASK_STACK_SIZE = 1792; #if ESPHOME_LOG_LEVEL < ESPHOME_LOG_LEVEL_VERBOSE static constexpr uint32_t FRAME_LOG_INTERVAL_MS = 60000; diff --git a/esphome/components/esp32_camera_web_server/__init__.py b/esphome/components/esp32_camera_web_server/__init__.py index d54d5c69376..c063eb88be5 100644 --- a/esphome/components/esp32_camera_web_server/__init__.py +++ b/esphome/components/esp32_camera_web_server/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@ayufan"] AUTO_LOAD = ["camera"] DEPENDENCIES = ["network"] +DOMAIN = "esp32_camera_web_server" MULTI_CONF = True esp32_camera_web_server_ns = cg.esphome_ns.namespace("esp32_camera_web_server") diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp index bee231d132d..36c1cdfa45b 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.cpp +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -16,7 +16,7 @@ namespace esphome::esp32_camera_web_server { static const uint32_t IMAGE_REQUEST_TIMEOUT = 5000; // How often streaming_handler_ reports its throughput. static const uint32_t STREAM_STATS_INTERVAL = 5000; -static const char *const TAG = "esp32_camera_web_server"; +ESPHOME_LOG_TAG(TAG, "esp32_camera_web_server"); #define PART_BOUNDARY "123456789000000000000987654321" #define CONTENT_TYPE "image/jpeg" diff --git a/esphome/components/esp32_can/__init__.py b/esphome/components/esp32_can/__init__.py index e69de29bb2d..eb2b5b2aaa7 100644 --- a/esphome/components/esp32_can/__init__.py +++ b/esphome/components/esp32_can/__init__.py @@ -0,0 +1 @@ +DOMAIN = "esp32_can" diff --git a/esphome/components/esp32_can/esp32_can.cpp b/esphome/components/esp32_can/esp32_can.cpp index e04405c63c0..083c97f8cfe 100644 --- a/esphome/components/esp32_can/esp32_can.cpp +++ b/esphome/components/esp32_can/esp32_can.cpp @@ -11,7 +11,7 @@ namespace esphome::esp32_can { -static const char *const TAG = "esp32_can"; +ESPHOME_LOG_TAG(TAG, "esp32_can"); static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config) { switch (bitrate) { diff --git a/esphome/components/esp32_dac/__init__.py b/esphome/components/esp32_dac/__init__.py index e69de29bb2d..6aa72c6456b 100644 --- a/esphome/components/esp32_dac/__init__.py +++ b/esphome/components/esp32_dac/__init__.py @@ -0,0 +1 @@ +DOMAIN = "esp32_dac" diff --git a/esphome/components/esp32_dac/esp32_dac.cpp b/esphome/components/esp32_dac/esp32_dac.cpp index 54b89c46ad0..3517941de1e 100644 --- a/esphome/components/esp32_dac/esp32_dac.cpp +++ b/esphome/components/esp32_dac/esp32_dac.cpp @@ -12,7 +12,7 @@ static constexpr uint8_t DAC0_PIN = 17; static constexpr uint8_t DAC0_PIN = 25; #endif -static const char *const TAG = "esp32_dac"; +ESPHOME_LOG_TAG(TAG, "esp32_dac"); void ESP32DAC::setup() { this->pin_->setup(); diff --git a/esphome/components/esp32_hall/__init__.py b/esphome/components/esp32_hall/__init__.py index e69de29bb2d..a8e97842ae4 100644 --- a/esphome/components/esp32_hall/__init__.py +++ b/esphome/components/esp32_hall/__init__.py @@ -0,0 +1 @@ +DOMAIN = "esp32_hall" diff --git a/esphome/components/esp32_hosted/__init__.py b/esphome/components/esp32_hosted/__init__.py index d78941ea4c8..f84f9c64642 100644 --- a/esphome/components/esp32_hosted/__init__.py +++ b/esphome/components/esp32_hosted/__init__.py @@ -28,6 +28,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@swoboda1337"] DEPENDENCIES = ["esp32"] +DOMAIN = "esp32_hosted" # esp32_ble raises the task watchdog around the remote BT controller bring-up AUTO_LOAD = ["watchdog"] diff --git a/esphome/components/esp32_hosted/esp_now_hosted.cpp b/esphome/components/esp32_hosted/esp_now_hosted.cpp index ad29b208feb..03b03fd983f 100644 --- a/esphome/components/esp32_hosted/esp_now_hosted.cpp +++ b/esphome/components/esp32_hosted/esp_now_hosted.cpp @@ -45,7 +45,7 @@ extern "C" { namespace { -const char *const TAG = "esp_now_hosted"; +ESPHOME_LOG_TAG(TAG, "esp_now_hosted"); // One outstanding request at a time. ESPHome drives esp_now_* from the main // loop; the matching response and the async RECV/SEND events all arrive on the diff --git a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp index c35301a6852..7b9056bc809 100644 --- a/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp +++ b/esphome/components/esp32_hosted/update/esp32_hosted_update.cpp @@ -22,7 +22,7 @@ extern "C" { namespace esphome::esp32_hosted { -static const char *const TAG = "esp32_hosted.update"; +ESPHOME_LOG_TAG(TAG, "esp32_hosted.update"); // Older coprocessor firmware versions have a 1500-byte limit per RPC call constexpr size_t CHUNK_SIZE = 1500; diff --git a/esphome/components/esp32_rmt/__init__.py b/esphome/components/esp32_rmt/__init__.py index a213a787786..9d3562ed7bc 100644 --- a/esphome/components/esp32_rmt/__init__.py +++ b/esphome/components/esp32_rmt/__init__.py @@ -7,6 +7,7 @@ from esphome.core import CORE from esphome.types import ConfigType CODEOWNERS = ["@jesserockz"] +DOMAIN = "esp32_rmt" VARIANTS_NO_RMT = {esp32.VARIANT_ESP32C2, esp32.VARIANT_ESP32C61} diff --git a/esphome/components/esp32_rmt_led_strip/__init__.py b/esphome/components/esp32_rmt_led_strip/__init__.py index e69de29bb2d..a33110a7d37 100644 --- a/esphome/components/esp32_rmt_led_strip/__init__.py +++ b/esphome/components/esp32_rmt_led_strip/__init__.py @@ -0,0 +1 @@ +DOMAIN = "esp32_rmt_led_strip" diff --git a/esphome/components/esp32_rmt_led_strip/led_strip.cpp b/esphome/components/esp32_rmt_led_strip/led_strip.cpp index 7cac1dfb41c..420ff64ddc6 100644 --- a/esphome/components/esp32_rmt_led_strip/led_strip.cpp +++ b/esphome/components/esp32_rmt_led_strip/led_strip.cpp @@ -11,7 +11,7 @@ namespace esphome::esp32_rmt_led_strip { -static const char *const TAG = "esp32_rmt_led_strip"; +ESPHOME_LOG_TAG(TAG, "esp32_rmt_led_strip"); static const size_t RMT_SYMBOLS_PER_BYTE = 8; diff --git a/esphome/components/esp32_touch/__init__.py b/esphome/components/esp32_touch/__init__.py index ede6beb9b6e..bdd16bc2eff 100644 --- a/esphome/components/esp32_touch/__init__.py +++ b/esphome/components/esp32_touch/__init__.py @@ -31,6 +31,7 @@ _LOGGER = logging.getLogger(__name__) AUTO_LOAD = ["binary_sensor"] DEPENDENCIES = ["esp32"] +DOMAIN = "esp32_touch" CONF_DEBOUNCE_COUNT = "debounce_count" CONF_DENOISE_GRADE = "denoise_grade" diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index 54bbbe52ed8..ed7d0bd7cb2 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -13,7 +13,7 @@ template static const char *lookup_str(const char *const (&table)[N], return (index < N) ? table[index] : "UNKNOWN"; } -static const char *const TAG = "esp32_touch"; +ESPHOME_LOG_TAG(TAG, "esp32_touch"); static constexpr uint32_t SETUP_MODE_LOG_INTERVAL_MS = 250; static constexpr uint32_t INITIAL_STATE_DELAY_MS = 1500; diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index e4d8dfc4928..d95cbd239b5 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -53,6 +53,7 @@ from .const import ( KEY_SERIAL1_REQUIRED, KEY_SERIAL_REQUIRED, KEY_WAVEFORM_REQUIRED, + THROW_STUBS_HEADER, enable_serial, enable_serial1, esp8266_ns, @@ -70,6 +71,7 @@ CONF_ENABLE_SCANF_FLOAT = "enable_scanf_float" _SCANF_FLOAT_RE = re.compile(r"scanf\s*\([^;]*?%[*\d.]*[hlL]*[feEgGaAF]") CODEOWNERS = ["@esphome/core"] +DOMAIN = "esp8266" _LOGGER = logging.getLogger(__name__) AUTO_LOAD = ["preferences"] IS_TARGET_PLATFORM = True @@ -114,9 +116,37 @@ _validate_toolchain = cv.toolchain_enum(_TOOLCHAINS) _resolve_toolchain = cv.resolve_toolchain("ESP8266", _TOOLCHAINS, Toolchain.ARDUINO) +# Known boards keyed by spelling with case and "-" / "_" folded away +_FOLDED_BOARDS = {board.lower().replace("-", "_"): board for board in BOARDS} + + +def _resolve_board(config: ConfigType) -> ConfigType: + """Fix a misspelled board, else build an unknown one with PlatformIO.""" + board = config[CONF_BOARD] + if board in BOARDS: + return config + if canonical := _FOLDED_BOARDS.get(board.lower().replace("-", "_")): + _LOGGER.warning( + "Board '%s' is not a known ESP8266 board; using '%s'", board, canonical + ) + return {**config, CONF_BOARD: canonical} + if CORE.toolchain is None and CONF_TOOLCHAIN not in config: + return {**config, CONF_TOOLCHAIN: Toolchain.PLATFORMIO} + return config + + def _warn_platformio_toolchain(config: ConfigType) -> ConfigType: # Remove before 2027.4.0 - if CORE.using_toolchain_platformio: + if not CORE.using_toolchain_platformio: + return config + if config[CONF_BOARD] not in BOARDS: + _LOGGER.warning( + "Board '%s' is not supported by the native 'arduino' toolchain, so it " + "builds with the deprecated 'platformio' toolchain, which will be " + "removed in ESPHome 2027.4.0", + config[CONF_BOARD], + ) + else: _LOGGER.warning( "The 'platformio' toolchain for ESP8266 is deprecated and will be " "removed in ESPHome 2027.4.0; the native 'arduino' toolchain is the " @@ -329,6 +359,7 @@ CONFIG_SCHEMA = cv.All( ): _validate_toolchain, } ), + _resolve_board, _resolve_toolchain, _warn_platformio_toolchain, _validate_native_toolchain, @@ -413,6 +444,8 @@ async def to_code(config: ConfigType) -> None: cg.add_build_flag("-DUSE_ARDUINO") cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO") cg.add_build_flag("-Wno-nonnull-compare") + # .rodata is linked into RAM on ESP8266; keep switch lookup tables (CSWTCH) out of it + cg.add_build_flag("-fno-tree-switch-conversion") if use_platformio: cg.add_platformio_option("framework", "arduino") cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) @@ -448,11 +481,9 @@ async def to_code(config: ConfigType) -> None: # Force-include inline std::__throw_* overrides so GCC dead-strips the unused # libstdc++ error message strings (e.g. "basic_string::_M_create") from DRAM. - # See throw_stubs.h. Unconditional: the native build generator reads - # the same option, keeping one source of truth. - cg.add_platformio_option( - "build_src_flags", "-include esphome/components/esp8266/throw_stubs.h" - ) + # See throw_stubs.h. The native build generator reads this option, also + # passes it to the core and libraries, and drops it with exceptions on. + cg.add_platformio_option("build_src_flags", f"-include {THROW_STUBS_HEADER}") # In testing mode, fake larger memory to allow linking grouped component tests # Real ESP8266 hardware only has 32KB IRAM and ~80KB RAM, but for CI testing diff --git a/esphome/components/esp8266/const.py b/esphome/components/esp8266/const.py index bf4dd1a3584..fb50b6a5ca2 100644 --- a/esphome/components/esp8266/const.py +++ b/esphome/components/esp8266/const.py @@ -17,6 +17,8 @@ KEY_SERIAL_REQUIRED = "serial_required" KEY_SERIAL1_REQUIRED = "serial1_required" # Set for the native (non-PlatformIO) toolchain's build generator KEY_SCANF_FLOAT = "scanf_float" +# Force-included std::__throw_* overrides, relative to the src dir +THROW_STUBS_HEADER = "esphome/components/esp8266/throw_stubs.h" # Per-board flash-layout override consumed by board_ld_script() KEY_LDSCRIPT = "ldscript" # Crash-decode tools, resolved once per run by __init__ diff --git a/esphome/components/esp8266/crash_handler.cpp b/esphome/components/esp8266/crash_handler.cpp index dc79043f217..1ba96eaebb2 100644 --- a/esphome/components/esp8266/crash_handler.cpp +++ b/esphome/components/esp8266/crash_handler.cpp @@ -82,7 +82,7 @@ static_assert(sizeof(RtcCrashData) == 72, "RtcCrashData must fit in 18 RTC block namespace esphome::esp8266 { -static const char *const TAG = "esp8266"; +ESPHOME_LOG_TAG(TAG, "esp8266"); static inline bool is_crash_reason(uint32_t reason) { return reason == REASON_WDT_RST || reason == REASON_EXCEPTION_RST || reason == REASON_SOFT_WDT_RST; diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index 66ac122f000..1d97b2c1e56 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -1,11 +1,12 @@ #ifdef USE_ESP8266 #include "gpio.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" namespace esphome::esp8266 { -static const char *const TAG = "esp8266"; +ESPHOME_LOG_TAG(TAG, "esp8266"); static int flags_to_mode(gpio::Flags flags, uint8_t pin) { flags = @@ -74,7 +75,7 @@ ISRInternalGPIOPin ESP8266GPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } -void ESP8266GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { +void ESP8266GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { uint8_t arduino_mode = 0; switch (type) { case gpio::INTERRUPT_RISING_EDGE: diff --git a/esphome/components/esp8266/gpio.h b/esphome/components/esp8266/gpio.h index 57ef06106a2..84968d97527 100644 --- a/esphome/components/esp8266/gpio.h +++ b/esphome/components/esp8266/gpio.h @@ -2,12 +2,12 @@ #ifdef USE_ESP8266 -#include "esphome/core/hal.h" +#include "esphome/core/gpio_pin.h" #include namespace esphome::esp8266 { -class ESP8266GPIOPin final : public InternalGPIOPin { +class ESP8266GPIOPin final : public GPIOPin { public: void set_pin(uint8_t pin) { pin_ = pin; } void set_inverted(bool inverted) { inverted_ = inverted; } @@ -18,14 +18,18 @@ class ESP8266GPIOPin final : public InternalGPIOPin { bool digital_read() override; void digital_write(bool value) override; size_t dump_summary(char *buffer, size_t len) const override; - void detach_interrupt() const override; - ISRInternalGPIOPin to_isr() const override; - uint8_t get_pin() const override { return pin_; } + void detach_interrupt() const; + template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { + this->attach_interrupt_(reinterpret_cast(func), arg, type); + } + ISRInternalGPIOPin to_isr() const; + uint8_t get_pin() const { return pin_; } gpio::Flags get_flags() const override { return flags_; } - bool is_inverted() const override { return inverted_; } + bool is_inverted() const { return inverted_; } + bool is_internal() override { return true; } protected: - void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const; uint8_t pin_; bool inverted_{}; diff --git a/esphome/components/esp8266/hal.h b/esphome/components/esp8266/hal.h index f3b33da6927..1a54065541e 100644 --- a/esphome/components/esp8266/hal.h +++ b/esphome/components/esp8266/hal.h @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -40,6 +41,9 @@ void delay_microseconds_safe(uint32_t us); /// which is ISR-safe) so this helper is unused on this platform. __attribute__((always_inline)) inline bool in_isr_context() { return false; } +/// SDK SYS callbacks and interrupt handlers cannot yield, so they are not the loop context. +__attribute__((always_inline)) inline bool is_main_loop_thread() { return can_yield(); } + __attribute__((always_inline)) inline void yield() { ::yield(); } __attribute__((always_inline)) inline uint32_t micros() { return static_cast(::micros()); } void delay(uint32_t ms); diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index d954ae4a0f3..b731187f9c3 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -14,7 +14,7 @@ extern "C" { namespace esphome::esp8266 { -static const char *const TAG = "preferences"; +ESPHOME_LOG_TAG(TAG, "preferences"); static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200; static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128; diff --git a/esphome/components/esp8266/throw_stubs.h b/esphome/components/esp8266/throw_stubs.h index a650935a5e2..c18814d4d80 100644 --- a/esphome/components/esp8266/throw_stubs.h +++ b/esphome/components/esp8266/throw_stubs.h @@ -15,7 +15,8 @@ * unused, dead-strip the load, and drop the string entirely -- no LTO needed. * Behavior is identical to today: a bare abort() (the message was never * printed). This header MUST be force-included before , so it is - * wired up via build_src_flags "-include ..." in this component's __init__.py. + * wired up via build_src_flags "-include ..." in this component's __init__.py; + * the native toolchain also force-includes it into the core and libraries. * * Note: this defines functions in namespace std (technically UB). It is safe * here because the definitions match the existing abort() behavior exactly. diff --git a/esphome/components/esp8266_pwm/__init__.py b/esphome/components/esp8266_pwm/__init__.py index e69de29bb2d..741b06bb8bf 100644 --- a/esphome/components/esp8266_pwm/__init__.py +++ b/esphome/components/esp8266_pwm/__init__.py @@ -0,0 +1 @@ +DOMAIN = "esp8266_pwm" diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.cpp b/esphome/components/esp8266_pwm/esp8266_pwm.cpp index b5b3d5073aa..c01a69ce10d 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.cpp +++ b/esphome/components/esp8266_pwm/esp8266_pwm.cpp @@ -10,7 +10,7 @@ namespace esphome::esp8266_pwm { -static const char *const TAG = "esp8266_pwm"; +ESPHOME_LOG_TAG(TAG, "esp8266_pwm"); void ESP8266PWM::setup() { this->pin_->setup(); diff --git a/esphome/components/esp_ldo/esp_ldo.cpp b/esphome/components/esp_ldo/esp_ldo.cpp index b3c9a59865d..e978dd12b02 100644 --- a/esphome/components/esp_ldo/esp_ldo.cpp +++ b/esphome/components/esp_ldo/esp_ldo.cpp @@ -5,7 +5,7 @@ namespace esphome::esp_ldo { -static const char *const TAG = "esp_ldo"; +ESPHOME_LOG_TAG(TAG, "esp_ldo"); void EspLdo::setup() { esp_ldo_channel_config_t config{}; config.chan_id = this->channel_; diff --git a/esphome/components/espectre/__init__.py b/esphome/components/espectre/__init__.py new file mode 100644 index 00000000000..4f10db996c9 --- /dev/null +++ b/esphome/components/espectre/__init__.py @@ -0,0 +1,186 @@ +from ipaddress import IPv4Address + +import esphome.codegen as cg +from esphome.components import esp32, wifi +from esphome.components.esp32.const import ( + VARIANT_ESP32, + VARIANT_ESP32C3, + VARIANT_ESP32C5, + VARIANT_ESP32C6, + VARIANT_ESP32S2, + VARIANT_ESP32S3, +) +from esphome.components.wifi import DOMAIN as WIFI_DOMAIN +import esphome.config_validation as cv +from esphome.const import CONF_ID +from esphome.core import CORE +from esphome.types import ConfigType + +DOMAIN = "espectre" +CODEOWNERS = ["@francescopace"] +DEPENDENCIES = ["esp32", "wifi"] + +CONF_ESPECTRE_ID = "espectre_id" +CONF_DETECTION_ALGORITHM = "detection_algorithm" +CONF_CSI_CAPTURE_PROFILE = "csi_capture_profile" +CONF_TRAFFIC_GENERATOR_MODE = "traffic_generator_mode" +CONF_TRAFFIC_GENERATOR_TARGET_IP = "traffic_generator_target_ip" +CONF_CSI_TRAFFIC_MULTICAST_GROUP = "csi_traffic_multicast_group" +CONF_MOTION_ON_HITS = "motion_on_hits" +CONF_MOTION_OFF_HITS = "motion_off_hits" + +# Fully qualified: a bare espectre:: in main.cpp would clash with the SDK namespace. +espectre_ns = cg.global_ns.namespace("esphome").namespace("espectre") +ESPectreComponent = espectre_ns.class_("ESPectreComponent", cg.Component) +sdk_ns = cg.global_ns.namespace("::espectre") +DetectionAlgorithm = sdk_ns.enum("DetectionAlgorithm", is_class=True) +CsiCapturePolicy = sdk_ns.enum("CsiCapturePolicy", is_class=True) +TrafficGeneratorMode = sdk_ns.enum("TrafficGeneratorMode", is_class=True) +WifiBandPolicy = sdk_ns.enum("WifiBandPolicy", is_class=True) + +DETECTION_ALGORITHMS = { + "lightweight": DetectionAlgorithm.LIGHTWEIGHT, + "high_accuracy": DetectionAlgorithm.HIGH_ACCURACY, +} +CSI_CAPTURE_PROFILES = { + "auto": CsiCapturePolicy.AUTO, + "lltf": CsiCapturePolicy.LLTF, + "ht_vht": CsiCapturePolicy.HT_VHT, +} +TRAFFIC_GENERATOR_MODES = { + "ping": TrafficGeneratorMode.PING, + "dns": TrafficGeneratorMode.DNS, + "dns_tcp": TrafficGeneratorMode.DNS_TCP, + "wifi_raw": TrafficGeneratorMode.WIFI_RAW, + "external": TrafficGeneratorMode.EXTERNAL_HOST, +} + + +def validate_target_ip(value: str) -> str: + value = str(cv.ipv4address(value)) + first_octet = int(IPv4Address(value)) >> 24 + if first_octet in (0, 127) or first_octet >= 224 or value == "255.255.255.255": + raise cv.Invalid("ESPectre traffic target must be a unicast IPv4 address") + return value + + +def validate_multicast_group(value: str) -> str: + """An IPv4 multicast group, or an empty string to skip joining one.""" + if not (value := cv.string_strict(value).strip()): + return value + value = str(cv.ipv4address(value)) + if not IPv4Address(value).is_multicast: + raise cv.Invalid("ESPectre multicast group must be an IPv4 multicast address") + return value + + +def supported_traffic_generator_modes(config: ConfigType) -> list[str]: + """Traffic generator modes available with this chip and CSI capture profile.""" + wifi_raw = ( + esp32.get_esp32_variant() != VARIANT_ESP32C6 + and config[CONF_CSI_CAPTURE_PROFILE] != "ht_vht" + ) + return [mode for mode in TRAFFIC_GENERATOR_MODES if wifi_raw or mode != "wifi_raw"] + + +def validate_config(config: ConfigType) -> ConfigType: + mode = config[CONF_TRAFFIC_GENERATOR_MODE] + if mode not in supported_traffic_generator_modes(config): + raise cv.Invalid( + "wifi_raw traffic is not supported on ESP32-C6 " + "or with the ht_vht CSI capture profile" + ) + if mode in ("wifi_raw", "external") and CONF_TRAFFIC_GENERATOR_TARGET_IP in config: + raise cv.Invalid(f"{mode} traffic does not use a target IP address") + if mode != "external" and CONF_CSI_TRAFFIC_MULTICAST_GROUP in config: + raise cv.Invalid( + f"{CONF_CSI_TRAFFIC_MULTICAST_GROUP} requires " + f"{CONF_TRAFFIC_GENERATOR_MODE}: external" + ) + return config + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ESPectreComponent), + cv.Optional(CONF_DETECTION_ALGORITHM, default="lightweight"): cv.enum( + DETECTION_ALGORITHMS, lower=True + ), + cv.Optional(CONF_CSI_CAPTURE_PROFILE, default="auto"): cv.enum( + CSI_CAPTURE_PROFILES, lower=True + ), + cv.Optional(CONF_TRAFFIC_GENERATOR_MODE, default="ping"): cv.enum( + TRAFFIC_GENERATOR_MODES, lower=True + ), + cv.Optional(CONF_TRAFFIC_GENERATOR_TARGET_IP): validate_target_ip, + cv.Optional(CONF_CSI_TRAFFIC_MULTICAST_GROUP): validate_multicast_group, + cv.Optional(CONF_MOTION_ON_HITS): cv.int_range(min=1, max=20), + cv.Optional(CONF_MOTION_OFF_HITS): cv.int_range(min=1, max=20), + } + ).extend(cv.COMPONENT_SCHEMA), + esp32.only_on_variant( + supported=[ + VARIANT_ESP32, + VARIANT_ESP32S2, + VARIANT_ESP32S3, + VARIANT_ESP32C3, + VARIANT_ESP32C5, + VARIANT_ESP32C6, + ], + msg_prefix="ESPectre", + ), + # Arduino 3.3.7 is the first release built on ESP-IDF 5.5.3. + cv.require_framework_version( + esp_idf=cv.Version(5, 5, 3), esp32_arduino=cv.Version(3, 3, 7) + ), + validate_config, +) + + +def final_validate(config: ConfigType) -> None: + wifi.force_power_save_off( + "ESPectre needs the radio awake to receive a steady flow of CSI packets" + ) + + +FINAL_VALIDATE_SCHEMA = final_validate + + +async def to_code(config: ConfigType) -> None: + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + cg.add_define("USE_ESPECTRE") + cg.add(var.set_detection_algorithm(config[CONF_DETECTION_ALGORITHM])) + cg.add(var.set_csi_capture_profile(config[CONF_CSI_CAPTURE_PROFILE])) + cg.add(var.set_traffic_generator_mode(config[CONF_TRAFFIC_GENERATOR_MODE])) + if (target_ip := config.get(CONF_TRAFFIC_GENERATOR_TARGET_IP)) is not None: + cg.add(var.set_traffic_generator_target_ip(target_ip)) + if (group := config.get(CONF_CSI_TRAFFIC_MULTICAST_GROUP)) is not None: + cg.add(var.set_csi_traffic_multicast_group(group)) + if (on_hits := config.get(CONF_MOTION_ON_HITS)) is not None: + cg.add(var.set_motion_on_hits(on_hits)) + if (off_hits := config.get(CONF_MOTION_OFF_HITS)) is not None: + cg.add(var.set_motion_off_hits(off_hits)) + if esp32.get_esp32_variant() == VARIANT_ESP32C5: + band = CORE.config[WIFI_DOMAIN].get(wifi.CONF_BAND_MODE, "AUTO") + cg.add( + var.set_wifi_band_policy( + { + "2.4GHZ": WifiBandPolicy.BAND_2G, + "5GHZ": WifiBandPolicy.BAND_5G, + "AUTO": WifiBandPolicy.AUTO, + }[band] + ) + ) + + wifi.enable_runtime_roaming_suppression() + esp32.add_idf_component(name="francescopace/espectre", ref="3.0.0") + esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_CSI_ENABLED", True) + # CSI is reported once per received transmission, so aggregation hides frames from sensing. + # Disabling TX aggregation also lets the SDK fix the station TX rate (6.5 Mbps on ESP32). + # Both may lower Wi-Fi throughput for the whole firmware. + esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_TX_ENABLED", False) + esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_AMPDU_RX_ENABLED", False) + # Keep the radio awake while disconnected too, matching force_power_save_off(). + esp32.add_idf_sdkconfig_option("CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE", False) diff --git a/esphome/components/espectre/binary_sensor.py b/esphome/components/espectre/binary_sensor.py new file mode 100644 index 00000000000..c012f823cfa --- /dev/null +++ b/esphome/components/espectre/binary_sensor.py @@ -0,0 +1,32 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import CONF_MOTION, DEVICE_CLASS_MOTION, ENTITY_CATEGORY_DIAGNOSTIC +from esphome.types import ConfigType + +from . import CONF_ESPECTRE_ID, ESPectreComponent + +DEPENDENCIES = ["espectre"] +CONF_CALIBRATING = "calibrating" + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_ESPECTRE_ID): cv.use_id(ESPectreComponent), + cv.Optional(CONF_MOTION): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_MOTION + ), + cv.Optional(CONF_CALIBRATING): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC + ), + } + ), + cv.has_at_least_one_key(CONF_MOTION, CONF_CALIBRATING), +) + + +async def to_code(config: ConfigType) -> None: + hub = await cg.get_variable(config[CONF_ESPECTRE_ID]) + binary_sensors = binary_sensor.sub_binary_sensors(config) + await binary_sensors(CONF_MOTION, hub.set_motion_binary_sensor) + await binary_sensors(CONF_CALIBRATING, hub.set_calibrating_binary_sensor) diff --git a/esphome/components/espectre/button/__init__.py b/esphome/components/espectre/button/__init__.py new file mode 100644 index 00000000000..c6a167324e0 --- /dev/null +++ b/esphome/components/espectre/button/__init__.py @@ -0,0 +1,20 @@ +import esphome.codegen as cg +from esphome.components import button +import esphome.config_validation as cv +from esphome.const import ENTITY_CATEGORY_CONFIG +from esphome.types import ConfigType + +from .. import CONF_ESPECTRE_ID, ESPectreComponent, espectre_ns + +DEPENDENCIES = ["espectre"] + +RecalibrateButton = espectre_ns.class_("RecalibrateButton", button.Button) + +CONFIG_SCHEMA = button.button_schema( + RecalibrateButton, entity_category=ENTITY_CATEGORY_CONFIG +).extend({cv.GenerateID(CONF_ESPECTRE_ID): cv.use_id(ESPectreComponent)}) + + +async def to_code(config: ConfigType) -> None: + var = await button.new_button(config) + await cg.register_parented(var, config[CONF_ESPECTRE_ID]) diff --git a/esphome/components/espectre/button/espectre_button.h b/esphome/components/espectre/button/espectre_button.h new file mode 100644 index 00000000000..f89c42f8fe9 --- /dev/null +++ b/esphome/components/espectre/button/espectre_button.h @@ -0,0 +1,19 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_ESPECTRE + +#include "esphome/components/button/button.h" +#include "../espectre.h" + +namespace esphome::espectre { + +class RecalibrateButton final : public button::Button, public Parented { + protected: + void press_action() override { this->parent_->recalibrate(); } +}; + +} // namespace esphome::espectre + +#endif // USE_ESPECTRE diff --git a/esphome/components/espectre/espectre.cpp b/esphome/components/espectre/espectre.cpp new file mode 100644 index 00000000000..c758a3b2466 --- /dev/null +++ b/esphome/components/espectre/espectre.cpp @@ -0,0 +1,305 @@ +#include "espectre.h" + +#ifdef USE_ESPECTRE + +#include +#include +#include +#include "esphome/core/application.h" +#include "esphome/core/log.h" +#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_ROAMING_SUPPRESSION) +#include "esphome/components/wifi/wifi_component.h" +#endif + +namespace esphome::espectre { + +ESPHOME_LOG_TAG(TAG, "espectre"); + +struct TrafficModeOption { + const char *name; + ::espectre::TrafficGeneratorMode mode; +}; +// Option names match the traffic_generator_mode YAML values. +static constexpr TrafficModeOption TRAFFIC_MODE_OPTIONS[] = { + {"ping", ::espectre::TrafficGeneratorMode::PING}, + {"dns", ::espectre::TrafficGeneratorMode::DNS}, + {"dns_tcp", ::espectre::TrafficGeneratorMode::DNS_TCP}, + {"wifi_raw", ::espectre::TrafficGeneratorMode::WIFI_RAW}, + {"external", ::espectre::TrafficGeneratorMode::EXTERNAL_HOST}, +}; + +static const char *traffic_mode_name(::espectre::TrafficGeneratorMode mode) { + for (const auto &option : TRAFFIC_MODE_OPTIONS) { + if (option.mode == mode) + return option.name; + } + return nullptr; +} + +static const char *csi_capture_profile_name(::espectre::CsiCapturePolicy profile) { + switch (profile) { + case ::espectre::CsiCapturePolicy::LLTF: + return LOG_STR_LITERAL("lltf"); + case ::espectre::CsiCapturePolicy::HT_VHT: + return LOG_STR_LITERAL("ht_vht"); + default: + return LOG_STR_LITERAL("auto"); + } +} + +#ifdef USE_SELECT +void ESPectreComponent::request_traffic_generator_mode(const char *name) { + for (const auto &option : TRAFFIC_MODE_OPTIONS) { + if (strcmp(option.name, name) == 0) { + this->pending_traffic_mode_ = option.mode; + return; + } + } +} +#endif + +static int log_level(::espectre::LogLevel level) { + switch (level) { + case ::espectre::LogLevel::ERROR: + return ESPHOME_LOG_LEVEL_ERROR; + case ::espectre::LogLevel::WARNING: + return ESPHOME_LOG_LEVEL_WARN; + case ::espectre::LogLevel::INFO: + return ESPHOME_LOG_LEVEL_INFO; + case ::espectre::LogLevel::DEBUG: + return ESPHOME_LOG_LEVEL_DEBUG; + default: + return ESPHOME_LOG_LEVEL_VERBOSE; + } +} + +void ESPectreComponent::setup() { + // ESPHome owns scan results, including scans requested by the SDK's CSI recovery. + this->runtime_.config().wifi_scan_results_managed_externally = true; +#ifdef USE_SELECT + this->restore_traffic_mode_(); +#endif + this->start_runtime_(); +} + +void ESPectreComponent::start_runtime_() { +#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_ROAMING_SUPPRESSION) + // A sensor stays in one place, and each roaming scan takes the radio off-channel for + // seconds, emptying the CSI window. Losing the access point still reconnects normally. + if (wifi::global_wifi_component != nullptr) { + wifi::global_wifi_component->request_roaming_suppression(); + this->roaming_suppressed_ = true; + } +#endif + ::espectre::set_log_sink({ + .context = nullptr, + .enabled = [](void *, ::espectre::LogLevel level, const char *) { return log_level(level) <= ESPHOME_LOG_LEVEL; }, + .write = [](void *, ::espectre::LogLevel level, const char *tag, int line, const char *format, + va_list args) { esp_log_vprintf_(log_level(level), tag, line, format, args); }, + }); + if (!this->runtime_.setup(this)) { + ESP_LOGE(TAG, "Runtime setup failed"); + this->stop_(); + this->schedule_restart_(); + return; + } + this->running_ = true; + this->status_clear_error(); +#ifdef USE_SELECT + this->publish_traffic_mode_(); +#endif +} + +void ESPectreComponent::schedule_restart_() { + // A fault can be transient, such as a Wi-Fi stall, so retry instead of failing for good. + this->runtime_fault_ = false; + this->status_set_error(LOG_STR("Runtime stopped")); + ESP_LOGW(TAG, "Restarting the runtime in %" PRIu32 " s", RESTART_DELAY_MS / 1000); + this->restart_pending_ = true; + this->restart_requested_ms_ = App.get_loop_component_start_time(); +} + +#ifdef USE_SELECT +void ESPectreComponent::restore_traffic_mode_() { + if (this->traffic_mode_select_ == nullptr) + return; + // Keyed by the YAML mode, so changing it in YAML discards a mode saved from the select. + const auto yaml_mode = this->runtime_.config().traffic_generator_mode; + this->traffic_mode_pref_ = + this->traffic_mode_select_->make_entity_preference(static_cast(yaml_mode) + 1); + uint8_t saved; + if (!this->traffic_mode_pref_.load(&saved)) + return; + const auto mode = static_cast<::espectre::TrafficGeneratorMode>(saved); + const char *name = traffic_mode_name(mode); + if (name != nullptr && this->traffic_mode_select_->has_option(name)) + this->runtime_.config().traffic_generator_mode = mode; +} + +void ESPectreComponent::apply_pending_traffic_mode_() { + if (!this->pending_traffic_mode_.has_value()) + return; + const auto mode = *this->pending_traffic_mode_; + this->pending_traffic_mode_.reset(); + if (this->runtime_.set_traffic_generator_mode(mode)) { + const auto saved = static_cast(mode); + this->traffic_mode_pref_.save(&saved); + } else { + ESP_LOGW(TAG, "Traffic generator mode %s was rejected", traffic_mode_name(mode)); + } + this->publish_traffic_mode_(); +} + +void ESPectreComponent::publish_traffic_mode_() { + if (this->traffic_mode_select_ == nullptr) + return; + const char *name = traffic_mode_name(this->runtime_.config().traffic_generator_mode); + if (name != nullptr) + this->traffic_mode_select_->publish_state(name); +} +#endif + +void ESPectreComponent::loop() { + if (this->runtime_fault_) { + this->stop_(); + this->schedule_restart_(); + return; + } + if (!this->running_) { + // Without a backend this only reaps a traffic worker that outlived the last runtime. + this->runtime_.loop(); + if (this->restart_pending_ && + App.get_loop_component_start_time() - this->restart_requested_ms_ >= RESTART_DELAY_MS) { + this->restart_pending_ = false; + this->start_runtime_(); + } + return; + } + if (this->recalibrate_pending_) { + this->recalibrate_pending_ = false; + if (!this->runtime_.trigger_recalibration()) { + ESP_LOGW(TAG, "Recalibration is not available"); + } + } +#ifdef USE_SELECT + this->apply_pending_traffic_mode_(); +#endif + this->runtime_.loop(); + if (this->runtime_fault_) + return; + + // Read once after the SDK finishes dispatching callbacks, including readiness changes. + const auto &snapshot = this->runtime_.snapshot(); +#ifdef USE_BINARY_SENSOR + if (this->calibrating_binary_sensor_ != nullptr && + (!this->calibrating_published_ || this->calibrating_state_ != snapshot.calibrating)) { + this->calibrating_state_ = snapshot.calibrating; + this->calibrating_published_ = true; + this->calibrating_binary_sensor_->publish_state(snapshot.calibrating); + } +#endif + if (!snapshot.ready_to_publish) { + if (this->ready_) + this->invalidate_sensing_(); + this->movement_pending_ = false; + return; + } + +#ifdef USE_BINARY_SENSOR + const bool motion = snapshot.motion_state == ::espectre::MotionState::MOTION; + if (this->motion_binary_sensor_ != nullptr && (!this->ready_ || this->motion_state_ != motion)) { + this->motion_state_ = motion; + this->motion_binary_sensor_->publish_state(motion); + } +#endif +#ifdef USE_SENSOR + if (this->movement_sensor_ != nullptr && (this->movement_pending_ || !this->ready_)) + this->movement_sensor_->publish_state(snapshot.movement_metric); +#endif + this->movement_pending_ = false; + this->ready_ = true; +} + +void ESPectreComponent::invalidate_sensing_() { +#ifdef USE_BINARY_SENSOR + if (this->motion_binary_sensor_ != nullptr) + this->motion_binary_sensor_->invalidate_state(); +#endif +#ifdef USE_SENSOR + if (this->movement_sensor_ != nullptr) + this->movement_sensor_->publish_state(NAN); +#endif + this->ready_ = false; +} + +void ESPectreComponent::on_calibration_finished(const ::espectre::RuntimeSnapshot &snapshot, bool success) { + if (success) { + this->calibrated_ = true; + this->status_clear_warning(); + ESP_LOGI(TAG, "Calibration complete"); + return; + } + // After a successful calibration, a failed one keeps that calibrated threshold. + if (!this->calibrated_) + this->status_set_warning(LOG_STR("Calibration failed")); + ESP_LOGW(TAG, "Calibration failed; retaining the previous threshold"); +} + +void ESPectreComponent::on_runtime_fault(const char *message) { + ESP_LOGE(TAG, "Runtime fault: %s", message); + this->runtime_fault_ = true; +} + +void ESPectreComponent::stop_() { +#if defined(USE_ESP32) && defined(USE_WIFI_RUNTIME_ROAMING_SUPPRESSION) + if (this->roaming_suppressed_ && wifi::global_wifi_component != nullptr) { + wifi::global_wifi_component->release_roaming_suppression(); + } +#endif + this->roaming_suppressed_ = false; + this->running_ = false; + // The next runtime starts from the default threshold and publishes its calibration again. + this->calibrated_ = false; + this->calibrating_published_ = false; + this->runtime_.shutdown(); + ::espectre::clear_log_sink(); + this->invalidate_sensing_(); +#ifdef USE_BINARY_SENSOR + if (this->calibrating_binary_sensor_ != nullptr) + this->calibrating_binary_sensor_->invalidate_state(); +#endif +} + +void ESPectreComponent::on_shutdown() { + this->restart_pending_ = false; + this->stop_(); +} + +void ESPectreComponent::dump_config() { + const auto &config = this->runtime_.config(); + ESP_LOGCONFIG(TAG, + "ESPectre:\n" + " Detection algorithm: %s\n" + " CSI capture profile: %s\n" + " Traffic generator mode: %s\n" + " Motion on/off hits: %u/%u", + config.detection_algorithm == ::espectre::DetectionAlgorithm::LIGHTWEIGHT + ? LOG_STR_LITERAL("lightweight") + : LOG_STR_LITERAL("high_accuracy"), + csi_capture_profile_name(config.csi_capture_policy), traffic_mode_name(config.traffic_generator_mode), + config.motion_on_hits, config.motion_off_hits); +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "Motion", this->motion_binary_sensor_); + LOG_BINARY_SENSOR(" ", "Calibrating", this->calibrating_binary_sensor_); +#endif +#ifdef USE_SENSOR + LOG_SENSOR(" ", "Movement score", this->movement_sensor_); +#endif +#ifdef USE_SELECT + LOG_SELECT(" ", "Traffic generator mode", this->traffic_mode_select_); +#endif +} + +} // namespace esphome::espectre + +#endif // USE_ESPECTRE diff --git a/esphome/components/espectre/espectre.h b/esphome/components/espectre/espectre.h new file mode 100644 index 00000000000..e526c80cb41 --- /dev/null +++ b/esphome/components/espectre/espectre.h @@ -0,0 +1,110 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_ESPECTRE + +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/core/optional.h" +#include +#include +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif +#ifdef USE_SELECT +#include "esphome/components/select/select.h" +#include "esphome/core/preferences.h" +#endif +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif + +namespace esphome::espectre { + +class ESPectreComponent final : public Component, public ::espectre::IRuntimeListener { + public: + void setup() override; + void loop() override; + void dump_config() override; + void on_shutdown() override; + // Register the SDK's Wi-Fi event handlers after network setup, before station startup. + float get_setup_priority() const override { return setup_priority::WIFI + 1.0f; } + + void set_detection_algorithm(::espectre::DetectionAlgorithm value) { + this->runtime_.config().detection_algorithm = value; + } + void set_csi_capture_profile(::espectre::CsiCapturePolicy value) { + this->runtime_.config().csi_capture_policy = value; + } + void set_traffic_generator_mode(::espectre::TrafficGeneratorMode value) { + this->runtime_.config().traffic_generator_mode = value; + } + void set_traffic_generator_target_ip(const std::string &value) { + this->runtime_.config().traffic_generator_target_ip = value; + } + void set_csi_traffic_multicast_group(const std::string &value) { + this->runtime_.config().csi_traffic_multicast_group = value; + } + void set_motion_on_hits(uint8_t value) { this->runtime_.config().motion_on_hits = value; } + void set_motion_off_hits(uint8_t value) { this->runtime_.config().motion_off_hits = value; } + void set_wifi_band_policy(::espectre::WifiBandPolicy value) { this->runtime_.config().wifi_band_policy = value; } + // Queue controls so entity automations cannot re-enter the SDK from a listener callback. + void recalibrate() { this->recalibrate_pending_ = true; } +#ifdef USE_SELECT + void set_traffic_mode_select(select::Select *value) { this->traffic_mode_select_ = value; } + void request_traffic_generator_mode(::espectre::TrafficGeneratorMode mode) { this->pending_traffic_mode_ = mode; } + /// Request a mode by its traffic_generator_mode YAML name; unknown names are ignored. + void request_traffic_generator_mode(const char *name); +#endif + +#ifdef USE_BINARY_SENSOR + SUB_BINARY_SENSOR(motion) + SUB_BINARY_SENSOR(calibrating) +#endif +#ifdef USE_SENSOR + SUB_SENSOR(movement) +#endif + /// Latest one-second runtime diagnostics sample, or nullptr before the runtime starts. + const ::espectre::RuntimeDiagnosticsSample *diagnostics_sample() const { return this->runtime_.diagnostics_sample(); } + + protected: + // Called once per SDK detector evaluation (every 250 ms), which bounds the movement publish rate. + void on_live_telemetry(const ::espectre::RuntimeSnapshot &snapshot) override { this->movement_pending_ = true; } + void on_calibration_finished(const ::espectre::RuntimeSnapshot &snapshot, bool success) override; + void on_runtime_fault(const char *message) override; + void start_runtime_(); + void schedule_restart_(); + void invalidate_sensing_(); + void stop_(); +#ifdef USE_SELECT + void restore_traffic_mode_(); + void apply_pending_traffic_mode_(); + void publish_traffic_mode_(); +#endif + + static constexpr uint32_t RESTART_DELAY_MS = 30000; + + ::espectre::RuntimeFrontendController runtime_; + uint32_t restart_requested_ms_{0}; +#ifdef USE_SELECT + select::Select *traffic_mode_select_{nullptr}; + ESPPreferenceObject traffic_mode_pref_; + optional<::espectre::TrafficGeneratorMode> pending_traffic_mode_; +#endif + bool recalibrate_pending_{false}; + bool movement_pending_{false}; + bool ready_{false}; + bool motion_state_{false}; + bool calibrating_state_{false}; + bool calibrating_published_{false}; + bool runtime_fault_{false}; + bool running_{false}; + bool restart_pending_{false}; + bool calibrated_{false}; + bool roaming_suppressed_{false}; +}; + +} // namespace esphome::espectre + +#endif // USE_ESPECTRE diff --git a/esphome/components/espectre/select/__init__.py b/esphome/components/espectre/select/__init__.py new file mode 100644 index 00000000000..8a4835e58b5 --- /dev/null +++ b/esphome/components/espectre/select/__init__.py @@ -0,0 +1,32 @@ +import esphome.codegen as cg +from esphome.components import select +import esphome.config_validation as cv +from esphome.const import ENTITY_CATEGORY_CONFIG +from esphome.core import CORE +from esphome.types import ConfigType + +from .. import ( + CONF_ESPECTRE_ID, + DOMAIN, + ESPectreComponent, + espectre_ns, + supported_traffic_generator_modes, +) + +DEPENDENCIES = ["espectre"] + +TrafficModeSelect = espectre_ns.class_( + "TrafficModeSelect", select.Select, cg.Parented.template(ESPectreComponent) +) + +CONFIG_SCHEMA = select.select_schema( + TrafficModeSelect, entity_category=ENTITY_CATEGORY_CONFIG +).extend({cv.GenerateID(CONF_ESPECTRE_ID): cv.use_id(ESPectreComponent)}) + + +async def to_code(config: ConfigType) -> None: + options = supported_traffic_generator_modes(CORE.config[DOMAIN]) + var = await select.new_select(config, options=options) + await cg.register_parented(var, config[CONF_ESPECTRE_ID]) + parent = await cg.get_variable(config[CONF_ESPECTRE_ID]) + cg.add(parent.set_traffic_mode_select(var)) diff --git a/esphome/components/espectre/select/espectre_select.cpp b/esphome/components/espectre/select/espectre_select.cpp new file mode 100644 index 00000000000..9569b2955b5 --- /dev/null +++ b/esphome/components/espectre/select/espectre_select.cpp @@ -0,0 +1,11 @@ +#include "espectre_select.h" + +#ifdef USE_ESPECTRE + +namespace esphome::espectre { + +void TrafficModeSelect::control(size_t index) { this->parent_->request_traffic_generator_mode(this->option_at(index)); } + +} // namespace esphome::espectre + +#endif // USE_ESPECTRE diff --git a/esphome/components/espectre/select/espectre_select.h b/esphome/components/espectre/select/espectre_select.h new file mode 100644 index 00000000000..3d27cfabf9e --- /dev/null +++ b/esphome/components/espectre/select/espectre_select.h @@ -0,0 +1,19 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_ESPECTRE + +#include "esphome/components/select/select.h" +#include "../espectre.h" + +namespace esphome::espectre { + +class TrafficModeSelect final : public select::Select, public Parented { + protected: + void control(size_t index) override; +}; + +} // namespace esphome::espectre + +#endif // USE_ESPECTRE diff --git a/esphome/components/espectre/sensor/__init__.py b/esphome/components/espectre/sensor/__init__.py new file mode 100644 index 00000000000..c0efac62042 --- /dev/null +++ b/esphome/components/espectre/sensor/__init__.py @@ -0,0 +1,78 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + ENTITY_CATEGORY_DIAGNOSTIC, + STATE_CLASS_MEASUREMENT, + UNIT_PERCENT, +) +from esphome.types import ConfigType + +from .. import CONF_ESPECTRE_ID, ESPectreComponent, espectre_ns + +DEPENDENCIES = ["espectre"] + +CONF_MOVEMENT = "movement" +CONF_DIAGNOSTICS = "diagnostics" +CONF_GENERATOR_RATE = "generator_rate" +CONF_TRAFFIC_TX_RATE = "traffic_tx_rate" +CONF_TRAFFIC_RX_RATE = "traffic_rx_rate" +CONF_CSI_ACCEPTED_RATE = "csi_accepted_rate" +CONF_CSI_OCCUPANCY = "csi_occupancy" +UNIT_PACKETS_PER_SECOND = "pps" + +DiagnosticsUpdater = espectre_ns.class_("DiagnosticsUpdater", cg.PollingComponent) + + +def _diagnostic_schema(unit: str, accuracy_decimals: int) -> cv.Schema: + return sensor.sensor_schema( + unit_of_measurement=unit, + accuracy_decimals=accuracy_decimals, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ) + + +DIAGNOSTIC_SENSORS = { + CONF_GENERATOR_RATE: _diagnostic_schema(UNIT_PACKETS_PER_SECOND, 1), + CONF_TRAFFIC_TX_RATE: _diagnostic_schema(UNIT_PACKETS_PER_SECOND, 1), + CONF_TRAFFIC_RX_RATE: _diagnostic_schema(UNIT_PACKETS_PER_SECOND, 1), + CONF_CSI_ACCEPTED_RATE: _diagnostic_schema(UNIT_PACKETS_PER_SECOND, 1), + CONF_CSI_OCCUPANCY: _diagnostic_schema(UNIT_PERCENT, 0), +} + +# Diagnostics are rarely watched, so they publish only on request unless an interval is set. +DIAGNOSTICS_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(DiagnosticsUpdater), + **{cv.Optional(key): schema for key, schema in DIAGNOSTIC_SENSORS.items()}, + } + ).extend(cv.polling_component_schema("never")), + cv.has_at_least_one_key(*DIAGNOSTIC_SENSORS), +) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_ESPECTRE_ID): cv.use_id(ESPectreComponent), + cv.Optional(CONF_MOVEMENT): sensor.sensor_schema( + accuracy_decimals=3, state_class=STATE_CLASS_MEASUREMENT + ), + cv.Optional(CONF_DIAGNOSTICS): DIAGNOSTICS_SCHEMA, + } + ), + cv.has_at_least_one_key(CONF_MOVEMENT, CONF_DIAGNOSTICS), +) + + +async def to_code(config: ConfigType) -> None: + hub = await cg.get_variable(config[CONF_ESPECTRE_ID]) + await sensor.sub_sensors(config)(CONF_MOVEMENT, hub.set_movement_sensor) + if (diagnostics_config := config.get(CONF_DIAGNOSTICS)) is not None: + updater = cg.new_Pvariable(diagnostics_config[CONF_ID], hub) + await cg.register_component(updater, diagnostics_config) + diagnostic_sensors = sensor.sub_sensors(diagnostics_config) + for key in DIAGNOSTIC_SENSORS: + await diagnostic_sensors(key, getattr(updater, f"set_{key}_sensor")) diff --git a/esphome/components/espectre/sensor/espectre_diagnostics.cpp b/esphome/components/espectre/sensor/espectre_diagnostics.cpp new file mode 100644 index 00000000000..bb6024a2332 --- /dev/null +++ b/esphome/components/espectre/sensor/espectre_diagnostics.cpp @@ -0,0 +1,40 @@ +#include "espectre_diagnostics.h" + +#ifdef USE_ESPECTRE + +#include +#include "esphome/core/log.h" + +namespace esphome::espectre { + +ESPHOME_LOG_TAG(TAG, "espectre.sensor"); + +static void publish_diagnostic(sensor::Sensor *sensor, const ::espectre::RuntimeDiagnosticsSample *sample, + float ::espectre::RuntimeDiagnosticsSample::*field, float scale = 1.0f) { + if (sensor != nullptr) + sensor->publish_state(sample != nullptr ? sample->*field * scale : NAN); +} + +void DiagnosticsUpdater::update() { + using Sample = ::espectre::RuntimeDiagnosticsSample; + const auto *sample = this->parent_->diagnostics_sample(); + publish_diagnostic(this->generator_rate_sensor_, sample, &Sample::generator_pps); + publish_diagnostic(this->traffic_tx_rate_sensor_, sample, &Sample::traffic_tx_pps); + publish_diagnostic(this->traffic_rx_rate_sensor_, sample, &Sample::traffic_rx_pps); + publish_diagnostic(this->csi_accepted_rate_sensor_, sample, &Sample::csi_accepted_pps); + publish_diagnostic(this->csi_occupancy_sensor_, sample, &Sample::csi_occupancy_ratio, 100.0f); +} + +void DiagnosticsUpdater::dump_config() { + ESP_LOGCONFIG(TAG, "ESPectre diagnostics:"); + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Generator rate", this->generator_rate_sensor_); + LOG_SENSOR(" ", "Traffic TX rate", this->traffic_tx_rate_sensor_); + LOG_SENSOR(" ", "Traffic RX rate", this->traffic_rx_rate_sensor_); + LOG_SENSOR(" ", "CSI accepted rate", this->csi_accepted_rate_sensor_); + LOG_SENSOR(" ", "CSI occupancy", this->csi_occupancy_sensor_); +} + +} // namespace esphome::espectre + +#endif // USE_ESPECTRE diff --git a/esphome/components/espectre/sensor/espectre_diagnostics.h b/esphome/components/espectre/sensor/espectre_diagnostics.h new file mode 100644 index 00000000000..fde1c461137 --- /dev/null +++ b/esphome/components/espectre/sensor/espectre_diagnostics.h @@ -0,0 +1,32 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_ESPECTRE + +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" +#include "../espectre.h" + +namespace esphome::espectre { + +/// Publishes all diagnostic sensors together from the same runtime sample. +class DiagnosticsUpdater final : public PollingComponent { + public: + explicit DiagnosticsUpdater(ESPectreComponent *parent) : parent_(parent) {} + void update() override; + void dump_config() override; + + SUB_SENSOR(generator_rate) + SUB_SENSOR(traffic_tx_rate) + SUB_SENSOR(traffic_rx_rate) + SUB_SENSOR(csi_accepted_rate) + SUB_SENSOR(csi_occupancy) + + protected: + ESPectreComponent *parent_; +}; + +} // namespace esphome::espectre + +#endif // USE_ESPECTRE diff --git a/esphome/components/esphome/__init__.py b/esphome/components/esphome/__init__.py new file mode 100644 index 00000000000..8bcc59dd2d1 --- /dev/null +++ b/esphome/components/esphome/__init__.py @@ -0,0 +1 @@ +DOMAIN = "esphome" diff --git a/esphome/components/esphome/ota/__init__.py b/esphome/components/esphome/ota/__init__.py index ffd7c509497..dc4fc5b47ed 100644 --- a/esphome/components/esphome/ota/__init__.py +++ b/esphome/components/esphome/ota/__init__.py @@ -6,7 +6,12 @@ from esphome.components.noise import ( new_psk_progmem, static_encryption_key, ) -from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code +from esphome.components.ota import ( + BASE_OTA_SCHEMA, + DOMAIN as OTA_DOMAIN, + OTAComponent, + ota_to_code, +) from esphome.config_helpers import filter_source_files_from_defines, merge_config import esphome.config_validation as cv from esphome.const import ( @@ -31,6 +36,8 @@ from esphome.espota2 import CONF_ALLOW_PLAINTEXT_UPLOAD import esphome.final_validate as fv from esphome.types import ConfigType +from .. import DOMAIN + CONF_ALLOW_PARTITION_ACCESS = "allow_partition_access" _LOGGER = logging.getLogger(__name__) @@ -56,12 +63,12 @@ ESPHomeOTAComponent = esphome.class_("ESPHomeOTAComponent", OTAComponent) def ota_esphome_final_validate(config: ConfigType) -> None: full_conf = fv.full_config.get() - full_ota_conf = full_conf[CONF_OTA] + full_ota_conf = full_conf[OTA_DOMAIN] new_ota_conf = [] merged_ota_esphome_configs_by_port = {} ports_with_merged_configs = [] for ota_conf in full_ota_conf: - if ota_conf.get(CONF_PLATFORM) == CONF_ESPHOME: + if ota_conf.get(CONF_PLATFORM) == DOMAIN: if ( conf_port := ota_conf.get(CONF_PORT) ) not in merged_ota_esphome_configs_by_port: @@ -133,20 +140,12 @@ def ota_esphome_final_validate(config: ConfigType) -> None: _resolve_encryption_key(encryption_conf, api_conf) elif CONF_PASSWORD in ota_conf and static_encryption_key(api_conf) is not None: _LOGGER.warning( - "'%s' %s wastes significant flash and RAM (about 3.5 KB and 60 " - "bytes plus the password on the heap): the device already offers " - "encryption with the '%s' %s %s, which authenticates any uploader " - "that takes it, and a password only matters for uploaders without " - "encryption support; remove '%s' and add '%s' under '%s' so " - "uploads use the key and encryption is required", + "'%s' %s wastes significant flash and RAM; " + "using '%s' instead is recommended - " + "see https://esphome.io/components/ota/esphome/#configuration-variables", CONF_OTA, CONF_PASSWORD, - CONF_API, CONF_ENCRYPTION, - CONF_KEY, - CONF_PASSWORD, - CONF_ENCRYPTION, - CONF_OTA, ) elif ( CONF_PASSWORD in ota_conf @@ -190,7 +189,7 @@ def ota_esphome_final_validate(config: ConfigType) -> None: CONF_WEB_SERVER, ) - full_conf[CONF_OTA] = new_ota_conf + full_conf[OTA_DOMAIN] = new_ota_conf fv.full_config.set(full_conf) if len(ports_with_merged_configs) > 0: diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index c399c0901c6..e6b19c42f0c 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -30,7 +30,7 @@ namespace esphome { -static const char *const TAG = "esphome.ota"; +ESPHOME_LOG_TAG(TAG, "esphome.ota"); #ifdef USE_OTA_ENCRYPTION const noise::NoiseContext &ESPHomeOTAComponent::noise_context_() const { @@ -635,7 +635,7 @@ error: this->cleanup_connection_(); - this->status_momentary_error("err", 5000); + this->status_momentary_error(5000); #ifdef USE_OTA_STATE_LISTENER this->notify_state_(ota::OTA_ERROR, 0.0f, static_cast(error_code)); #endif diff --git a/esphome/components/esphome/ota/ota_esphome_inflate_session.cpp b/esphome/components/esphome/ota/ota_esphome_inflate_session.cpp index f3005cac488..38b58b0b03d 100644 --- a/esphome/components/esphome/ota/ota_esphome_inflate_session.cpp +++ b/esphome/components/esphome/ota/ota_esphome_inflate_session.cpp @@ -7,7 +7,7 @@ namespace esphome { -static const char *const TAG = "esphome.ota"; +ESPHOME_LOG_TAG(TAG, "esphome.ota"); // The window doubles as the output buffer; flushed bytes stay as back // reference history for the next windowful. diff --git a/esphome/components/esphome/ota/ota_esphome_noise.cpp b/esphome/components/esphome/ota/ota_esphome_noise.cpp index b3d8501f100..a271ef9d39d 100644 --- a/esphome/components/esphome/ota/ota_esphome_noise.cpp +++ b/esphome/components/esphome/ota/ota_esphome_noise.cpp @@ -14,7 +14,7 @@ namespace esphome { -static const char *const TAG = "esphome.ota"; +ESPHOME_LOG_TAG(TAG, "esphome.ota"); #ifdef USE_ESP8266 static constexpr char OTA_NOISE_PROLOGUE_INIT[] PROGMEM = "NoiseOTAInit"; diff --git a/esphome/components/espnow/__init__.py b/esphome/components/espnow/__init__.py index c04cf1ed59b..aef8fd17b56 100644 --- a/esphome/components/espnow/__init__.py +++ b/esphome/components/espnow/__init__.py @@ -4,7 +4,9 @@ from esphome import automation, core import esphome.codegen as cg from esphome.components import wifi from esphome.components.esp32 import VARIANT_ESP32P4, get_esp32_variant +from esphome.components.esp32_hosted import DOMAIN as ESP32_HOSTED_DOMAIN from esphome.components.udp import CONF_ON_RECEIVE +from esphome.components.wifi import DOMAIN as WIFI_DOMAIN import esphome.config_validation as cv from esphome.const import ( CONF_ADDRESS, @@ -23,8 +25,8 @@ from esphome.types import ConfigType CODEOWNERS = ["@jesserockz"] AUTO_LOAD = ["network"] +DOMAIN = "espnow" -byte_vector = cg.std_vector.template(cg.uint8) peer_address_t = cg.std_ns.class_("array").template(cg.uint8, 6) espnow_ns = cg.esphome_ns.namespace("espnow") @@ -141,7 +143,7 @@ def _validate_variant(config: ConfigType) -> ConfigType: return config if variant != VARIANT_ESP32P4: raise cv.Invalid(f"ESP-NOW is not supported on {variant} (no Wi-Fi radio)") - if "esp32_hosted" not in fv.full_config.get(): + if ESP32_HOSTED_DOMAIN not in fv.full_config.get(): raise cv.Invalid(f"ESP-NOW on {variant} requires the esp32_hosted component") return config @@ -177,7 +179,7 @@ async def to_code(config: ConfigType) -> None: include_builtin_idf_component("esp_wifi") - if CONF_WIFI in CORE.config: + if WIFI_DOMAIN in CORE.config: # Track the Wi-Fi channel via connect events instead of polling every loop wifi.request_wifi_connect_state_listener() if wifi_channel := config.get(CONF_CHANNEL): @@ -304,11 +306,12 @@ async def send_action( await register_peer(var, config, args) - data = config.get(CONF_DATA, []) + data = config[CONF_DATA] if isinstance(data, str): data = list(data.encode()) - templ = await cg.templatable(data, args, byte_vector, byte_vector) - cg.add(var.set_data(templ)) + await automation.templatable_bytes( + data, args, var.set_data_template, var.set_data_static, "espnow_data" + ) cg.add(var.set_wait_for_sent(config[CONF_WAIT_FOR_SENT])) cg.add(var.set_continue_on_error(config[CONF_CONTINUE_ON_ERROR])) diff --git a/esphome/components/espnow/automation.h b/esphome/components/espnow/automation.h index 3c9c6e8be95..851b8345c94 100644 --- a/esphome/components/espnow/automation.h +++ b/esphome/components/espnow/automation.h @@ -11,7 +11,7 @@ namespace esphome::espnow { template class SendAction final : public Action, public Parented { TEMPLATABLE_VALUE(peer_address_t, address); - TEMPLATABLE_VALUE(std::vector, data); + TEMPLATABLE_BYTES(data) public: void add_on_sent(const std::initializer_list *> &actions) { @@ -58,8 +58,10 @@ template class SendAction final : public Action, public P } }; peer_address_t address = this->address_.value(x...); - std::vector data = this->data_.value(x...); - esp_err_t err = this->parent_->send(address.data(), data, send_callback); + esp_err_t err = ESP_OK; + this->data_.visit( + [&](const uint8_t *data, size_t len) { err = this->parent_->send(address.data(), data, len, send_callback); }, + x...); if (err != ESP_OK) { send_callback(err); } else if (!this->flags_.wait_for_sent) { diff --git a/esphome/components/espnow/espnow_component.cpp b/esphome/components/espnow/espnow_component.cpp index ecf0f79e4a7..4d888a9f373 100644 --- a/esphome/components/espnow/espnow_component.cpp +++ b/esphome/components/espnow/espnow_component.cpp @@ -26,7 +26,7 @@ namespace esphome::espnow { -static constexpr const char *TAG = "espnow"; +ESPHOME_LOG_TAG(TAG, "espnow"); ESPNowComponent *global_esp_now = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -406,7 +406,7 @@ esp_err_t ESPNowComponent::send(const uint8_t *peer_address, const uint8_t *payl if (packet == nullptr) { this->send_packet_queue_.increment_dropped_count(); ESP_LOGE(TAG, "Failed to allocate send packet from pool"); - this->status_momentary_warning("send-packet-pool-full"); + this->status_momentary_warning(); return ESP_ERR_ESPNOW_NO_MEM; } // Load the packet data @@ -434,7 +434,7 @@ void ESPNowComponent::send_() { if (packet->callback_ != nullptr) { packet->callback_(err); } - this->status_momentary_warning("send-failed"); + this->status_momentary_warning(); this->send_packet_pool_.release(packet); this->current_send_packet_ = nullptr; // Reset current packet return; @@ -447,7 +447,7 @@ esp_err_t ESPNowComponent::add_peer(const uint8_t *peer) { } if (memcmp(peer, this->own_address_, ESP_NOW_ETH_ALEN) == 0) { - this->status_momentary_warning("peer-add-failed"); + this->status_momentary_warning(); return ESP_ERR_INVALID_MAC; } @@ -462,7 +462,7 @@ esp_err_t ESPNowComponent::add_peer(const uint8_t *peer) { char peer_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; format_mac_addr_upper(peer, peer_buf); ESP_LOGE(TAG, "Failed to add peer %s - %s", peer_buf, LOG_STR_ARG(espnow_error_to_str(err))); - this->status_momentary_warning("peer-add-failed"); + this->status_momentary_warning(); return err; } } @@ -492,7 +492,7 @@ esp_err_t ESPNowComponent::del_peer(const uint8_t *peer) { char peer_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; format_mac_addr_upper(peer, peer_buf); ESP_LOGE(TAG, "Failed to delete peer %s - %s", peer_buf, LOG_STR_ARG(espnow_error_to_str(err))); - this->status_momentary_warning("peer-del-failed"); + this->status_momentary_warning(); return err; } } diff --git a/esphome/components/espnow/packet_transport/espnow_transport.cpp b/esphome/components/espnow/packet_transport/espnow_transport.cpp index b7686f23d6f..7053229db42 100644 --- a/esphome/components/espnow/packet_transport/espnow_transport.cpp +++ b/esphome/components/espnow/packet_transport/espnow_transport.cpp @@ -7,7 +7,7 @@ namespace esphome::espnow { -static const char *const TAG = "espnow.transport"; +ESPHOME_LOG_TAG(TAG, "espnow.transport"); bool ESPNowTransport::should_send() { return this->parent_ != nullptr && !this->parent_->is_failed(); } diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index ce7206bf57a..58915ff96db 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -import logging from esphome import automation, pins from esphome.automation import Condition @@ -61,13 +60,12 @@ import esphome.final_validate as fv from esphome.types import ConfigType AUTO_LOAD = ["network"] -LOGGER = logging.getLogger(__name__) +DOMAIN = "ethernet" # Key for tracking IP state listener count in CORE.data ETHERNET_IP_STATE_LISTENERS_KEY = "ethernet_ip_state_listeners" # Key for tracking configured ethernet type ETHERNET_TYPE_KEY = "ethernet_type" -KEY_ETHERNET = "ethernet" def request_ethernet_ip_state_listener() -> None: @@ -214,13 +212,6 @@ CLK_MODES = { "CLK_OUT": emac_rmii_clock_mode_t.EMAC_CLK_OUT, } -CLK_MODES_DEPRECATED = { - "GPIO0_IN": ("CLK_EXT_IN", 0), - "GPIO0_OUT": ("CLK_OUT", 0), - "GPIO16_OUT": ("CLK_OUT", 16), - "GPIO17_OUT": ("CLK_OUT", 17), -} - spi_host_device_t = cg.global_ns.enum("spi_host_device_t") SPI_INTERFACE_MAP = { @@ -387,23 +378,6 @@ def _validate(config: ConfigType) -> ConfigType: get_esp32_variant, ) - if CONF_CLK_MODE in config: - mode, pin = CLK_MODES_DEPRECATED[config[CONF_CLK_MODE]] - LOGGER.warning( - "[ethernet] The 'clk_mode' option is deprecated. " - "Please replace 'clk_mode: %s' with:\n" - " clk:\n" - " mode: %s\n" - " pin: %s\n" - "Removal scheduled for 2026.11.0.", - config[CONF_CLK_MODE], - mode, - pin, - ) - config[CONF_CLK] = CLK_SCHEMA({CONF_MODE: mode, CONF_PIN: pin}) - del config[CONF_CLK_MODE] - elif CONF_CLK not in config: - raise cv.Invalid("'clk' is a required option for [ethernet].") variant = get_esp32_variant() if variant not in (VARIANT_ESP32, VARIANT_ESP32P4): raise cv.Invalid( @@ -452,10 +426,17 @@ RMII_SCHEMA = cv.All( { cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number, cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_CLK_MODE): cv.enum( - CLK_MODES_DEPRECATED, upper=True, space="_" + # Removed in 2026.11.0 - kept to provide helpful error message + # Remove before 2027.5.0 + cv.Optional(CONF_CLK_MODE): cv.invalid( + "The 'clk_mode' option has been removed in ESPHome 2026.11.0.\n" + "Replace it with a 'clk:' block containing 'mode:' and 'pin:':\n" + " GPIO0_IN -> mode: CLK_EXT_IN, pin: GPIO0\n" + " GPIO0_OUT -> mode: CLK_OUT, pin: GPIO0\n" + " GPIO16_OUT -> mode: CLK_OUT, pin: GPIO16\n" + " GPIO17_OUT -> mode: CLK_OUT, pin: GPIO17" ), - cv.Optional(CONF_CLK): CLK_SCHEMA, + cv.Required(CONF_CLK): CLK_SCHEMA, cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31), cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number, cv.Optional(CONF_PHY_REGISTERS): cv.ensure_list(PHY_REGISTER_SCHEMA), @@ -636,6 +617,19 @@ def phy_register(address: int, value: int, page: int) -> cg.StructInitializer: ) +def _add_phy_registers(var: cg.MockObj, config: ConfigType) -> None: + if not (registers := config.get(CONF_PHY_REGISTERS)): + return + cg.add_define("ESPHOME_ETHERNET_PHY_REGISTER_COUNT", len(registers)) + for register_value in registers: + reg = phy_register( + register_value.get(CONF_ADDRESS), + register_value.get(CONF_VALUE), + register_value.get(CONF_PAGE_ID), + ) + cg.add(var.add_phy_register(reg)) + + @coroutine_with_priority(CoroPriority.COMMUNICATION) async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) @@ -659,7 +653,7 @@ async def to_code(config: ConfigType) -> None: # enable_on_boot defaults to true in C++ - only set if false if not config[CONF_ENABLE_ON_BOOT]: cg.add(var.set_enable_on_boot(False)) - CORE.data.setdefault(KEY_ETHERNET, {})[ETHERNET_TYPE_KEY] = config[CONF_TYPE] + CORE.data.setdefault(DOMAIN, {})[ETHERNET_TYPE_KEY] = config[CONF_TYPE] if CONF_MANUAL_IP in config: cg.add_define("USE_ETHERNET_MANUAL_IP") @@ -741,13 +735,7 @@ async def _to_code_esp32(var: cg.MockObj, config: ConfigType) -> None: cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN])) if CONF_POWER_PIN in config: cg.add(var.set_power_pin(config[CONF_POWER_PIN])) - for register_value in config.get(CONF_PHY_REGISTERS, []): - reg = phy_register( - register_value.get(CONF_ADDRESS), - register_value.get(CONF_VALUE), - register_value.get(CONF_PAGE_ID), - ) - cg.add(var.add_phy_register(reg)) + _add_phy_registers(var, config) else: cg.add(var.set_phy_addr(config[CONF_PHY_ADDR])) cg.add(var.set_mdc_pin(config[CONF_MDC_PIN])) @@ -756,13 +744,7 @@ async def _to_code_esp32(var: cg.MockObj, config: ConfigType) -> None: cg.add(var.set_clk_pin(config[CONF_CLK][CONF_PIN])) if CONF_POWER_PIN in config: cg.add(var.set_power_pin(config[CONF_POWER_PIN])) - for register_value in config.get(CONF_PHY_REGISTERS, []): - reg = phy_register( - register_value.get(CONF_ADDRESS), - register_value.get(CONF_VALUE), - register_value.get(CONF_PAGE_ID), - ) - cg.add(var.add_phy_register(reg)) + _add_phy_registers(var, config) # Register Ethernet with the esp32 sdkconfig reconciler. It disables the # WiFi stack and WiFi/BT coexistence only when Ethernet runs without WiFi, @@ -912,7 +894,7 @@ _define_filter = filter_source_files_from_defines( def _filter_source_files() -> list[str]: excluded = _platform_filter() + _define_filter() - eth_data = CORE.data.get(KEY_ETHERNET, {}) + eth_data = CORE.data.get(DOMAIN, {}) eth_type = eth_data.get(ETHERNET_TYPE_KEY) # Only compile the custom JL1101 driver when JL1101 is configured # and pioarduino doesn't have it builtin (IDF 5.4.2 to 5.x) diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 71fa9d40b67..44c4d6733d3 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -193,7 +193,9 @@ class EthernetComponent final : public Component { void set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; } void set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; } void set_clk_mode(emac_rmii_clock_mode_t clk_mode) { this->clk_mode_ = clk_mode; } - void add_phy_register(PHYRegister register_value); +#ifdef ESPHOME_ETHERNET_PHY_REGISTER_COUNT + void add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); } +#endif #endif // USE_ETHERNET_SPI #endif // USE_ESP32 @@ -255,8 +257,10 @@ class EthernetComponent final : public Component { /// reset) and set the RGMII Tx/Rx clock delays needed for reliable data sampling. void yt8531_phy_init_(); #endif +#ifdef ESPHOME_ETHERNET_PHY_REGISTER_COUNT /// @brief Set arbitratry PHY registers from config. void write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data); +#endif #ifdef USE_ETHERNET_SPI uint8_t clk_pin_; @@ -280,7 +284,9 @@ class EthernetComponent final : public Component { // Group all 32-bit members first int power_pin_{-1}; emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; - std::vector phy_registers_{}; +#ifdef ESPHOME_ETHERNET_PHY_REGISTER_COUNT + StaticVector phy_registers_{}; +#endif // Group all 8-bit members together uint8_t clk_pin_{0}; diff --git a/esphome/components/ethernet/ethernet_component_esp32.cpp b/esphome/components/ethernet/ethernet_component_esp32.cpp index 2c3c5f1ef98..1568d37b114 100644 --- a/esphome/components/ethernet/ethernet_component_esp32.cpp +++ b/esphome/components/ethernet/ethernet_component_esp32.cpp @@ -77,7 +77,7 @@ namespace esphome::ethernet { -static const char *const TAG = "ethernet"; +ESPHOME_LOG_TAG(TAG, "ethernet"); // PHY register size for hex logging static constexpr size_t PHY_REG_SIZE = 2; @@ -462,9 +462,11 @@ void EthernetComponent::ethernet_lazy_init_() { } #endif // USE_ETHERNET_KSZ8081 +#ifdef ESPHOME_ETHERNET_PHY_REGISTER_COUNT for (const auto &phy_register : this->phy_registers_) { this->write_phy_register_(mac, phy_register); } +#endif #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) #ifdef USE_ETHERNET_GENERIC @@ -1043,10 +1045,6 @@ void EthernetComponent::dump_connect_params_() { #endif /* USE_NETWORK_IPV6 */ } -#ifndef USE_ETHERNET_SPI -void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); } -#endif - void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) { if (!this->ethernet_initialized_) { // External callers (mdns, ethernet_info, etc.) may ask for the MAC before/regardless @@ -1144,6 +1142,7 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { } #endif // USE_ETHERNET_KSZ8081 +#ifdef ESPHOME_ETHERNET_PHY_REGISTER_COUNT void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data) { esp_err_t err; @@ -1168,6 +1167,7 @@ void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister regi } #endif } +#endif // ESPHOME_ETHERNET_PHY_REGISTER_COUNT #ifdef USE_ETHERNET_YT8531 void EthernetComponent::yt8531_phy_init_() { diff --git a/esphome/components/ethernet/ethernet_component_rp2.cpp b/esphome/components/ethernet/ethernet_component_rp2.cpp index 94d84cc8914..ecb238419ee 100644 --- a/esphome/components/ethernet/ethernet_component_rp2.cpp +++ b/esphome/components/ethernet/ethernet_component_rp2.cpp @@ -14,7 +14,7 @@ namespace esphome::ethernet { -static const char *const TAG = "ethernet"; +ESPHOME_LOG_TAG(TAG, "ethernet"); void EthernetComponent::setup() { // Configure SPI pins diff --git a/esphome/components/ethernet_info/__init__.py b/esphome/components/ethernet_info/__init__.py index 312688d3fde..3f72edbc7d1 100644 --- a/esphome/components/ethernet_info/__init__.py +++ b/esphome/components/ethernet_info/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@gtjadsonsantos"] +DOMAIN = "ethernet_info" diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp b/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp index 15ef6a1f205..e75d3eb9ae6 100644 --- a/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp @@ -5,7 +5,7 @@ namespace esphome::ethernet_info { -static const char *const TAG = "ethernet_info"; +ESPHOME_LOG_TAG(TAG, "ethernet_info"); #ifdef USE_ETHERNET_IP_STATE_LISTENERS void IPAddressEthernetInfo::setup() { ethernet::global_eth_component->add_ip_state_listener(this); } diff --git a/esphome/components/event/__init__.py b/esphome/components/event/__init__.py index d02863b9ee2..c40130634f9 100644 --- a/esphome/components/event/__init__.py +++ b/esphome/components/event/__init__.py @@ -27,6 +27,7 @@ from esphome.cpp_generator import MockObj, MockObjClass from esphome.types import ConfigType CODEOWNERS = ["@nohat"] +DOMAIN = "event" IS_PLATFORM_COMPONENT = True DEVICE_CLASSES = [ diff --git a/esphome/components/event/event.cpp b/esphome/components/event/event.cpp index 00bb9497f96..3170c7a8dd0 100644 --- a/esphome/components/event/event.cpp +++ b/esphome/components/event/event.cpp @@ -5,7 +5,7 @@ namespace esphome::event { -static const char *const TAG = "event"; +ESPHOME_LOG_TAG(TAG, "event"); void Event::trigger(const char *event_type) { // Linear search with strcmp - faster than std::set for small datasets (1-5 items typical) diff --git a/esphome/components/exponential_moving_average/__init__.py b/esphome/components/exponential_moving_average/__init__.py new file mode 100644 index 00000000000..7a389785e70 --- /dev/null +++ b/esphome/components/exponential_moving_average/__init__.py @@ -0,0 +1,2 @@ +CODEOWNERS = ["@clydebarrow"] +DOMAIN = "exponential_moving_average" diff --git a/esphome/components/exponential_moving_average/exponential_moving_average_sensor.cpp b/esphome/components/exponential_moving_average/exponential_moving_average_sensor.cpp new file mode 100644 index 00000000000..ff4eded9cf3 --- /dev/null +++ b/esphome/components/exponential_moving_average/exponential_moving_average_sensor.cpp @@ -0,0 +1,116 @@ +#include "exponential_moving_average_sensor.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" + +#include + +namespace esphome::exponential_moving_average { + +ESPHOME_LOG_TAG(TAG, "exponential_moving_average"); + +const LogString *time_weighting_to_string(TimeWeighting weighting) { + switch (weighting) { + case TIME_WEIGHTING_PREVIOUS: + return LOG_STR("previous"); + case TIME_WEIGHTING_LINEAR: + return LOG_STR("linear"); + default: + return LOG_STR("new"); + } +} + +ScaledDuration scale_duration(uint32_t ms) { + if (ms < 1000) + return {static_cast(ms), LOG_STR("ms"), 0}; + if (ms < 60 * 1000) + return {ms / 1000.0f, LOG_STR("s"), 1}; + if (ms < 60 * 60 * 1000) + return {ms / (60 * 1000.0f), LOG_STR("min"), 1}; + return {ms / (60 * 60 * 1000.0f), LOG_STR("h"), 1}; +} + +void ExponentialMovingAverageSensor::setup() { + if (this->restore_) { + this->pref_ = this->make_entity_preference(); + float restored; + if (this->pref_.load(&restored) && std::isfinite(restored)) { + this->accumulator_ = restored; + this->publish_state(restored); + } + } + const uint32_t now = App.get_loop_component_start_time(); + this->last_update_ = now; + this->source_->add_on_state_callback( + [this](float value) { this->process_(value, App.get_loop_component_start_time()); }); + // The source may have published during its own setup(), before the callback was added. + if (this->source_->has_state()) + this->process_(this->source_->state, now); +} + +void ExponentialMovingAverageSensor::dump_config() { + LOG_SENSOR("", "Exponential Moving Average Sensor", this); + if (this->time_constant_ms_ != 0) { + const ScaledDuration time_constant = scale_duration(this->time_constant_ms_); + ESP_LOGCONFIG(TAG, + " Time Constant: %.*f %s\n" + " Time Weighting: %s", + time_constant.decimals, time_constant.value, LOG_STR_ARG(time_constant.unit), + LOG_STR_ARG(time_weighting_to_string(this->time_weighting_))); + } else { + ESP_LOGCONFIG(TAG, " Alpha: %.3f", this->alpha_); + } + ESP_LOGCONFIG(TAG, " Restore: %s", YESNO(this->restore_)); +} + +void ExponentialMovingAverageSensor::reset() { this->publish_and_save_(NAN); } + +void ExponentialMovingAverageSensor::process_(float value, uint32_t now) { + if (std::isnan(value)) + return; + // After a reboot the downtime is unknown, so the first interval is measured from setup(). + const uint32_t dt = now - this->last_update_; + this->last_update_ = now; + const float previous = this->previous_value_; + this->previous_value_ = value; + if (std::isnan(this->accumulator_)) { + this->publish_and_save_(value); + return; + } + if (this->time_constant_ms_ == 0) { + this->publish_and_save_(this->alpha_ * value + (1.0f - this->alpha_) * this->accumulator_); + return; + } + // Computed in double with expm1(): when the interval is short compared to the time constant, the weights are + // tiny and float rounding of exp() would swamp them. + const double x = static_cast(dt) / this->time_constant_ms_; + // The share of the old average replaced during this interval. + const double gain = -std::expm1(-x); + const double average = this->accumulator_; + // After a reboot there is no previous reading, so only the new value can be used. + const TimeWeighting weighting = std::isnan(previous) ? TIME_WEIGHTING_NEW : this->time_weighting_; + double result; + switch (weighting) { + case TIME_WEIGHTING_PREVIOUS: + result = average + gain * (previous - average); + break; + case TIME_WEIGHTING_LINEAR: { + // Exact result for a value moving in a straight line from the previous reading to the new one. + const double weight_new = x > 0.0 ? (x + std::expm1(-x)) / x : 0.0; + result = average + (gain - weight_new) * (previous - average) + weight_new * (value - average); + break; + } + default: + result = average + gain * (value - average); + break; + } + this->publish_and_save_(static_cast(result)); +} + +void ExponentialMovingAverageSensor::publish_and_save_(float value) { + this->accumulator_ = value; + this->publish_state(value); + if (this->restore_) + this->pref_.save(&value); +} + +} // namespace esphome::exponential_moving_average diff --git a/esphome/components/exponential_moving_average/exponential_moving_average_sensor.h b/esphome/components/exponential_moving_average/exponential_moving_average_sensor.h new file mode 100644 index 00000000000..98bd88553bb --- /dev/null +++ b/esphome/components/exponential_moving_average/exponential_moving_average_sensor.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + +#include "esphome/core/component.h" +#include "esphome/core/log.h" +#include "esphome/core/preferences.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome::exponential_moving_average { + +/// Which value is assumed to apply during the time between two readings, when a time constant is used. +enum TimeWeighting : uint8_t { + TIME_WEIGHTING_NEW = 0, + TIME_WEIGHTING_PREVIOUS, + TIME_WEIGHTING_LINEAR, +}; + +const LogString *time_weighting_to_string(TimeWeighting weighting); + +/// A duration in the largest of ms, s, min or h that keeps the value at 1 or more. +struct ScaledDuration { + float value; + const LogString *unit; + uint8_t decimals; +}; + +ScaledDuration scale_duration(uint32_t ms); + +class ExponentialMovingAverageSensor : public sensor::Sensor, public Component { + public: + explicit ExponentialMovingAverageSensor(sensor::Sensor *source) : source_(source) {} + + void setup() override; + void dump_config() override; + + void set_alpha(float alpha) { this->alpha_ = alpha; } + /// When non-zero, each sample is weighted by the time since the previous one instead of by a fixed alpha. + void set_time_constant(uint32_t time_constant_ms) { this->time_constant_ms_ = time_constant_ms; } + void set_time_weighting(TimeWeighting weighting) { this->time_weighting_ = weighting; } + void set_restore(bool restore) { this->restore_ = restore; } + /// Clear the average; the next sample starts it again. + void reset(); + + protected: + void process_(float value, uint32_t now); + void publish_and_save_(float value); + + sensor::Sensor *source_; + ESPPreferenceObject pref_; + float alpha_{0.1f}; + float accumulator_{NAN}; + float previous_value_{NAN}; + uint32_t time_constant_ms_{0}; + uint32_t last_update_{0}; + TimeWeighting time_weighting_{TIME_WEIGHTING_NEW}; + bool restore_{true}; +}; + +} // namespace esphome::exponential_moving_average diff --git a/esphome/components/exponential_moving_average/sensor.py b/esphome/components/exponential_moving_average/sensor.py new file mode 100644 index 00000000000..20c8135636d --- /dev/null +++ b/esphome/components/exponential_moving_average/sensor.py @@ -0,0 +1,103 @@ +from esphome import automation +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_ACCURACY_DECIMALS, + CONF_ALPHA, + CONF_DEVICE_CLASS, + CONF_ICON, + CONF_ID, + CONF_RESTORE, + CONF_SENSOR, + CONF_STATE_CLASS, + CONF_TIME_CONSTANT, + CONF_UNIT_OF_MEASUREMENT, +) +from esphome.core.entity_helpers import inherit_property_from +from esphome.types import ConfigType + +exponential_moving_average_ns = cg.esphome_ns.namespace("exponential_moving_average") +ExponentialMovingAverageSensor = exponential_moving_average_ns.class_( + "ExponentialMovingAverageSensor", sensor.Sensor, cg.Component +) + +TimeWeighting = exponential_moving_average_ns.enum("TimeWeighting") +TIME_WEIGHTINGS: dict[str, cg.MockObj] = { + "new": TimeWeighting.TIME_WEIGHTING_NEW, + "previous": TimeWeighting.TIME_WEIGHTING_PREVIOUS, + "linear": TimeWeighting.TIME_WEIGHTING_LINEAR, +} + +CONF_TIME_WEIGHTING: str = "time_weighting" + +DEFAULT_ALPHA: float = 0.1 + + +def inherit_accuracy_decimals(decimals: int, config: ConfigType) -> int: + # An average carries more precision than the individual readings. + return decimals + 1 + + +def validate_time_weighting(config: ConfigType) -> ConfigType: + if CONF_TIME_WEIGHTING in config and CONF_TIME_CONSTANT not in config: + raise cv.Invalid( + f"'{CONF_TIME_WEIGHTING}' can only be used with '{CONF_TIME_CONSTANT}'", + path=[CONF_TIME_WEIGHTING], + ) + return config + + +CONFIG_SCHEMA = cv.All( + sensor.sensor_schema(ExponentialMovingAverageSensor) + .extend( + { + cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Optional(CONF_ALPHA): cv.All( + cv.float_, cv.Range(min=0, min_included=False, max=1) + ), + cv.Optional(CONF_TIME_CONSTANT): cv.positive_time_period_milliseconds, + cv.Optional(CONF_TIME_WEIGHTING): cv.enum(TIME_WEIGHTINGS, lower=True), + cv.Optional(CONF_RESTORE, default=True): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA), + cv.has_at_most_one_key(CONF_ALPHA, CONF_TIME_CONSTANT), + validate_time_weighting, +) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SENSOR), + inherit_property_from(CONF_UNIT_OF_MEASUREMENT, CONF_SENSOR), + inherit_property_from( + CONF_ACCURACY_DECIMALS, CONF_SENSOR, transform=inherit_accuracy_decimals + ), + inherit_property_from(CONF_DEVICE_CLASS, CONF_SENSOR), + inherit_property_from(CONF_STATE_CLASS, CONF_SENSOR), +) + + +async def to_code(config: ConfigType) -> None: + source = await cg.get_variable(config[CONF_SENSOR]) + var = cg.new_Pvariable(config[CONF_ID], source) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + + if (time_constant := config.get(CONF_TIME_CONSTANT)) is not None: + cg.add(var.set_time_constant(time_constant)) + if (weighting := config.get(CONF_TIME_WEIGHTING)) is not None: + cg.add(var.set_time_weighting(weighting)) + else: + cg.add(var.set_alpha(config.get(CONF_ALPHA, DEFAULT_ALPHA))) + cg.add(var.set_restore(config[CONF_RESTORE])) + + +automation.register_apply_action( + "sensor.exponential_moving_average.reset", + automation.maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(ExponentialMovingAverageSensor), + } + ), + automation.ApplyCall("reset()"), +) diff --git a/esphome/components/exposure_notifications/__init__.py b/esphome/components/exposure_notifications/__init__.py index 4f7e698e23b..adcfa83717b 100644 --- a/esphome/components/exposure_notifications/__init__.py +++ b/esphome/components/exposure_notifications/__init__.py @@ -10,6 +10,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@OttoWinter"] AUTO_LOAD = ["ble_device_base"] +DOMAIN = "exposure_notifications" exposure_notifications_ns = cg.esphome_ns.namespace("exposure_notifications") ExposureNotification = exposure_notifications_ns.struct("ExposureNotification") diff --git a/esphome/components/exposure_notifications/exposure_notifications.cpp b/esphome/components/exposure_notifications/exposure_notifications.cpp index 4f4b93b59cc..063d7ba6789 100644 --- a/esphome/components/exposure_notifications/exposure_notifications.cpp +++ b/esphome/components/exposure_notifications/exposure_notifications.cpp @@ -6,7 +6,7 @@ namespace esphome::exposure_notifications { using namespace ble_device_base; -static const char *const TAG = "exposure_notifications"; +ESPHOME_LOG_TAG(TAG, "exposure_notifications"); bool ExposureNotificationTrigger::parse_device(const ESPBTDevice &device) { // See also https://blog.google/documents/70/Exposure_Notification_-_Bluetooth_Specification_v1.2.2.pdf diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index 504b1ae6794..8994805432c 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -6,7 +6,6 @@ from esphome import git, loader import esphome.config_validation as cv from esphome.const import ( CONF_COMPONENTS, - CONF_EXTERNAL_COMPONENTS, CONF_PASSWORD, CONF_PATH, CONF_REF, @@ -22,7 +21,7 @@ from esphome.core import CORE, TimePeriodSeconds _LOGGER = logging.getLogger(__name__) -DOMAIN = CONF_EXTERNAL_COMPONENTS +DOMAIN = "external_components" CONFIG_SCHEMA = cv.ensure_list( diff --git a/esphome/components/ezo/__init__.py b/esphome/components/ezo/__init__.py index e69de29bb2d..60408a40eee 100644 --- a/esphome/components/ezo/__init__.py +++ b/esphome/components/ezo/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ezo" diff --git a/esphome/components/ezo_pmp/__init__.py b/esphome/components/ezo_pmp/__init__.py index 1157ef1fb01..aed071e8cf5 100644 --- a/esphome/components/ezo_pmp/__init__.py +++ b/esphome/components/ezo_pmp/__init__.py @@ -13,6 +13,7 @@ from esphome.const import ( CODEOWNERS = ["@carlos-sarmiento"] DEPENDENCIES = ["i2c"] +DOMAIN = "ezo_pmp" MULTI_CONF = True CONF_VOLUME_PER_MINUTE = "volume_per_minute" diff --git a/esphome/components/ezo_pmp/ezo_pmp.cpp b/esphome/components/ezo_pmp/ezo_pmp.cpp index 21307fcd7af..8db57670aea 100644 --- a/esphome/components/ezo_pmp/ezo_pmp.cpp +++ b/esphome/components/ezo_pmp/ezo_pmp.cpp @@ -4,7 +4,7 @@ namespace esphome::ezo_pmp { -static const char *const TAG = "ezo-pmp"; +ESPHOME_LOG_TAG(TAG, "ezo-pmp"); static const uint16_t EZO_PMP_COMMAND_NONE = 0; static const uint16_t EZO_PMP_COMMAND_TYPE_READ = 1; diff --git a/esphome/components/factory_reset/__init__.py b/esphome/components/factory_reset/__init__.py index a9064eb18fd..e2376c217cb 100644 --- a/esphome/components/factory_reset/__init__.py +++ b/esphome/components/factory_reset/__init__.py @@ -15,6 +15,7 @@ from esphome.final_validate import full_config from esphome.types import ConfigType CODEOWNERS = ["@anatoly-savchenkov"] +DOMAIN = "factory_reset" factory_reset_ns = cg.esphome_ns.namespace("factory_reset") FactoryResetComponent = factory_reset_ns.class_("FactoryResetComponent", cg.Component) diff --git a/esphome/components/factory_reset/button/factory_reset_button.cpp b/esphome/components/factory_reset/button/factory_reset_button.cpp index efce998d9d1..8ef7dc67eb4 100644 --- a/esphome/components/factory_reset/button/factory_reset_button.cpp +++ b/esphome/components/factory_reset/button/factory_reset_button.cpp @@ -11,7 +11,7 @@ namespace esphome::factory_reset { -static const char *const TAG = "factory_reset.button"; +ESPHOME_LOG_TAG(TAG, "factory_reset.button"); void FactoryResetButton::dump_config() { LOG_BUTTON("", "Factory Reset Button", this); } void FactoryResetButton::press_action() { diff --git a/esphome/components/factory_reset/factory_reset.cpp b/esphome/components/factory_reset/factory_reset.cpp index bceaf6e40f5..1de2e3f5320 100644 --- a/esphome/components/factory_reset/factory_reset.cpp +++ b/esphome/components/factory_reset/factory_reset.cpp @@ -11,7 +11,7 @@ namespace esphome::factory_reset { -static const char *const TAG = "factory_reset"; +ESPHOME_LOG_TAG(TAG, "factory_reset"); static const uint32_t POWER_CYCLES_KEY = 0xFA5C0DE; static bool was_power_cycled() { diff --git a/esphome/components/factory_reset/switch/factory_reset_switch.cpp b/esphome/components/factory_reset/switch/factory_reset_switch.cpp index 65404a35d57..5f38a44654c 100644 --- a/esphome/components/factory_reset/switch/factory_reset_switch.cpp +++ b/esphome/components/factory_reset/switch/factory_reset_switch.cpp @@ -11,7 +11,7 @@ namespace esphome::factory_reset { -static const char *const TAG = "factory_reset.switch"; +ESPHOME_LOG_TAG(TAG, "factory_reset.switch"); void FactoryResetSwitch::dump_config() { LOG_SWITCH("", "Factory Reset Switch", this); } void FactoryResetSwitch::write_state(bool state) { diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 526499fe077..ac5469fc42c 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -38,6 +38,7 @@ from esphome.core.entity_helpers import ( setup_entity, ) +DOMAIN = "fan" IS_PLATFORM_COMPONENT = True fan_ns = cg.esphome_ns.namespace("fan") diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index 65521e63d51..866f5eb0cda 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -6,7 +6,7 @@ namespace esphome::fan { -static const char *const TAG = "fan"; +ESPHOME_LOG_TAG(TAG, "fan"); // Compat: shared empty vector for getter when no preset modes are set. // Remove in 2026.11.0 when deprecated FanTraits setters are removed diff --git a/esphome/components/fastled_base/__init__.py b/esphome/components/fastled_base/__init__.py index e2fc8578cde..8936627aaab 100644 --- a/esphome/components/fastled_base/__init__.py +++ b/esphome/components/fastled_base/__init__.py @@ -12,6 +12,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@OttoWinter"] +DOMAIN = "fastled_base" fastled_base_ns = cg.esphome_ns.namespace("fastled_base") FastLEDLightOutput = fastled_base_ns.class_( "FastLEDLightOutput", light.AddressableLight diff --git a/esphome/components/fastled_base/fastled_light.cpp b/esphome/components/fastled_base/fastled_light.cpp index da4dbf2ed77..38c594a7c7b 100644 --- a/esphome/components/fastled_base/fastled_light.cpp +++ b/esphome/components/fastled_base/fastled_light.cpp @@ -5,7 +5,7 @@ namespace esphome::fastled_base { -static const char *const TAG = "fastled"; +ESPHOME_LOG_TAG(TAG, "fastled"); void FastLEDLightOutput::setup() { this->controller_->init(); diff --git a/esphome/components/fastled_clockless/__init__.py b/esphome/components/fastled_clockless/__init__.py index e69de29bb2d..05af618c2ee 100644 --- a/esphome/components/fastled_clockless/__init__.py +++ b/esphome/components/fastled_clockless/__init__.py @@ -0,0 +1 @@ +DOMAIN = "fastled_clockless" diff --git a/esphome/components/fastled_spi/__init__.py b/esphome/components/fastled_spi/__init__.py index e69de29bb2d..26451add651 100644 --- a/esphome/components/fastled_spi/__init__.py +++ b/esphome/components/fastled_spi/__init__.py @@ -0,0 +1 @@ +DOMAIN = "fastled_spi" diff --git a/esphome/components/feedback/__init__.py b/esphome/components/feedback/__init__.py index 9ae2df986db..cfac77e7974 100644 --- a/esphome/components/feedback/__init__.py +++ b/esphome/components/feedback/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@ianchi"] +DOMAIN = "feedback" diff --git a/esphome/components/feedback/feedback_cover.cpp b/esphome/components/feedback/feedback_cover.cpp index 4baffc74f83..fb6c3929b04 100644 --- a/esphome/components/feedback/feedback_cover.cpp +++ b/esphome/components/feedback/feedback_cover.cpp @@ -5,7 +5,7 @@ namespace esphome::feedback { -static const char *const TAG = "feedback.cover"; +ESPHOME_LOG_TAG(TAG, "feedback.cover"); static constexpr uint32_t DIRECTION_CHANGE_TIMEOUT_ID = 1; diff --git a/esphome/components/file/__init__.py b/esphome/components/file/__init__.py index f70ffa95208..e98751dac5b 100644 --- a/esphome/components/file/__init__.py +++ b/esphome/components/file/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@esphome/core"] +DOMAIN = "file" diff --git a/esphome/components/fingerprint_grow/__init__.py b/esphome/components/fingerprint_grow/__init__.py index 0266a9caad5..0e522ced258 100644 --- a/esphome/components/fingerprint_grow/__init__.py +++ b/esphome/components/fingerprint_grow/__init__.py @@ -26,6 +26,7 @@ from esphome.const import ( CODEOWNERS = ["@OnFreund", "@loongyh", "@alexborro"] DEPENDENCIES = ["uart"] AUTO_LOAD = ["binary_sensor", "sensor"] +DOMAIN = "fingerprint_grow" MULTI_CONF = True CONF_FINGERPRINT_GROW_ID = "fingerprint_grow_id" diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.cpp b/esphome/components/fingerprint_grow/fingerprint_grow.cpp index 07630f121ac..99e88af5b74 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.cpp +++ b/esphome/components/fingerprint_grow/fingerprint_grow.cpp @@ -5,7 +5,7 @@ namespace esphome::fingerprint_grow { -static const char *const TAG = "fingerprint_grow"; +ESPHOME_LOG_TAG(TAG, "fingerprint_grow"); // Based on Adafruit's library: https://github.com/adafruit/Adafruit-Fingerprint-Sensor-Library diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index a2fad4bc25f..732933ba480 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -41,6 +41,7 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = "font" MULTI_CONF = True +AUTO_LOAD = ["unicode"] CODEOWNERS = ["@esphome/core", "@clydebarrow"] diff --git a/esphome/components/font/font.cpp b/esphome/components/font/font.cpp index 1ed946dadb1..8f7ad6e71f2 100644 --- a/esphome/components/font/font.cpp +++ b/esphome/components/font/font.cpp @@ -3,9 +3,10 @@ #include "esphome/core/color.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "esphome/components/unicode/unicode.h" namespace esphome::font { -static const char *const TAG = "font"; +ESPHOME_LOG_TAG(TAG, "font"); #ifdef USE_LVGL_FONT static const uint8_t OPA4_TABLE[16] = {0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255}; @@ -121,108 +122,6 @@ const Glyph *Font::get_glyph_data_(uint32_t unicode_letter) { } #endif -/** - * Attempt to extract a 32 bit Unicode codepoint from a UTF-8 string. - * If successful, return the codepoint and set the length to the number of bytes read. - * If the end of the string has been reached and a valid codepoint has not been found, return 0 and set the length to - * 0. - * - * @param utf8_str The input string - * @param length Pointer to length storage - * @return The extracted code point - */ -static uint32_t extract_unicode_codepoint(const char *utf8_str, size_t *length) { - // Safely cast to uint8_t* for correct bitwise operations on bytes - const uint8_t *current = reinterpret_cast(utf8_str); - uint32_t code_point = 0; - uint8_t c1 = *current++; - - // check for end of string - if (c1 == 0) { - *length = 0; - return 0; - } - - // --- 1-Byte Sequence: 0xxxxxxx (ASCII) --- - if (c1 < 0x80) { - // Valid ASCII byte. - code_point = c1; - // Optimization: No need to check for continuation bytes. - } - // --- 2-Byte Sequence: 110xxxxx 10xxxxxx --- - else if ((c1 & 0xE0) == 0xC0) { - uint8_t c2 = *current++; - - // Error Check 1: Check if c2 is a valid continuation byte (10xxxxxx) - if ((c2 & 0xC0) != 0x80) { - *length = 0; - return 0; - } - - code_point = (c1 & 0x1F) << 6; - code_point |= (c2 & 0x3F); - - // Error Check 2: Overlong check (2-byte must be > 0x7F) - if (code_point <= 0x7F) { - *length = 0; - return 0; - } - } - // --- 3-Byte Sequence: 1110xxxx 10xxxxxx 10xxxxxx --- - else if ((c1 & 0xF0) == 0xE0) { - uint8_t c2 = *current++; - uint8_t c3 = *current++; - - // Error Check 1: Check continuation bytes - if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) { - *length = 0; - return 0; - } - - code_point = (c1 & 0x0F) << 12; - code_point |= (c2 & 0x3F) << 6; - code_point |= (c3 & 0x3F); - - // Error Check 2: Overlong check (3-byte must be > 0x7FF) - // Also check for surrogates (0xD800-0xDFFF) - if (code_point <= 0x7FF || (code_point >= 0xD800 && code_point <= 0xDFFF)) { - *length = 0; - return 0; - } - } - // --- 4-Byte Sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx --- - else if ((c1 & 0xF8) == 0xF0) { - uint8_t c2 = *current++; - uint8_t c3 = *current++; - uint8_t c4 = *current++; - - // Error Check 1: Check continuation bytes - if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80) || ((c4 & 0xC0) != 0x80)) { - *length = 0; - return 0; - } - - code_point = (c1 & 0x07) << 18; - code_point |= (c2 & 0x3F) << 12; - code_point |= (c3 & 0x3F) << 6; - code_point |= (c4 & 0x3F); - - // Error Check 2: Overlong check (4-byte must be > 0xFFFF) - // Also check for valid Unicode range (must be <= 0x10FFFF) - if (code_point <= 0xFFFF || code_point > 0x10FFFF) { - *length = 0; - return 0; - } - } - // --- Invalid leading byte (e.g., 10xxxxxx or 11111xxx) --- - else { - *length = 0; - return 0; - } - *length = current - reinterpret_cast(utf8_str); - return code_point; -} - Font::Font(const Glyph *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight, uint8_t bpp) : glyphs_(ConstVector(data, data_nr)), @@ -271,7 +170,7 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in int x = 0; for (;;) { size_t length; - auto code_point = extract_unicode_codepoint(str, &length); + auto code_point = unicode::extract_unicode_codepoint(str, &length); if (length == 0) break; str += length; @@ -300,7 +199,7 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo int x_at = x_start; for (;;) { size_t length; - auto code_point = extract_unicode_codepoint(text, &length); + auto code_point = unicode::extract_unicode_codepoint(text, &length); if (length == 0) break; text += length; diff --git a/esphome/components/fs3000/__init__.py b/esphome/components/fs3000/__init__.py index e69de29bb2d..c1a6b39d9bc 100644 --- a/esphome/components/fs3000/__init__.py +++ b/esphome/components/fs3000/__init__.py @@ -0,0 +1 @@ +DOMAIN = "fs3000" diff --git a/esphome/components/fs3000/fs3000.cpp b/esphome/components/fs3000/fs3000.cpp index a89b1661828..81d6647642e 100644 --- a/esphome/components/fs3000/fs3000.cpp +++ b/esphome/components/fs3000/fs3000.cpp @@ -3,7 +3,7 @@ namespace esphome::fs3000 { -static const char *const TAG = "fs3000"; +ESPHOME_LOG_TAG(TAG, "fs3000"); void FS3000Component::setup() { if (model_ == FIVE) { diff --git a/esphome/components/ft5x06/__init__.py b/esphome/components/ft5x06/__init__.py index dceea71dd07..9770f30e435 100644 --- a/esphome/components/ft5x06/__init__.py +++ b/esphome/components/ft5x06/__init__.py @@ -2,5 +2,6 @@ import esphome.codegen as cg CODEOWNERS = ["@clydebarrow"] DEPENDENCIES = ["i2c"] +DOMAIN = "ft5x06" ft5x06_ns = cg.esphome_ns.namespace("ft5x06") diff --git a/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.cpp b/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.cpp index 24d3529fb4c..ac5f4d89338 100644 --- a/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.cpp +++ b/esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.cpp @@ -5,7 +5,7 @@ namespace esphome::ft5x06 { -static const char *const TAG = "ft5x06.touchscreen"; +ESPHOME_LOG_TAG(TAG, "ft5x06.touchscreen"); void FT5x06Touchscreen::setup() { if (this->interrupt_pin_ != nullptr) { diff --git a/esphome/components/ft63x6/__init__.py b/esphome/components/ft63x6/__init__.py index b6d7d3580ed..565da54fe16 100644 --- a/esphome/components/ft63x6/__init__.py +++ b/esphome/components/ft63x6/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@gpambrozio"] +DOMAIN = "ft63x6" diff --git a/esphome/components/ft63x6/ft63x6.cpp b/esphome/components/ft63x6/ft63x6.cpp index dd8f5ba629a..85729c17f68 100644 --- a/esphome/components/ft63x6/ft63x6.cpp +++ b/esphome/components/ft63x6/ft63x6.cpp @@ -25,7 +25,7 @@ static const uint8_t FT6X36_ADDR_THRESHHOLD = 0x80; static const uint8_t FT6X36_ADDR_TOUCHRATE_ACTIVE = 0x88; static const uint8_t FT63X6_ADDR_CHIP_ID = 0xA3; -static const char *const TAG = "FT63X6"; +ESPHOME_LOG_TAG(TAG, "FT63X6"); void FT63X6Touchscreen::setup() { if (this->interrupt_pin_ != nullptr) { diff --git a/esphome/components/fujitsu_general/__init__.py b/esphome/components/fujitsu_general/__init__.py index e69de29bb2d..fdb21576be4 100644 --- a/esphome/components/fujitsu_general/__init__.py +++ b/esphome/components/fujitsu_general/__init__.py @@ -0,0 +1 @@ +DOMAIN = "fujitsu_general" diff --git a/esphome/components/fujitsu_general/fujitsu_general.cpp b/esphome/components/fujitsu_general/fujitsu_general.cpp index 9995a3abbd9..9a928dac01b 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.cpp +++ b/esphome/components/fujitsu_general/fujitsu_general.cpp @@ -2,7 +2,7 @@ namespace esphome::fujitsu_general { -static const char *const TAG = "fujitsu_general.climate"; +ESPHOME_LOG_TAG(TAG, "fujitsu_general.climate"); // Common header constexpr uint8_t FUJITSU_GENERAL_COMMON_LENGTH = 6; diff --git a/esphome/components/gcja5/__init__.py b/esphome/components/gcja5/__init__.py index 122ffaf6edf..9df2024e2a4 100644 --- a/esphome/components/gcja5/__init__.py +++ b/esphome/components/gcja5/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@gcormier"] +DOMAIN = "gcja5" diff --git a/esphome/components/gcja5/gcja5.cpp b/esphome/components/gcja5/gcja5.cpp index e84c51bde8a..fcb9e832ea8 100644 --- a/esphome/components/gcja5/gcja5.cpp +++ b/esphome/components/gcja5/gcja5.cpp @@ -11,7 +11,7 @@ namespace esphome::gcja5 { -static const char *const TAG = "gcja5"; +ESPHOME_LOG_TAG(TAG, "gcja5"); void GCJA5Component::loop() { const uint32_t now = App.get_loop_component_start_time(); diff --git a/esphome/components/gdk101/__init__.py b/esphome/components/gdk101/__init__.py index f98af3f8634..e0ede6136dc 100644 --- a/esphome/components/gdk101/__init__.py +++ b/esphome/components/gdk101/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_ID from esphome.types import ConfigType CODEOWNERS = ["@Szewcson"] +DOMAIN = "gdk101" DEPENDENCIES = ["i2c"] MULTI_CONF = True diff --git a/esphome/components/gdk101/gdk101.cpp b/esphome/components/gdk101/gdk101.cpp index bc0ef70578f..3c7e8d21613 100644 --- a/esphome/components/gdk101/gdk101.cpp +++ b/esphome/components/gdk101/gdk101.cpp @@ -4,7 +4,7 @@ namespace esphome::gdk101 { -static const char *const TAG = "gdk101"; +ESPHOME_LOG_TAG(TAG, "gdk101"); static constexpr uint8_t NUMBER_OF_READ_RETRIES = 5; static constexpr uint8_t NUMBER_OF_RESET_RETRIES = 30; static constexpr uint32_t RESET_INTERVAL_ID = 0; diff --git a/esphome/components/gl_r01_i2c/__init__.py b/esphome/components/gl_r01_i2c/__init__.py index e69de29bb2d..23c5e19b18c 100644 --- a/esphome/components/gl_r01_i2c/__init__.py +++ b/esphome/components/gl_r01_i2c/__init__.py @@ -0,0 +1 @@ +DOMAIN = "gl_r01_i2c" diff --git a/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp b/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp index 75f0c69a7b1..6e2844eb9e8 100644 --- a/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp +++ b/esphome/components/gl_r01_i2c/gl_r01_i2c.cpp @@ -4,7 +4,7 @@ namespace esphome::gl_r01_i2c { -static const char *const TAG = "gl_r01_i2c"; +ESPHOME_LOG_TAG(TAG, "gl_r01_i2c"); // Register definitions from datasheet static const uint8_t REG_VERSION = 0x00; diff --git a/esphome/components/globals/__init__.py b/esphome/components/globals/__init__.py index 8adf4e983ca..a35e2280455 100644 --- a/esphome/components/globals/__init__.py +++ b/esphome/components/globals/__init__.py @@ -12,6 +12,7 @@ from esphome.core import CoroPriority, coroutine_with_priority from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] +DOMAIN = "globals" globals_ns = cg.esphome_ns.namespace("globals") GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component) RestoringGlobalsComponent = globals_ns.class_( diff --git a/esphome/components/gp2y1010au0f/__init__.py b/esphome/components/gp2y1010au0f/__init__.py index e69de29bb2d..36c643103c7 100644 --- a/esphome/components/gp2y1010au0f/__init__.py +++ b/esphome/components/gp2y1010au0f/__init__.py @@ -0,0 +1 @@ +DOMAIN = "gp2y1010au0f" diff --git a/esphome/components/gp2y1010au0f/gp2y1010au0f.cpp b/esphome/components/gp2y1010au0f/gp2y1010au0f.cpp index 0dd4a13a212..ac98a01fd5d 100644 --- a/esphome/components/gp2y1010au0f/gp2y1010au0f.cpp +++ b/esphome/components/gp2y1010au0f/gp2y1010au0f.cpp @@ -6,7 +6,10 @@ namespace esphome::gp2y1010au0f { -static const char *const TAG = "gp2y1010au0f"; +ESPHOME_LOG_TAG(TAG, "gp2y1010au0f"); + +static constexpr uint32_t READ_TIMEOUT_ID = 0; + static const float MIN_VOLTAGE = 0.0f; static const float MAX_VOLTAGE = 4.0f; @@ -22,7 +25,7 @@ void GP2Y1010AU0FSensor::dump_config() { void GP2Y1010AU0FSensor::update() { is_sampling_ = true; - this->set_timeout("read", this->sample_duration_, [this]() { + this->set_timeout(READ_TIMEOUT_ID, this->sample_duration_, [this]() { this->is_sampling_ = false; if (this->num_samples_ == 0) return; diff --git a/esphome/components/gp8403/__init__.py b/esphome/components/gp8403/__init__.py index 17c88b68750..583b1d15689 100644 --- a/esphome/components/gp8403/__init__.py +++ b/esphome/components/gp8403/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@jesserockz", "@sebydocky"] DEPENDENCIES = ["i2c"] +DOMAIN = "gp8403" MULTI_CONF = True gp8403_ns = cg.esphome_ns.namespace("gp8403") diff --git a/esphome/components/gp8403/gp8403.cpp b/esphome/components/gp8403/gp8403.cpp index 7e93bb6295d..b82c32c053e 100644 --- a/esphome/components/gp8403/gp8403.cpp +++ b/esphome/components/gp8403/gp8403.cpp @@ -4,7 +4,7 @@ namespace esphome::gp8403 { -static const char *const TAG = "gp8403"; +ESPHOME_LOG_TAG(TAG, "gp8403"); static const uint8_t RANGE_REGISTER = 0x01; static const uint8_t OUTPUT_REGISTER = 0x02; diff --git a/esphome/components/gp8403/output/gp8403_output.cpp b/esphome/components/gp8403/output/gp8403_output.cpp index 7a22a280ac7..92872f72f21 100644 --- a/esphome/components/gp8403/output/gp8403_output.cpp +++ b/esphome/components/gp8403/output/gp8403_output.cpp @@ -4,7 +4,7 @@ namespace esphome::gp8403 { -static const char *const TAG = "gp8403.output"; +ESPHOME_LOG_TAG(TAG, "gp8403.output"); void GP8403Output::dump_config() { ESP_LOGCONFIG(TAG, diff --git a/esphome/components/gpio/__init__.py b/esphome/components/gpio/__init__.py index 07ebb64cd23..e529b2276d5 100644 --- a/esphome/components/gpio/__init__.py +++ b/esphome/components/gpio/__init__.py @@ -1,4 +1,5 @@ import esphome.codegen as cg CODEOWNERS = ["@esphome/core"] +DOMAIN = "gpio" gpio_ns = cg.esphome_ns.namespace("gpio") diff --git a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp index 9d044dca2d0..87985f189cc 100644 --- a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp +++ b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::gpio { -static const char *const TAG = "gpio.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "gpio.binary_sensor"); #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG #ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT diff --git a/esphome/components/gpio/one_wire/gpio_one_wire.cpp b/esphome/components/gpio/one_wire/gpio_one_wire.cpp index f445efeca37..62b197d13c5 100644 --- a/esphome/components/gpio/one_wire/gpio_one_wire.cpp +++ b/esphome/components/gpio/one_wire/gpio_one_wire.cpp @@ -4,7 +4,7 @@ namespace esphome::gpio { -static const char *const TAG = "gpio.one_wire"; +ESPHOME_LOG_TAG(TAG, "gpio.one_wire"); void GPIOOneWireBus::setup() { this->t_pin_->setup(); diff --git a/esphome/components/gpio/output/__init__.py b/esphome/components/gpio/output/__init__.py index ab242c643fc..645dd61f802 100644 --- a/esphome/components/gpio/output/__init__.py +++ b/esphome/components/gpio/output/__init__.py @@ -1,8 +1,10 @@ from esphome import pins import esphome.codegen as cg from esphome.components import output +from esphome.components.const import CONF_HOLD_STATE import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_PIN +from esphome.const import CONF_ID, CONF_PIN, CONF_POWER_SUPPLY +import esphome.final_validate as fv from esphome.types import ConfigType from .. import gpio_ns @@ -17,6 +19,27 @@ CONFIG_SCHEMA = output.BINARY_OUTPUT_SCHEMA.extend( ).extend(cv.COMPONENT_SCHEMA) +def _require_hold(ps_config: ConfigType) -> ConfigType: + if not ps_config[CONF_PIN].get(CONF_HOLD_STATE): + raise cv.Invalid( + f"The power supply pin must also set {CONF_HOLD_STATE} " + f"when an output it powers sets it", + [CONF_PIN], + ) + return ps_config + + +def _final_validate(config: ConfigType) -> None: + if not config[CONF_PIN].get(CONF_HOLD_STATE): + return + if (ps_id := config.get(CONF_POWER_SUPPLY)) is None: + return + fv.id_declaration_match_schema(_require_hold)(ps_id) + + +FINAL_VALIDATE_SCHEMA = _final_validate + + async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await output.register_output(var, config) diff --git a/esphome/components/gpio/output/gpio_binary_output.cpp b/esphome/components/gpio/output/gpio_binary_output.cpp index 402ad03c49a..b407ea3f4b1 100644 --- a/esphome/components/gpio/output/gpio_binary_output.cpp +++ b/esphome/components/gpio/output/gpio_binary_output.cpp @@ -3,7 +3,7 @@ namespace esphome::gpio { -static const char *const TAG = "gpio.output"; +ESPHOME_LOG_TAG(TAG, "gpio.output"); void GPIOBinaryOutput::dump_config() { ESP_LOGCONFIG(TAG, "Binary Output:"); diff --git a/esphome/components/gpio/output/gpio_binary_output.h b/esphome/components/gpio/output/gpio_binary_output.h index 496afd131b2..06e36474820 100644 --- a/esphome/components/gpio/output/gpio_binary_output.h +++ b/esphome/components/gpio/output/gpio_binary_output.h @@ -11,9 +11,12 @@ class GPIOBinaryOutput final : public output::BinaryOutput, public Component { void set_pin(GPIOPin *pin) { pin_ = pin; } void setup() override { - this->turn_off(); + const bool held = this->pin_->is_held(); + if (!held) + this->turn_off(); this->pin_->setup(); - this->turn_off(); + if (!held) + this->turn_off(); } void dump_config() override; float get_setup_priority() const override { return setup_priority::HARDWARE; } diff --git a/esphome/components/gpio/switch/gpio_switch.cpp b/esphome/components/gpio/switch/gpio_switch.cpp index d231b3d77a5..450a97eb190 100644 --- a/esphome/components/gpio/switch/gpio_switch.cpp +++ b/esphome/components/gpio/switch/gpio_switch.cpp @@ -3,7 +3,7 @@ namespace esphome::gpio { -static const char *const TAG = "switch.gpio"; +ESPHOME_LOG_TAG(TAG, "switch.gpio"); #ifdef USE_GPIO_SWITCH_INTERLOCK static constexpr uint32_t INTERLOCK_TIMEOUT_ID = 0; #endif @@ -12,8 +12,9 @@ float GPIOSwitch::get_setup_priority() const { return setup_priority::HARDWARE; void GPIOSwitch::setup() { bool initial_state = this->get_initial_state_with_restore_mode().value_or(false); - // write state before setup - this->control(initial_state); + // write state before setup unless the pin kept it across the reset + if (!this->pin_->is_held()) + this->control(initial_state); this->pin_->setup(); // write after setup again for other IOs this->control(initial_state); diff --git a/esphome/components/gpio_expander/__init__.py b/esphome/components/gpio_expander/__init__.py index 0c7199b6dfb..cf6be6e8e0e 100644 --- a/esphome/components/gpio_expander/__init__.py +++ b/esphome/components/gpio_expander/__init__.py @@ -3,6 +3,8 @@ import esphome.config_validation as cv from esphome.const import CONF_ALLOW_OTHER_USES, CONF_INTERRUPT_PIN, CONF_INVERTED from esphome.types import ConfigType +DOMAIN = "gpio_expander" + def validate_interrupt_pin(value: ConfigType) -> ConfigType: # The expander components own INT polarity (active-low, hardcoded falling-edge ISR) diff --git a/esphome/components/gps/__init__.py b/esphome/components/gps/__init__.py index 94a36a0afa0..cc77dff496b 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -33,6 +33,7 @@ DEPENDENCIES = ["uart"] AUTO_LOAD = ["sensor"] CODEOWNERS = ["@coogle", "@ximex"] +DOMAIN = "gps" gps_ns = cg.esphome_ns.namespace("gps") GPS = gps_ns.class_("GPS", cg.PollingComponent, uart.UARTDevice) diff --git a/esphome/components/gps/gps.cpp b/esphome/components/gps/gps.cpp index 4b8abd219b0..ec54ac97480 100644 --- a/esphome/components/gps/gps.cpp +++ b/esphome/components/gps/gps.cpp @@ -3,7 +3,7 @@ namespace esphome::gps { -static const char *const TAG = "gps"; +ESPHOME_LOG_TAG(TAG, "gps"); TinyGPSPlus &GPSListener::get_tiny_gps() { return this->parent_->get_tiny_gps(); } diff --git a/esphome/components/gps/time/gps_time.cpp b/esphome/components/gps/time/gps_time.cpp index 3859983ceb7..958d0645d90 100644 --- a/esphome/components/gps/time/gps_time.cpp +++ b/esphome/components/gps/time/gps_time.cpp @@ -3,7 +3,7 @@ namespace esphome::gps { -static const char *const TAG = "gps.time"; +ESPHOME_LOG_TAG(TAG, "gps.time"); void GPSTime::from_tiny_gps_(TinyGPSPlus &tiny_gps) { if (!tiny_gps.time.isValid() || !tiny_gps.date.isValid() || !tiny_gps.time.isUpdated() || diff --git a/esphome/components/graph/__init__.py b/esphome/components/graph/__init__.py index 1b99491f9c5..0aee5973347 100644 --- a/esphome/components/graph/__init__.py +++ b/esphome/components/graph/__init__.py @@ -32,6 +32,7 @@ from esphome.const import ( from esphome.types import ConfigType CODEOWNERS = ["@synco"] +DOMAIN = "graph" DEPENDENCIES = ["display", "sensor"] MULTI_CONF = True diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index 9ceb2f2ba01..e09eb385e32 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -9,8 +9,8 @@ namespace esphome::graph { using namespace display; -static const char *const TAG = "graph"; -static const char *const TAGL = "graphlegend"; +ESPHOME_LOG_TAG(TAG, "graph"); +ESPHOME_LOG_TAG(TAGL, "graphlegend"); void HistoryData::init(int length) { this->length_ = length; diff --git a/esphome/components/graphical_display_menu/__init__.py b/esphome/components/graphical_display_menu/__init__.py index 668a0d74d14..a398aea6192 100644 --- a/esphome/components/graphical_display_menu/__init__.py +++ b/esphome/components/graphical_display_menu/__init__.py @@ -13,7 +13,6 @@ from esphome.const import ( CONF_FONT, CONF_FOREGROUND_COLOR, CONF_ID, - CONF_TRIGGER_ID, ) from esphome.types import ConfigType @@ -29,11 +28,9 @@ MenuItemValueArguments = graphical_display_menu_ns.struct("MenuItemValueArgument MenuItemValueArgumentsConstPtr = MenuItemValueArguments.operator("ptr").operator( "const" ) -GraphicalDisplayMenuOnRedrawTrigger = graphical_display_menu_ns.class_( - "GraphicalDisplayMenuOnRedrawTrigger", automation.Trigger -) CODEOWNERS = ["@MrMDavidson"] +DOMAIN = "graphical_display_menu" AUTO_LOAD = ["display_menu_base"] @@ -48,13 +45,7 @@ CONFIG_SCHEMA = DISPLAY_MENU_BASE_SCHEMA.extend( cv.Optional(CONF_MENU_ITEM_VALUE): cv.templatable(cv.string), cv.Optional(CONF_FOREGROUND_COLOR): cv.use_id(color.ColorStruct), cv.Optional(CONF_BACKGROUND_COLOR): cv.use_id(color.ColorStruct), - cv.Optional(CONF_ON_REDRAW): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - GraphicalDisplayMenuOnRedrawTrigger - ) - } - ), + cv.Optional(CONF_ON_REDRAW): automation.validate_automation({}), } ) ) @@ -91,9 +82,8 @@ async def to_code(config: ConfigType) -> None: cg.add(var.set_background_color(background_color)) for conf in config.get(CONF_ON_REDRAW, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(GraphicalDisplayMenuConstPtr, "it")], conf + await automation.build_callback_automation( + var, "add_on_redraw_callback", [(GraphicalDisplayMenuConstPtr, "it")], conf ) await display_menu_to_code(var, config) diff --git a/esphome/components/graphical_display_menu/graphical_display_menu.cpp b/esphome/components/graphical_display_menu/graphical_display_menu.cpp index d261c488557..5f5b3e8d425 100644 --- a/esphome/components/graphical_display_menu/graphical_display_menu.cpp +++ b/esphome/components/graphical_display_menu/graphical_display_menu.cpp @@ -7,7 +7,7 @@ namespace esphome::graphical_display_menu { -static const char *const TAG = "graphical_display_menu"; +ESPHOME_LOG_TAG(TAG, "graphical_display_menu"); void GraphicalDisplayMenu::setup() { if (this->display_ != nullptr) { @@ -241,6 +241,6 @@ void GraphicalDisplayMenu::draw_item(const display_menu_base::MenuItem *item, co "draw_item should be called."); } -void GraphicalDisplayMenu::update() { this->on_redraw_callbacks_.call(); } +void GraphicalDisplayMenu::update() { this->on_redraw_callbacks_.call(this); } } // namespace esphome::graphical_display_menu diff --git a/esphome/components/graphical_display_menu/graphical_display_menu.h b/esphome/components/graphical_display_menu/graphical_display_menu.h index 13c0f9d73f8..1f1d8ff3fc5 100644 --- a/esphome/components/graphical_display_menu/graphical_display_menu.h +++ b/esphome/components/graphical_display_menu/graphical_display_menu.h @@ -70,17 +70,7 @@ class GraphicalDisplayMenu final : public display_menu_base::DisplayMenuComponen Color foreground_color_{COLOR_ON}; Color background_color_{COLOR_OFF}; - CallbackManager on_redraw_callbacks_{}; -}; - -class GraphicalDisplayMenuOnRedrawTrigger final : public Trigger { - public: - explicit GraphicalDisplayMenuOnRedrawTrigger(GraphicalDisplayMenu *parent) : parent_(parent) { - parent->add_on_redraw_callback([this]() { this->trigger(this->parent_); }); - } - - protected: - GraphicalDisplayMenu *parent_; + LazyCallbackManager on_redraw_callbacks_{}; }; } // namespace graphical_display_menu diff --git a/esphome/components/gree/__init__.py b/esphome/components/gree/__init__.py index 2dd9ac0f1c7..67489b38767 100644 --- a/esphome/components/gree/__init__.py +++ b/esphome/components/gree/__init__.py @@ -1,3 +1,5 @@ import esphome.codegen as cg +DOMAIN = "gree" + gree_ns = cg.esphome_ns.namespace("gree") diff --git a/esphome/components/gree/gree.cpp b/esphome/components/gree/gree.cpp index e59f186521b..f47be4aea03 100644 --- a/esphome/components/gree/gree.cpp +++ b/esphome/components/gree/gree.cpp @@ -3,7 +3,7 @@ namespace esphome::gree { -static const char *const TAG = "gree.climate"; +ESPHOME_LOG_TAG(TAG, "gree.climate"); void GreeClimate::set_model(Model model) { if (model == GREE_YAN) { diff --git a/esphome/components/gree/switch/gree_switch.cpp b/esphome/components/gree/switch/gree_switch.cpp index 2f649733afc..0d8f7695809 100644 --- a/esphome/components/gree/switch/gree_switch.cpp +++ b/esphome/components/gree/switch/gree_switch.cpp @@ -3,7 +3,7 @@ namespace esphome::gree { -static const char *const TAG = "gree.switch"; +ESPHOME_LOG_TAG(TAG, "gree.switch"); void GreeModeBitSwitch::setup() { auto initial = this->get_initial_state_with_restore_mode(); diff --git a/esphome/components/grove_gas_mc_v2/__init__.py b/esphome/components/grove_gas_mc_v2/__init__.py index e69de29bb2d..31a956ac6f2 100644 --- a/esphome/components/grove_gas_mc_v2/__init__.py +++ b/esphome/components/grove_gas_mc_v2/__init__.py @@ -0,0 +1 @@ +DOMAIN = "grove_gas_mc_v2" diff --git a/esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp b/esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp index 0de0b02182f..b01dd119e63 100644 --- a/esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp +++ b/esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp @@ -4,7 +4,7 @@ namespace esphome::grove_gas_mc_v2 { -static const char *const TAG = "grove_gas_mc_v2"; +ESPHOME_LOG_TAG(TAG, "grove_gas_mc_v2"); // I2C Commands for Grove Gas Multichannel V2 Sensor // Taken from: diff --git a/esphome/components/grove_tb6612fng/__init__.py b/esphome/components/grove_tb6612fng/__init__.py index 2d44d883230..0874613c5cf 100644 --- a/esphome/components/grove_tb6612fng/__init__.py +++ b/esphome/components/grove_tb6612fng/__init__.py @@ -13,6 +13,7 @@ from esphome.const import ( DEPENDENCIES = ["i2c"] CODEOWNERS = ["@max246"] +DOMAIN = "grove_tb6612fng" MULTI_CONF = True diff --git a/esphome/components/grove_tb6612fng/grove_tb6612fng.cpp b/esphome/components/grove_tb6612fng/grove_tb6612fng.cpp index 2c68eef623c..80de3451d77 100644 --- a/esphome/components/grove_tb6612fng/grove_tb6612fng.cpp +++ b/esphome/components/grove_tb6612fng/grove_tb6612fng.cpp @@ -4,7 +4,7 @@ namespace esphome::grove_tb6612fng { -static const char *const TAG = "GroveMotorDriveTB6612FNG"; +ESPHOME_LOG_TAG(TAG, "GroveMotorDriveTB6612FNG"); static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_BRAKE = 0x00; static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_STOP = 0x01; diff --git a/esphome/components/growatt_solar/__init__.py b/esphome/components/growatt_solar/__init__.py index e69de29bb2d..078e8367830 100644 --- a/esphome/components/growatt_solar/__init__.py +++ b/esphome/components/growatt_solar/__init__.py @@ -0,0 +1 @@ +DOMAIN = "growatt_solar" diff --git a/esphome/components/growatt_solar/growatt_solar.cpp b/esphome/components/growatt_solar/growatt_solar.cpp index 08c3966ed9b..5c079b42d92 100644 --- a/esphome/components/growatt_solar/growatt_solar.cpp +++ b/esphome/components/growatt_solar/growatt_solar.cpp @@ -5,7 +5,7 @@ namespace esphome::growatt_solar { namespace helpers = modbus::helpers; -static const char *const TAG = "growatt_solar"; +ESPHOME_LOG_TAG(TAG, "growatt_solar"); static const uint8_t MODBUS_REGISTER_COUNT[] = {33, 95}; // indexed with enum GrowattProtocolVersion diff --git a/esphome/components/gsl3670/__init__.py b/esphome/components/gsl3670/__init__.py index c58ce8a01e8..24d19ee8873 100644 --- a/esphome/components/gsl3670/__init__.py +++ b/esphome/components/gsl3670/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@clydebarrow"] +DOMAIN = "gsl3670" diff --git a/esphome/components/gsl3670/gsl3670_touchscreen.cpp b/esphome/components/gsl3670/gsl3670_touchscreen.cpp index 9115130f4a0..e518368071c 100644 --- a/esphome/components/gsl3670/gsl3670_touchscreen.cpp +++ b/esphome/components/gsl3670/gsl3670_touchscreen.cpp @@ -4,7 +4,7 @@ namespace esphome::gsl3670 { -static const char *const TAG = "gsl3670.touchscreen"; +ESPHOME_LOG_TAG(TAG, "gsl3670.touchscreen"); static const size_t MAX_TOUCHES = 3; // --------------------------------------------------------------------------- // setup() – mirrors esp_lcd_touch_gsl3670_init() in the Seeed BSP: diff --git a/esphome/components/gt911/__init__.py b/esphome/components/gt911/__init__.py index 1f7ecd1d5e8..2b45ba5efe7 100644 --- a/esphome/components/gt911/__init__.py +++ b/esphome/components/gt911/__init__.py @@ -2,5 +2,6 @@ import esphome.codegen as cg CODEOWNERS = ["@jesserockz", "@clydebarrow"] DEPENDENCIES = ["i2c"] +DOMAIN = "gt911" gt911_ns = cg.esphome_ns.namespace("gt911") diff --git a/esphome/components/gt911/binary_sensor/gt911_button.cpp b/esphome/components/gt911/binary_sensor/gt911_button.cpp index 7b356f3946d..2a3fc631d60 100644 --- a/esphome/components/gt911/binary_sensor/gt911_button.cpp +++ b/esphome/components/gt911/binary_sensor/gt911_button.cpp @@ -3,7 +3,7 @@ namespace esphome::gt911 { -static const char *const TAG = "GT911.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "GT911.binary_sensor"); void GT911Button::setup() { this->parent_->register_button_listener(this); diff --git a/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp b/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp index 8ced2679474..07dd325a2c7 100644 --- a/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp +++ b/esphome/components/gt911/touchscreen/gt911_touchscreen.cpp @@ -6,7 +6,7 @@ namespace esphome::gt911 { -static const char *const TAG = "gt911.touchscreen"; +ESPHOME_LOG_TAG(TAG, "gt911.touchscreen"); static const uint8_t PRIMARY_ADDRESS = 0x5D; // default I2C address for GT911 static const uint8_t SECONDARY_ADDRESS = 0x14; // secondary I2C address for GT911 diff --git a/esphome/components/haier/__init__.py b/esphome/components/haier/__init__.py index e69de29bb2d..605d3151853 100644 --- a/esphome/components/haier/__init__.py +++ b/esphome/components/haier/__init__.py @@ -0,0 +1 @@ +DOMAIN = "haier" diff --git a/esphome/components/haier/climate.py b/esphome/components/haier/climate.py index 5c4d498912d..ba760536865 100644 --- a/esphome/components/haier/climate.py +++ b/esphome/components/haier/climate.py @@ -4,13 +4,14 @@ from esphome import automation import esphome.codegen as cg from esphome.components import climate, logger, uart from esphome.components.climate import ClimateMode, ClimatePreset, ClimateSwingMode +from esphome.components.logger import DOMAIN as LOGGER_DOMAIN +from esphome.components.wifi import DOMAIN as WIFI_DOMAIN import esphome.config_validation as cv from esphome.const import ( CONF_BEEPER, CONF_CURRENT_TEMPERATURE, CONF_DISPLAY, CONF_LEVEL, - CONF_LOGGER, CONF_LOGS, CONF_MAX_TEMPERATURE, CONF_MIN_TEMPERATURE, @@ -22,7 +23,6 @@ from esphome.const import ( CONF_TARGET_TEMPERATURE, CONF_TEMPERATURE_STEP, CONF_VISUAL, - CONF_WIFI, ) from esphome.cpp_generator import MockObjClass import esphome.final_validate as fv @@ -326,9 +326,9 @@ automation.register_apply_action( def _final_validate(config) -> None: full_config = fv.full_config.get() - if CONF_LOGGER in full_config: + if LOGGER_DOMAIN in full_config: _level = "NONE" - logger_config = full_config[CONF_LOGGER] + logger_config = full_config[LOGGER_DOMAIN] if CONF_LOGS in logger_config: if "haier.protocol" in logger_config[CONF_LOGS]: _level = logger_config[CONF_LOGS]["haier.protocol"] @@ -344,7 +344,7 @@ def _final_validate(config) -> None: "No logger component found, logging for Haier protocol is disabled" ) cg.add_build_flag("-DHAIER_LOG_LEVEL=0") - if config.get(CONF_WIFI_SIGNAL) and CONF_WIFI not in full_config: + if config.get(CONF_WIFI_SIGNAL) and WIFI_DOMAIN not in full_config: raise cv.Invalid( f"No WiFi configured, if you want to use haier climate without WiFi add {CONF_WIFI_SIGNAL}: false to climate configuration" ) diff --git a/esphome/components/haier/haier_base.cpp b/esphome/components/haier/haier_base.cpp index 87f9331d555..65a3ed2616f 100644 --- a/esphome/components/haier/haier_base.cpp +++ b/esphome/components/haier/haier_base.cpp @@ -12,7 +12,7 @@ using namespace esphome::uart; namespace esphome::haier { -static const char *const TAG = "haier.climate"; +ESPHOME_LOG_TAG(TAG, "haier.climate"); constexpr size_t COMMUNICATION_TIMEOUT_MS = 60000; constexpr size_t STATUS_REQUEST_INTERVAL_MS = 5000; constexpr size_t PROTOCOL_INITIALIZATION_INTERVAL = 10000; diff --git a/esphome/components/haier/hon_climate.cpp b/esphome/components/haier/hon_climate.cpp index 0ce4142fd43..5e672c3e65d 100644 --- a/esphome/components/haier/hon_climate.cpp +++ b/esphome/components/haier/hon_climate.cpp @@ -11,7 +11,7 @@ using namespace esphome::uart; namespace esphome::haier { -static const char *const TAG = "haier.climate"; +ESPHOME_LOG_TAG(TAG, "haier.climate"); constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000; constexpr int PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET = -64; constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5; diff --git a/esphome/components/haier/smartair2_climate.cpp b/esphome/components/haier/smartair2_climate.cpp index fdb3b779e2f..01e1cbaeed5 100644 --- a/esphome/components/haier/smartair2_climate.cpp +++ b/esphome/components/haier/smartair2_climate.cpp @@ -9,7 +9,7 @@ using namespace esphome::uart; namespace esphome::haier { -static const char *const TAG = "haier.climate"; +ESPHOME_LOG_TAG(TAG, "haier.climate"); constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000; constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5; constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500); diff --git a/esphome/components/havells_solar/__init__.py b/esphome/components/havells_solar/__init__.py index e69de29bb2d..f6fa38817c4 100644 --- a/esphome/components/havells_solar/__init__.py +++ b/esphome/components/havells_solar/__init__.py @@ -0,0 +1 @@ +DOMAIN = "havells_solar" diff --git a/esphome/components/havells_solar/havells_solar.cpp b/esphome/components/havells_solar/havells_solar.cpp index d43dfbb89aa..6abca8c2dde 100644 --- a/esphome/components/havells_solar/havells_solar.cpp +++ b/esphome/components/havells_solar/havells_solar.cpp @@ -6,7 +6,7 @@ namespace esphome::havells_solar { namespace helpers = modbus::helpers; -static const char *const TAG = "havells_solar"; +ESPHOME_LOG_TAG(TAG, "havells_solar"); static const uint8_t MODBUS_REGISTER_COUNT = 48; // 48 x 16-bit registers diff --git a/esphome/components/hbridge/__init__.py b/esphome/components/hbridge/__init__.py index 7eae863ff53..bbf8313de07 100644 --- a/esphome/components/hbridge/__init__.py +++ b/esphome/components/hbridge/__init__.py @@ -1,3 +1,5 @@ import esphome.codegen as cg +DOMAIN = "hbridge" + hbridge_ns = cg.esphome_ns.namespace("hbridge") diff --git a/esphome/components/hbridge/fan/hbridge_fan.cpp b/esphome/components/hbridge/fan/hbridge_fan.cpp index fc2a738e8d5..311f82b8505 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.cpp +++ b/esphome/components/hbridge/fan/hbridge_fan.cpp @@ -3,7 +3,7 @@ namespace esphome::hbridge { -static const char *const TAG = "fan.hbridge"; +ESPHOME_LOG_TAG(TAG, "fan.hbridge"); void HBridgeFan::set_hbridge_levels_(float a_level, float b_level) { this->pin_a_->set_level(a_level); diff --git a/esphome/components/hbridge/switch/hbridge_switch.cpp b/esphome/components/hbridge/switch/hbridge_switch.cpp index c8e472d7aab..837c355f460 100644 --- a/esphome/components/hbridge/switch/hbridge_switch.cpp +++ b/esphome/components/hbridge/switch/hbridge_switch.cpp @@ -5,7 +5,7 @@ namespace esphome::hbridge { -static const char *const TAG = "switch.hbridge"; +ESPHOME_LOG_TAG(TAG, "switch.hbridge"); float HBridgeSwitch::get_setup_priority() const { return setup_priority::HARDWARE; } void HBridgeSwitch::setup() { diff --git a/esphome/components/hc8/__init__.py b/esphome/components/hc8/__init__.py index e1028456b0a..bbcfae894a5 100644 --- a/esphome/components/hc8/__init__.py +++ b/esphome/components/hc8/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@omartijn"] +DOMAIN = "hc8" diff --git a/esphome/components/hc8/hc8.cpp b/esphome/components/hc8/hc8.cpp index 6a19f977a65..4fbfbc84d70 100644 --- a/esphome/components/hc8/hc8.cpp +++ b/esphome/components/hc8/hc8.cpp @@ -7,7 +7,7 @@ namespace esphome::hc8 { -static const char *const TAG = "hc8"; +ESPHOME_LOG_TAG(TAG, "hc8"); static const std::array HC8_COMMAND_GET_PPM{0x64, 0x69, 0x03, 0x5E, 0x4E}; static const std::array HC8_COMMAND_CALIBRATE_PREAMBLE{0x11, 0x03, 0x03}; diff --git a/esphome/components/hdc1080/__init__.py b/esphome/components/hdc1080/__init__.py index e69de29bb2d..7c1f276f3d1 100644 --- a/esphome/components/hdc1080/__init__.py +++ b/esphome/components/hdc1080/__init__.py @@ -0,0 +1 @@ +DOMAIN = "hdc1080" diff --git a/esphome/components/hdc1080/hdc1080.cpp b/esphome/components/hdc1080/hdc1080.cpp index 6692345e62e..be0de1bae6e 100644 --- a/esphome/components/hdc1080/hdc1080.cpp +++ b/esphome/components/hdc1080/hdc1080.cpp @@ -4,7 +4,7 @@ namespace esphome::hdc1080 { -static const char *const TAG = "hdc1080"; +ESPHOME_LOG_TAG(TAG, "hdc1080"); static const uint8_t HDC1080_CMD_CONFIGURATION = 0x02; static const uint8_t HDC1080_CMD_TEMPERATURE = 0x00; diff --git a/esphome/components/hdc2010/__init__.py b/esphome/components/hdc2010/__init__.py index badf9dbb0c5..839dc630b24 100644 --- a/esphome/components/hdc2010/__init__.py +++ b/esphome/components/hdc2010/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@optimusprimespace", "@ssieb"] +DOMAIN = "hdc2010" diff --git a/esphome/components/hdc2010/hdc2010.cpp b/esphome/components/hdc2010/hdc2010.cpp index 946cf4898f1..825d7d3da96 100644 --- a/esphome/components/hdc2010/hdc2010.cpp +++ b/esphome/components/hdc2010/hdc2010.cpp @@ -5,7 +5,7 @@ namespace esphome::hdc2010 { -static const char *const TAG = "hdc2010"; +ESPHOME_LOG_TAG(TAG, "hdc2010"); // Register addresses static constexpr uint8_t REG_TEMPERATURE_LOW = 0x00; diff --git a/esphome/components/hdc2080/__init__.py b/esphome/components/hdc2080/__init__.py index 341ea610489..e1ab22cc418 100644 --- a/esphome/components/hdc2080/__init__.py +++ b/esphome/components/hdc2080/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@G-Pereira", "@jesserockz"] +DOMAIN = "hdc2080" diff --git a/esphome/components/hdc2080/hdc2080.cpp b/esphome/components/hdc2080/hdc2080.cpp index bf3a4bc79f1..8210733b4cb 100644 --- a/esphome/components/hdc2080/hdc2080.cpp +++ b/esphome/components/hdc2080/hdc2080.cpp @@ -4,7 +4,7 @@ namespace esphome::hdc2080 { -static const char *const TAG = "hdc2080"; +ESPHOME_LOG_TAG(TAG, "hdc2080"); // Register map (Table 8-6) static constexpr uint8_t REG_TEMPERATURE_LOW = 0x00; // Temperature [7:0] diff --git a/esphome/components/hdc302x/__init__.py b/esphome/components/hdc302x/__init__.py index d8165281bfe..2fbfb0191ce 100644 --- a/esphome/components/hdc302x/__init__.py +++ b/esphome/components/hdc302x/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@joshuasing"] +DOMAIN = "hdc302x" diff --git a/esphome/components/hdc302x/hdc302x.cpp b/esphome/components/hdc302x/hdc302x.cpp index 53d4c7f016b..d5428486579 100644 --- a/esphome/components/hdc302x/hdc302x.cpp +++ b/esphome/components/hdc302x/hdc302x.cpp @@ -6,7 +6,9 @@ namespace esphome::hdc302x { -static const char *const TAG = "hdc302x.sensor"; +ESPHOME_LOG_TAG(TAG, "hdc302x.sensor"); + +static constexpr uint32_t HEATER_OFF_TIMEOUT_ID = 0; // Commands (per datasheet Table 7-4) static const uint8_t HDC302X_CMD_SOFT_RESET[2] = {0x30, 0xa2}; @@ -68,14 +70,14 @@ void HDC302XComponent::start_heater(uint16_t power, uint32_t duration_ms) { return; } this->heater_active_ = true; - this->cancel_timeout("heater_off"); + this->cancel_timeout(HEATER_OFF_TIMEOUT_ID); if (duration_ms > 0) { - this->set_timeout("heater_off", duration_ms, [this]() { this->stop_heater(); }); + this->set_timeout(HEATER_OFF_TIMEOUT_ID, duration_ms, [this]() { this->stop_heater(); }); } } void HDC302XComponent::stop_heater() { - this->cancel_timeout("heater_off"); + this->cancel_timeout(HEATER_OFF_TIMEOUT_ID); if (!this->disable_heater_()) { ESP_LOGW(TAG, "Heater stop failed"); } diff --git a/esphome/components/he60r/__init__.py b/esphome/components/he60r/__init__.py index c58ce8a01e8..070ba595a30 100644 --- a/esphome/components/he60r/__init__.py +++ b/esphome/components/he60r/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@clydebarrow"] +DOMAIN = "he60r" diff --git a/esphome/components/he60r/he60r.cpp b/esphome/components/he60r/he60r.cpp index 008505e2bbf..7c67c5ffd3f 100644 --- a/esphome/components/he60r/he60r.cpp +++ b/esphome/components/he60r/he60r.cpp @@ -6,7 +6,7 @@ namespace esphome::he60r { -static const char *const TAG = "he60r.cover"; +ESPHOME_LOG_TAG(TAG, "he60r.cover"); static const uint8_t QUERY_BYTE = 0x38; static const uint8_t TOGGLE_BYTE = 0x30; diff --git a/esphome/components/heatpumpir/__init__.py b/esphome/components/heatpumpir/__init__.py index e69de29bb2d..f0ef79b8958 100644 --- a/esphome/components/heatpumpir/__init__.py +++ b/esphome/components/heatpumpir/__init__.py @@ -0,0 +1 @@ +DOMAIN = "heatpumpir" diff --git a/esphome/components/heatpumpir/heatpumpir.cpp b/esphome/components/heatpumpir/heatpumpir.cpp index 502f83cd5d4..2376a67416f 100644 --- a/esphome/components/heatpumpir/heatpumpir.cpp +++ b/esphome/components/heatpumpir/heatpumpir.cpp @@ -41,7 +41,7 @@ class IRSenderESPHome : public IRSender { remote_base::RemoteTransmitterBase::TransmitCall transmit_; }; -static const char *const TAG = "heatpumpir.climate"; +ESPHOME_LOG_TAG(TAG, "heatpumpir.climate"); const std::map> PROTOCOL_CONSTRUCTOR_MAP = { {PROTOCOL_AUX, []() { return new AUXHeatpumpIR(); }}, // NOLINT @@ -187,15 +187,18 @@ void HeatpumpIRClimate::transmit_state() { swing_h_cmd = HDIR_SWING; } + // MitsubishiMSCHeatpumpIR only knows FAN_1..FAN_3 and sends anything else as auto, + // so low/medium/high have to start at FAN_1 there instead of FAN_2. + const bool three_speed_fan = this->protocol_ == PROTOCOL_MITSUBISHI_MSC; switch (this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO)) { case climate::CLIMATE_FAN_LOW: - fan_speed_cmd = FAN_2; + fan_speed_cmd = three_speed_fan ? FAN_1 : FAN_2; break; case climate::CLIMATE_FAN_MEDIUM: - fan_speed_cmd = FAN_3; + fan_speed_cmd = three_speed_fan ? FAN_2 : FAN_3; break; case climate::CLIMATE_FAN_HIGH: - fan_speed_cmd = FAN_4; + fan_speed_cmd = three_speed_fan ? FAN_3 : FAN_4; break; case climate::CLIMATE_FAN_AUTO: default: diff --git a/esphome/components/hitachi_ac344/__init__.py b/esphome/components/hitachi_ac344/__init__.py index e69de29bb2d..15dac432607 100644 --- a/esphome/components/hitachi_ac344/__init__.py +++ b/esphome/components/hitachi_ac344/__init__.py @@ -0,0 +1 @@ +DOMAIN = "hitachi_ac344" diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.cpp b/esphome/components/hitachi_ac344/hitachi_ac344.cpp index ea211a7a59b..c712500a53d 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.cpp +++ b/esphome/components/hitachi_ac344/hitachi_ac344.cpp @@ -2,7 +2,7 @@ namespace esphome::hitachi_ac344 { -static const char *const TAG = "climate.hitachi_ac344"; +ESPHOME_LOG_TAG(TAG, "climate.hitachi_ac344"); void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, const uint8_t data) { if (offset >= 8 || !nbits) diff --git a/esphome/components/hitachi_ac424/__init__.py b/esphome/components/hitachi_ac424/__init__.py index 10f2c27fe83..8372b84c7e9 100644 --- a/esphome/components/hitachi_ac424/__init__.py +++ b/esphome/components/hitachi_ac424/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@sourabhjaiswal"] +DOMAIN = "hitachi_ac424" diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.cpp b/esphome/components/hitachi_ac424/hitachi_ac424.cpp index cdf8dda22ce..d8f7f661799 100644 --- a/esphome/components/hitachi_ac424/hitachi_ac424.cpp +++ b/esphome/components/hitachi_ac424/hitachi_ac424.cpp @@ -2,7 +2,7 @@ namespace esphome::hitachi_ac424 { -static const char *const TAG = "climate.hitachi_ac424"; +ESPHOME_LOG_TAG(TAG, "climate.hitachi_ac424"); void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, const uint8_t data) { if (offset >= 8 || !nbits) diff --git a/esphome/components/hlk_fm22x/__init__.py b/esphome/components/hlk_fm22x/__init__.py index b8040e55794..72d3cc2ada3 100644 --- a/esphome/components/hlk_fm22x/__init__.py +++ b/esphome/components/hlk_fm22x/__init__.py @@ -13,6 +13,7 @@ from esphome.const import ( CODEOWNERS = ["@OnFreund"] DEPENDENCIES = ["uart"] AUTO_LOAD = ["binary_sensor", "sensor", "text_sensor"] +DOMAIN = "hlk_fm22x" MULTI_CONF = True CONF_HLK_FM22X_ID = "hlk_fm22x_id" diff --git a/esphome/components/hlk_fm22x/hlk_fm22x.cpp b/esphome/components/hlk_fm22x/hlk_fm22x.cpp index a9242598027..e4845daa57b 100644 --- a/esphome/components/hlk_fm22x/hlk_fm22x.cpp +++ b/esphome/components/hlk_fm22x/hlk_fm22x.cpp @@ -5,7 +5,7 @@ namespace esphome::hlk_fm22x { -static const char *const TAG = "hlk_fm22x"; +ESPHOME_LOG_TAG(TAG, "hlk_fm22x"); static constexpr uint32_t PAYLOAD_TIMEOUT_MS = 20; void HlkFm22xComponent::setup() { diff --git a/esphome/components/hlw8012/__init__.py b/esphome/components/hlw8012/__init__.py index e69de29bb2d..3da6262ce7f 100644 --- a/esphome/components/hlw8012/__init__.py +++ b/esphome/components/hlw8012/__init__.py @@ -0,0 +1 @@ +DOMAIN = "hlw8012" diff --git a/esphome/components/hlw8012/hlw8012.cpp b/esphome/components/hlw8012/hlw8012.cpp index 9ef81f075d3..a14a9aaf486 100644 --- a/esphome/components/hlw8012/hlw8012.cpp +++ b/esphome/components/hlw8012/hlw8012.cpp @@ -3,7 +3,7 @@ namespace esphome::hlw8012 { -static const char *const TAG = "hlw8012"; +ESPHOME_LOG_TAG(TAG, "hlw8012"); // valid for HLW8012 and CSE7759 static const uint32_t HLW8012_CLOCK_FREQUENCY = 3579000; diff --git a/esphome/components/hlw8032/__init__.py b/esphome/components/hlw8032/__init__.py index 4908e10037a..fc9c674659f 100644 --- a/esphome/components/hlw8032/__init__.py +++ b/esphome/components/hlw8032/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@rici4kubicek"] +DOMAIN = "hlw8032" diff --git a/esphome/components/hlw8032/hlw8032.cpp b/esphome/components/hlw8032/hlw8032.cpp index 55e6664a8b8..cbb6b6c628d 100644 --- a/esphome/components/hlw8032/hlw8032.cpp +++ b/esphome/components/hlw8032/hlw8032.cpp @@ -4,7 +4,7 @@ namespace esphome::hlw8032 { -static const char *const TAG = "hlw8032"; +ESPHOME_LOG_TAG(TAG, "hlw8032"); static constexpr uint8_t STATE_REG_OFFSET = 0; static constexpr uint8_t VOLTAGE_PARAM_OFFSET = 2; diff --git a/esphome/components/hm3301/__init__.py b/esphome/components/hm3301/__init__.py index e69de29bb2d..eb459892701 100644 --- a/esphome/components/hm3301/__init__.py +++ b/esphome/components/hm3301/__init__.py @@ -0,0 +1 @@ +DOMAIN = "hm3301" diff --git a/esphome/components/hm3301/hm3301.cpp b/esphome/components/hm3301/hm3301.cpp index 02c6e751468..bfed99b21b5 100644 --- a/esphome/components/hm3301/hm3301.cpp +++ b/esphome/components/hm3301/hm3301.cpp @@ -3,7 +3,7 @@ namespace esphome::hm3301 { -static const char *const TAG = "hm3301.sensor"; +ESPHOME_LOG_TAG(TAG, "hm3301.sensor"); static const uint8_t PM_1_0_VALUE_INDEX = 5; static const uint8_t PM_2_5_VALUE_INDEX = 6; diff --git a/esphome/components/hmac_md5/__init__.py b/esphome/components/hmac_md5/__init__.py index e37eb9b1164..72f9aa60fea 100644 --- a/esphome/components/hmac_md5/__init__.py +++ b/esphome/components/hmac_md5/__init__.py @@ -2,5 +2,6 @@ import esphome.config_validation as cv AUTO_LOAD = ["md5"] CODEOWNERS = ["@dwmw2"] +DOMAIN = "hmac_md5" CONFIG_SCHEMA = cv.Schema({}) diff --git a/esphome/components/hmac_sha256/__init__.py b/esphome/components/hmac_sha256/__init__.py index 158d740dc59..4f42c968a39 100644 --- a/esphome/components/hmac_sha256/__init__.py +++ b/esphome/components/hmac_sha256/__init__.py @@ -2,5 +2,6 @@ import esphome.config_validation as cv AUTO_LOAD = ["sha256"] CODEOWNERS = ["@dwmw2"] +DOMAIN = "hmac_sha256" CONFIG_SCHEMA = cv.Schema({}) diff --git a/esphome/components/hmc5883l/__init__.py b/esphome/components/hmc5883l/__init__.py index e69de29bb2d..5326507e448 100644 --- a/esphome/components/hmc5883l/__init__.py +++ b/esphome/components/hmc5883l/__init__.py @@ -0,0 +1 @@ +DOMAIN = "hmc5883l" diff --git a/esphome/components/hmc5883l/hmc5883l.cpp b/esphome/components/hmc5883l/hmc5883l.cpp index c6b7da66105..c747e7968ed 100644 --- a/esphome/components/hmc5883l/hmc5883l.cpp +++ b/esphome/components/hmc5883l/hmc5883l.cpp @@ -6,7 +6,7 @@ namespace esphome::hmc5883l { -static const char *const TAG = "hmc5883l"; +ESPHOME_LOG_TAG(TAG, "hmc5883l"); static const uint8_t HMC5883L_ADDRESS = 0x1E; static const uint8_t HMC5883L_REGISTER_CONFIG_A = 0x00; static const uint8_t HMC5883L_REGISTER_CONFIG_B = 0x01; diff --git a/esphome/components/hoermann_hcp/__init__.py b/esphome/components/hoermann_hcp/__init__.py index 958b495c2e9..addd1b299b5 100644 --- a/esphome/components/hoermann_hcp/__init__.py +++ b/esphome/components/hoermann_hcp/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@zweckj"] DEPENDENCIES = ["modbus"] +DOMAIN = "hoermann_hcp" MULTI_CONF = True CONF_HOERMANN_HCP_ID = "hoermann_hcp_id" diff --git a/esphome/components/hoermann_hcp/binary_sensor/hoermann_hcp_binary_sensor.cpp b/esphome/components/hoermann_hcp/binary_sensor/hoermann_hcp_binary_sensor.cpp index edce6ce4c2b..db32eb29521 100644 --- a/esphome/components/hoermann_hcp/binary_sensor/hoermann_hcp_binary_sensor.cpp +++ b/esphome/components/hoermann_hcp/binary_sensor/hoermann_hcp_binary_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::hoermann_hcp { -static const char *const TAG = "hoermann_hcp.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "hoermann_hcp.binary_sensor"); void HoermannHcpConnectedBinarySensor::setup() { // Publishing unconditionally is deliberate: the base class dedupes, and filters need every input to drive diff --git a/esphome/components/hoermann_hcp/cover/hoermann_hcp_cover.cpp b/esphome/components/hoermann_hcp/cover/hoermann_hcp_cover.cpp index 66a141758e8..ab2afc74d93 100644 --- a/esphome/components/hoermann_hcp/cover/hoermann_hcp_cover.cpp +++ b/esphome/components/hoermann_hcp/cover/hoermann_hcp_cover.cpp @@ -4,7 +4,7 @@ namespace esphome::hoermann_hcp { -static const char *const TAG = "hoermann_hcp.cover"; +ESPHOME_LOG_TAG(TAG, "hoermann_hcp.cover"); cover::CoverTraits HoermannHcpCover::get_traits() { cover::CoverTraits traits; diff --git a/esphome/components/hoermann_hcp/hoermann_hcp.cpp b/esphome/components/hoermann_hcp/hoermann_hcp.cpp index 9ab7014e0e4..bbf1380d178 100644 --- a/esphome/components/hoermann_hcp/hoermann_hcp.cpp +++ b/esphome/components/hoermann_hcp/hoermann_hcp.cpp @@ -1,6 +1,7 @@ #include "hoermann_hcp.h" #include +#include #include "esphome/core/hal.h" #include "esphome/core/helpers.h" @@ -8,7 +9,7 @@ namespace esphome::hoermann_hcp { -static const char *const TAG = "hoermann_hcp"; +ESPHOME_LOG_TAG(TAG, "hoermann_hcp"); // Hoermann HCP holding-register blocks. static constexpr uint16_t COMMAND_REG = 0x9C41; // Commands written by the bus controller @@ -16,20 +17,17 @@ static constexpr uint16_t STATE_REG = 0x9CB9; // Internal state read back b static constexpr uint16_t BROADCAST_REG = 0x9D31; // Door status broadcast by the bus controller static constexpr float CLOSE_POSITION_THRESHOLD = 0.05f; static constexpr float OPEN_POSITION_THRESHOLD = 0.95f; -// Only the parity of the outstanding toggles says where the lamp is heading, so the count must not run away. -static constexpr uint8_t MAX_LIGHT_TOGGLES_IN_FLIGHT = 4; -// Command encoding: the high byte of the first register is the phase (0x02 pressed, 0x01 released) and the -// rest names the button - the low byte for the door commands, the second register for those that do not fit -// there. Both halves repeat that name, so neither register is a level to hold; they carry one event each. -static constexpr HoermannHcpCommand COMMAND_OPEN{"open", 0x0210, 0x0110}; -static constexpr HoermannHcpCommand COMMAND_CLOSE{"close", 0x0220, 0x0120}; -static constexpr HoermannHcpCommand COMMAND_IMPULSE{"impulse", 0x0240, 0x0140}; -// The intermediate positions are named in the second register, so the first only carries the phase. -static constexpr HoermannHcpCommand COMMAND_VENT{"vent", 0x0200, 0x0100, 0x4000, 0x4000}; -static constexpr HoermannHcpCommand COMMAND_HALF_OPEN{"half open", 0x0200, 0x0100, 0x0400, 0x0400}; -// The lamp is named in the second register, but its phase bytes follow no scheme the door commands share. -static constexpr HoermannHcpCommand COMMAND_TOGGLE_LAMP{"toggle light", 0x0100, 0x0800, 0x0200, 0x0200, false}; +static constexpr HoermannHcpCommand COMMAND_OPEN{"open", 0x0110}; +static constexpr HoermannHcpCommand COMMAND_CLOSE{"close", 0x0120}; +static constexpr HoermannHcpCommand COMMAND_IMPULSE{"impulse", 0x0140}; +static constexpr HoermannHcpCommand COMMAND_VENT{"vent", 0x0100, 0x4000}; +static constexpr HoermannHcpCommand COMMAND_HALF_OPEN{"half open", 0x0100, 0x0400}; +// Absolute, as a vendor gateway sends them, so a late or repeated one cannot switch the lamp the wrong way. +static constexpr HoermannHcpCommand COMMAND_LIGHT_ON{"light on", 0x0880}; +static constexpr HoermannHcpCommand COMMAND_LIGHT_OFF{"light off", 0x0800, 0x0100}; +// Kept as the intent, as the same impulse starts a door at rest; only a door still moving at the fetch gets it. +static constexpr HoermannHcpCommand COMMAND_STOP{"stop", 0x0140}; // High byte of the state register and the door state it stands for. State 0x00 is decoded separately because // its low byte tells a plain stop from the vent position. @@ -63,9 +61,24 @@ static bool is_moving(DoorState state) { } } -#ifdef USE_HOERMANN_HCP_IDENTITY -// The command byte of a status poll. Only its answer can carry a request. +static bool at_destination(const HoermannHcpCommand &command, DoorState state) { + if (&command == &COMMAND_OPEN) + return state == DoorState::OPEN; + if (&command == &COMMAND_CLOSE) + return state == DoorState::CLOSED; + if (&command == &COMMAND_VENT) + return state == DoorState::VENT; + if (&command == &COMMAND_HALF_OPEN) + return state == DoorState::HALF_OPEN; + return false; +} + +// Only a status poll's answer carries commands. static constexpr uint8_t STATUS_COMMAND = 0x03; +// A second stop this soon after one went out is ignored, so a double press cannot restart the door. +static constexpr uint32_t STOP_LOCK_MS = 500; + +#ifdef USE_HOERMANN_HCP_IDENTITY // A status answer with this code in the low byte of its second register asks the bus controller for a value, // named in the high byte of the third. static constexpr uint8_t ANSWER_REQUEST = 0x22; @@ -125,29 +138,40 @@ void HoermannHcp::update() { this->set_valid_(false); // Status broadcasts alone keep the connection alive, so a command the controller never fetches would // otherwise block every later one for as long as it keeps broadcasting. - if (this->next_command_ != nullptr && now - this->command_queued_at_ > this->connection_timeout_ms_) { - // Dropping after the press was presented leaves the door without its release value, which is worth saying - // apart from a command the controller never looked at. - if (this->command_written_at_ != 0) { - ESP_LOGW(TAG, "Bus controller stopped polling during '%s' command, dropping it mid key press", - this->next_command_->name); - } else { - ESP_LOGW(TAG, "Bus controller did not fetch '%s' command, dropping it", this->next_command_->name); - } + // A stop held for a door that has not reported its start yet is timed by the start window instead. + const bool stop_held = this->next_command_ == &COMMAND_STOP && this->starting_; + if (this->next_command_ != nullptr && !stop_held && now - this->command_queued_at_ > this->connection_timeout_ms_) { + ESP_LOGW(TAG, "Bus controller did not fetch '%s' command, dropping it", this->next_command_->name); this->drop_command_(); // Children may have assumed the command would land, so let them re-derive from the door. this->changed_ = true; } - // A target waits for a door still travelling the other way to turn around. If it never does, the target has - // to go as well, otherwise it would cut a later move short. The connection timeout doubles as that window. - if (this->has_target_() && !this->target_started_ && now - this->target_queued_at_ > this->connection_timeout_ms_) { + // A target the door never started towards would otherwise cut a later move short. + if (this->has_target_() && !this->target_started_ && now - this->target_queued_at_ > this->start_window_ms_) { ESP_LOGW(TAG, "Door did not start moving towards the requested position, dropping it"); this->clear_target_(); } - // The door took the lamp key press but never reported the lamp changing, so stop expecting it to. - if (this->light_toggle_released_at_ != 0 && now - this->light_toggle_released_at_ > this->connection_timeout_ms_) { - ESP_LOGW(TAG, "Door did not report the lamp changing, giving up on the toggle"); - this->forget_light_toggles_(); + // A door that never answers a fetched command is at rest after all. + if (this->starting_ && now - this->start_fetched_at_ > this->start_window_ms_) { + this->starting_ = false; + if (stop_held) { + ESP_LOGW(TAG, "Door did not report moving, dropping the stop"); + this->next_command_ = nullptr; + } else { + ESP_LOGD(TAG, "Door did not start after the command"); + } + } + // A lamp command held while the door starts or moves waits on purpose, so its deadline starts once the door rests. + if (this->is_moving_or_starting_() && this->light_requested_ && !this->light_command_sent_) + this->light_since_ = now; + // Neither fire late nor block the next request. + if (this->light_requested_ && now - this->light_since_ > this->connection_timeout_ms_) { + if (this->light_command_sent_) { + ESP_LOGW(TAG, "Door did not report the lamp changing, giving up"); + } else { + ESP_LOGW(TAG, "Bus controller did not fetch the lamp command, dropping it"); + } + this->clear_light_request_(); } #ifdef USE_HOERMANN_HCP_IDENTITY this->publish_identity_(); @@ -179,6 +203,7 @@ modbus::ResponseStatus HoermannHcp::on_read_holding_registers(uint16_t start_add } this->record_response_(); + const bool status_poll = std::exchange(this->status_poll_pending_, false); #ifdef USE_HOERMANN_HCP_IDENTITY // Acknowledge the transfer taken by the write half of this frame. @@ -198,7 +223,11 @@ modbus::ResponseStatus HoermannHcp::on_read_holding_registers(uint16_t start_add // Command request: return the internal state, injecting any pending command. registers.push_back(counter); registers.push_back(static_cast(0x0001 | command)); - this->push_command_registers_(registers); + if (status_poll && static_cast(this->command_reg_value_) == STATUS_COMMAND) { + this->push_command_registers_(registers); + } else { + push_zeros(registers, 2); + } push_zeros(registers, 4); #ifdef USE_HOERMANN_HCP_IDENTITY this->add_identity_request_(registers, command); @@ -234,6 +263,7 @@ modbus::ResponseStatus HoermannHcp::on_write_registers(uint16_t start_address, // command byte back from STATE_REG. The hub always runs the write before the read within one request. this->record_response_(); this->command_reg_value_ = registers[0]; + this->status_poll_pending_ = true; #ifdef USE_HOERMANN_HCP_IDENTITY this->transfer_answer_counter_ = this->take_identity_transfer_(registers); #endif @@ -268,34 +298,62 @@ modbus::ResponseStatus HoermannHcp::on_write_registers(uint16_t start_address, } void HoermannHcp::push_command_registers_(modbus::RegisterValues ®isters) { - const HoermannHcpCommand *command = this->next_command_; + const HoermannHcpCommand *command = this->take_command_(); + if (command == nullptr) + command = this->take_light_command_(); if (command == nullptr) { push_zeros(registers, 2); return; } - if (this->command_written_at_ == 0) { - // First read after the command was queued: present the "key pressed" values. - this->command_written_at_ = millis(); - ESP_LOGI(TAG, "Sending '%s' command to door", command->name); - registers.push_back(command->pressed_value); - registers.push_back(command->pressed_value_2); - return; - } - if (millis() - this->command_written_at_ <= this->key_press_delay_ms_) { - // Between the two events there is nothing to report, including in the second register. - push_zeros(registers, 2); - return; - } - // Enough time passed: present the "key released" values and clear the command. - ESP_LOGD(TAG, "Released '%s' command", command->name); - this->command_written_at_ = 0; + ESP_LOGI(TAG, "Sending '%s' command to door", command->name); + registers.push_back(command->value); + registers.push_back(command->value_2); +} + +const HoermannHcpCommand *HoermannHcp::take_command_() { + const HoermannHcpCommand *command = this->next_command_; + if (command == nullptr) + return nullptr; + const bool moving = is_moving(this->door_state_); + // The door was just told to start and has not said so yet, so an impulse now could start it instead. + if (command == &COMMAND_STOP && this->starting_ && !moving) + return nullptr; this->next_command_ = nullptr; - // A toggle whose count was already settled, by a lamp change reported from the door's side, has nothing left - // to wait for, so it must not re-arm the watchdog. - if (command == &COMMAND_TOGGLE_LAMP && this->light_toggles_in_flight_ != 0) - this->light_toggle_released_at_ = millis(); - registers.push_back(command->released_value); - registers.push_back(command->released_value_2); + if (moving) { + // The door may have been started from elsewhere since the command was queued. + if ((command == &COMMAND_OPEN && this->door_state_ == DoorState::OPENING) || + (command == &COMMAND_CLOSE && this->door_state_ == DoorState::CLOSING)) { + ESP_LOGD(TAG, "Door is already moving that way, dropping '%s'", command->name); + return nullptr; + } + if (command != &COMMAND_STOP) { + ESP_LOGD(TAG, "Door is moving, stopping it instead of '%s'", command->name); + } + this->last_stop_at_ = millis(); + this->stop_sent_ = true; + return &COMMAND_STOP; + } + if (command == &COMMAND_STOP) { + ESP_LOGD(TAG, "Door came to rest before the stop was fetched, dropping it"); + return nullptr; + } + // Until the door answers, it still reads as at rest. + if (!this->door_state_seen_ || !at_destination(*command, this->door_state_)) { + this->starting_ = true; + this->start_command_ = command; + this->start_fetched_at_ = millis(); + } + return command; +} + +const HoermannHcpCommand *HoermannHcp::take_light_command_() { + // The motor ignores the lamp while its door moves and may switch it itself as it starts, so the lamp waits for rest. + if (!this->light_requested_ || this->light_command_sent_ || this->is_moving_or_starting_() || + this->light_target_ == this->light_on_) + return nullptr; + this->light_command_sent_ = true; + this->light_since_ = millis(); + return this->light_target_ ? &COMMAND_LIGHT_ON : &COMMAND_LIGHT_OFF; } #ifdef USE_HOERMANN_HCP_IDENTITY @@ -308,8 +366,8 @@ void HoermannHcp::add_identity_request_(modbus::RegisterValues ®isters, uint1 this->arm_identity_request_(IdentityPhase::IDENTITY_PHASE_SERIAL); return; } - // Uses the registers of a key press, so it waits while one is pending. - if (this->next_command_ != nullptr || registers[2] != 0 || registers[3] != 0 || !this->take_identity_request_()) + // Uses the command registers, so it waits while a command is pending. + if (registers[2] != 0 || registers[3] != 0 || !this->take_identity_request_()) return; registers[1] = static_cast(ANSWER_REQUEST | command); registers[2] = encode_uint16(this->identity_request_(), 0); @@ -505,56 +563,57 @@ bool HoermannHcp::queue_command_(const HoermannHcpCommand &command) { ESP_LOGW(TAG, "Not connected to the bus controller, dropping '%s' command", command.name); return false; } + // A stop still waiting for a door that has come to rest would be dropped at the fetch anyway. + if (this->next_command_ == &COMMAND_STOP) + this->next_command_ = nullptr; if (this->next_command_ != nullptr) { ESP_LOGW(TAG, "Previous command not yet fetched by the bus controller"); return false; } // A new command supersedes any half-open target the door was still travelling to. - if (command.clears_target) - this->clear_target_(); + this->clear_target_(); this->next_command_ = &command; this->command_queued_at_ = millis(); return true; } -bool HoermannHcp::open_door() { return this->queue_command_(COMMAND_OPEN); } -bool HoermannHcp::close_door() { return this->queue_command_(COMMAND_CLOSE); } -bool HoermannHcp::impulse_door() { return this->queue_command_(COMMAND_IMPULSE); } -bool HoermannHcp::vent_door() { return this->queue_command_(COMMAND_VENT); } -bool HoermannHcp::half_open_door() { return this->queue_command_(COMMAND_HALF_OPEN); } -bool HoermannHcp::toggle_light() { - if (this->light_toggles_in_flight_ >= MAX_LIGHT_TOGGLES_IN_FLIGHT) { - ESP_LOGW(TAG, "Too many lamp toggles are still waiting to be confirmed, dropping this one"); +bool HoermannHcp::is_moving_or_starting_() const { return this->starting_ || is_moving(this->door_state_); } + +bool HoermannHcp::command_door_(const HoermannHcpCommand &command) { + // Only stopped, so it is never reversed at speed. take_command_() drops one queued before the door started the same + // way. + if (this->is_moving_or_starting_()) + return this->stop_door(); + return this->queue_command_(command); +} + +bool HoermannHcp::open_door() { return this->command_door_(COMMAND_OPEN); } +bool HoermannHcp::close_door() { return this->command_door_(COMMAND_CLOSE); } +bool HoermannHcp::impulse_door() { return this->command_door_(COMMAND_IMPULSE); } +bool HoermannHcp::vent_door() { return this->command_door_(COMMAND_VENT); } +bool HoermannHcp::half_open_door() { return this->command_door_(COMMAND_HALF_OPEN); } +bool HoermannHcp::stop_door() { + this->clear_target_(); + // A stop outranks whatever is still waiting, so a door at rest does not start after the user pressed stop. + if (this->next_command_ != nullptr && this->next_command_ != &COMMAND_STOP) { + ESP_LOGD(TAG, "Stop cancels the unfetched '%s' command", this->next_command_->name); + this->next_command_ = nullptr; + } + if (!this->is_moving_or_starting_()) + return true; + if (!this->valid_) { + ESP_LOGW(TAG, "Not connected to the bus controller, dropping 'stop' command"); return false; } - if (!this->queue_command_(COMMAND_TOGGLE_LAMP)) - return false; - this->light_toggles_in_flight_++; - return true; -} -bool HoermannHcp::is_light_toggle_pending_() const { return this->next_command_ == &COMMAND_TOGGLE_LAMP; } - -uint8_t HoermannHcp::unsent_light_toggles_() const { - return this->is_light_toggle_pending_() && this->command_written_at_ == 0 ? 1 : 0; -} - -bool HoermannHcp::cancel_light_toggle() { - // Once the pressed value has been presented the key press is already on the wire, so only an untouched - // command can be withdrawn. - if (!this->is_light_toggle_pending_() || this->command_written_at_ != 0) - return false; - ESP_LOGD(TAG, "Cancelling '%s' command the controller had not fetched", this->next_command_->name); - this->drop_command_(); - return true; -} - -bool HoermannHcp::stop_door() { - if (!is_moving(this->door_state_)) { - this->clear_target_(); + if (this->next_command_ == &COMMAND_STOP) + return true; + if (this->stop_sent_ && millis() - this->last_stop_at_ < STOP_LOCK_MS) { + ESP_LOGD(TAG, "Door already stopping, ignoring stop"); return true; } - // On success queue_command_() clears the target; on refusal it stays armed so the next position retries. - return this->queue_command_(COMMAND_IMPULSE); + this->next_command_ = &COMMAND_STOP; + this->command_queued_at_ = millis(); + return true; } bool HoermannHcp::set_position(float position) { @@ -563,8 +622,8 @@ bool HoermannHcp::set_position(float position) { return this->close_door(); if (position >= OPEN_POSITION_THRESHOLD) return this->open_door(); - // Asking the door to travel to where it already is means stopping it. - if (position == this->current_position_) + // Asking the door to travel to where it already is, or anywhere while it moves, means stopping it. + if (position == this->current_position_ || this->is_moving_or_starting_()) return this->stop_door(); // The door itself has no notion of a target, so it is started in the right direction and stopped on the way. @@ -574,8 +633,6 @@ bool HoermannHcp::set_position(float position) { this->target_position_ = position; this->target_queued_at_ = millis(); this->target_direction_ = opening ? DoorState::OPENING : DoorState::CLOSING; - // A door already travelling that way is on its way; one moving the other way has to turn around first. - this->target_started_ = this->door_state_ == this->target_direction_; return true; } @@ -596,9 +653,8 @@ void HoermannHcp::set_valid_(bool valid) { ESP_LOGW(TAG, "Bus controller connection lost (no request for %" PRIu32 "ms)", millis() - this->last_response_); // Drop what the controller never fetched, so it neither blocks later commands nor fires on reconnect. this->drop_command_(); - // The door cannot be watched while the bus is quiet, so a target left armed would stop it long afterwards. - this->clear_target_(); - this->forget_light_toggles_(); + this->starting_ = false; + this->clear_light_request_(); // The lamp can be switched at the door while the bus is quiet, so what was last read is no longer trusted. this->set_light_seen_(false); // The same holds for the door. The next broadcast is decoded even if it repeats the last one. @@ -608,38 +664,8 @@ void HoermannHcp::set_valid_(bool valid) { } void HoermannHcp::drop_command_() { - const bool was_light_toggle = this->is_light_toggle_pending_(); - // Cleared first so the settling below no longer counts this command among the toggles still to be sent. this->next_command_ = nullptr; - this->command_written_at_ = 0; - if (was_light_toggle) { - // A lamp toggle says nothing about where the door was going, so it leaves the target alone. - this->light_toggle_settled_(); - } else { - this->clear_target_(); - } -} - -void HoermannHcp::light_toggle_settled_() { - if (this->light_toggles_in_flight_ == 0) - return; - this->light_toggles_in_flight_--; - // Only a toggle the door has been shown can still be confirmed, so unsent ones leave nothing to wait for. - if (this->light_toggles_in_flight_ == this->unsent_light_toggles_()) - this->light_toggle_released_at_ = 0; - // The light was showing where the lamp was heading, so it has to be told to look again. - this->changed_ = true; -} - -void HoermannHcp::forget_light_toggles_() { - // Nothing outstanding must always mean nothing to wait for, or the watchdog below would fire for ever. - this->light_toggle_released_at_ = 0; - // A toggle the door has not been shown yet is still going to fire, so it keeps counting. - const uint8_t unsent = this->unsent_light_toggles_(); - if (this->light_toggles_in_flight_ == unsent) - return; - this->light_toggles_in_flight_ = unsent; - this->changed_ = true; + this->clear_target_(); } void HoermannHcp::set_door_state_(DoorState state) { @@ -648,10 +674,21 @@ void HoermannHcp::set_door_state_(DoorState state) { this->door_state_seen_ = true; this->changed_ = true; } + // Only moving or the command's destination answers it, even as a first report that changes nothing. + if (this->starting_ && (is_moving(state) || at_destination(*this->start_command_, state))) { + // A stop held for the start is due now, so its fetch deadline starts here. + if (this->next_command_ == &COMMAND_STOP) + this->command_queued_at_ = millis(); + this->starting_ = false; + } if (this->door_state_ == state) return; this->door_state_ = state; this->changed_ = true; + if (!is_moving(state)) { + // A door at rest cannot be restarted by a second stop, as stop_door() sends nothing then. + this->stop_sent_ = false; + } this->update_current_position_(); if (!this->has_target_()) return; @@ -688,19 +725,48 @@ void HoermannHcp::set_light_on_(bool on) { return; this->light_on_ = on; this->changed_ = true; - if (this->light_toggles_in_flight_ <= this->unsent_light_toggles_()) { - // The door has not been shown a toggle that could explain this, so the lamp was switched at the door. + if (!this->light_requested_) { ESP_LOGD(TAG, "Lamp %s at the door", ONOFF(on)); return; } - // The door acted, so one of the toggles it has seen has arrived. Any others still count. - this->light_toggle_settled_(); + if (on == this->light_target_) { + this->clear_light_request_(); + return; + } + // Switched away from the target while the command was out: send again. + this->light_command_sent_ = false; + this->light_since_ = millis(); +} + +bool HoermannHcp::set_light(bool on) { + // A known lamp implies a live connection. + if (!this->light_seen_) + return false; + this->light_target_ = on; + const bool was_requested = this->light_requested_; + // A sent command may still switch it away. + this->light_requested_ = on != this->light_on_ || this->light_command_sent_; + // The deadline belongs to the request, so more taps cannot keep it alive. + if (!was_requested) + this->light_since_ = millis(); + this->changed_ = true; + return true; +} + +void HoermannHcp::clear_light_request_() { + if (!this->light_requested_) + return; + this->light_requested_ = false; + this->light_command_sent_ = false; + this->changed_ = true; } void HoermannHcp::set_light_seen_(bool seen) { if (this->light_seen_ == seen) return; this->light_seen_ = seen; + if (!seen) + this->clear_light_request_(); // A resting door changes nothing else, so without this the light would never hear about it. this->changed_ = true; } diff --git a/esphome/components/hoermann_hcp/hoermann_hcp.h b/esphome/components/hoermann_hcp/hoermann_hcp.h index 9ebb717d0c4..c16a0890ef9 100644 --- a/esphome/components/hoermann_hcp/hoermann_hcp.h +++ b/esphome/components/hoermann_hcp/hoermann_hcp.h @@ -47,17 +47,11 @@ enum class IdentityPhase : uint8_t { }; #endif -// A HCP command is a simulated key press: the pressed value is presented to the bus controller, then after a -// short delay the released value. Each half also carries a second register, which names the buttons that do -// not fit into the first. +// Sent once, in a single status answer, as Hoermann's own bus accessory does. struct HoermannHcpCommand { const char *name; - uint16_t pressed_value; - uint16_t released_value; - uint16_t pressed_value_2{0x0000}; - uint16_t released_value_2{0x0000}; - // A door command supersedes a half-open target; the lamp has no bearing on where the door is going. - bool clears_target{true}; + uint16_t value; + uint16_t value_2{0x0000}; }; class HoermannHcp : public PollingComponent, public modbus::ModbusServerDevice { @@ -92,7 +86,8 @@ class HoermannHcp : public PollingComponent, public modbus::ModbusServerDevice { bool half_open_door(); bool stop_door(); bool set_position(float position); - bool toggle_light(); + // False while the door has not reported the lamp. + bool set_light(bool on); DoorState get_door_state() const { return this->door_state_; } // False until a broadcast has carried a state the door is known to report. Bus traffic alone makes the @@ -104,29 +99,20 @@ class HoermannHcp : public PollingComponent, public modbus::ModbusServerDevice { // False until a broadcast has actually carried the lamp register. Bus traffic alone makes the connection // valid without saying anything about the lamp, so is_light_on() would still be its default. bool is_light_known() const { return this->light_seen_; } - // Where the lamp ends up once every toggle on its way has landed, each of which inverts it. Until then the - // lamp still reads as its old self, so this is what a request has to be judged against. - bool is_light_heading_on() const { return this->light_on_ != (this->light_toggles_in_flight_ % 2 != 0); } - // Drops a lamp toggle the controller has not started reading, so a reversing request cancels it outright - // instead of fighting it. Returns false if there is nothing to cancel. - bool cancel_light_toggle(); + // The requested state while switching, else the reported one. + bool is_light_heading_on() const { return this->light_requested_ ? this->light_target_ : this->light_on_; } protected: - // True while a lamp toggle is queued but not yet fetched, so the lamp is about to invert. - bool is_light_toggle_pending_() const; - // Toggles the door has not been shown yet, which is at most the one still waiting in the command slot. - uint8_t unsent_light_toggles_() const; void record_response_(); + bool command_door_(const HoermannHcpCommand &command); // Returns false when the bus controller has not fetched the previous command yet. bool queue_command_(const HoermannHcpCommand &command); - // Throws away the pending command, taking any armed target with it unless the command was the lamp toggle. void drop_command_(); - // One outstanding toggle reached the lamp, was withdrawn, or was thrown away. - void light_toggle_settled_(); - // Stops expecting the toggles the door has already been shown to reach the lamp. - void forget_light_toggles_(); - // Appends the two key-press registers and advances the pending command's press/release state. + void clear_light_request_(); void push_command_registers_(modbus::RegisterValues ®isters); + // Decide at the fetch what goes into a status answer, against the door as it stands then. + const HoermannHcpCommand *take_command_(); + const HoermannHcpCommand *take_light_command_(); void on_position_reg_(uint16_t value); void on_state_reg_(uint16_t value); void on_light_reg_(uint16_t value); @@ -151,6 +137,7 @@ class HoermannHcp : public PollingComponent, public modbus::ModbusServerDevice { // Recomputes the reported position from position_raw_ and the current door state. void update_current_position_(); bool has_target_() const { return this->target_position_ != 0.0f; } + bool is_moving_or_starting_() const; void clear_target_(); void set_light_on_(bool on); void set_light_seen_(bool seen); @@ -161,21 +148,24 @@ class HoermannHcp : public PollingComponent, public modbus::ModbusServerDevice { // Position the door was told to travel to; 0.0 means no target is armed. float target_position_{0.0f}; - // Pending command / key-press state machine. const HoermannHcpCommand *next_command_{nullptr}; + const HoermannHcpCommand *start_command_{nullptr}; uint32_t command_queued_at_{0}; // Separate from command_queued_at_ so an unrelated command cannot extend the target's start deadline. uint32_t target_queued_at_{0}; - uint32_t command_written_at_{0}; uint32_t last_response_{0}; - // When the door was last handed a lamp key press. It reports the lamp a moment later, so this bounds the - // wait. Queueing another toggle deliberately leaves it alone, so the one already sent keeps its deadline. - uint32_t light_toggle_released_at_{0}; + // Start of the wait for the fetch, then for the report. + uint32_t light_since_{0}; + uint32_t last_stop_at_{0}; + uint32_t start_fetched_at_{0}; + bool stop_sent_{false}; + // A door command was fetched and the door has not answered it by moving or reaching its destination yet. + bool starting_{false}; - // A command is "pressed" for this long before its end value is sent. - uint16_t key_press_delay_ms_{100}; // Drop the "connected" flag if the bus controller has not polled us for this long. uint16_t connection_timeout_ms_{2000}; + // A chosen margin for a door to report moving after a fetched command. + uint16_t start_window_ms_{5000}; // The state starts on a value the bus controller never reports, so the first broadcast is decoded even when // it reads 0x0000. uint16_t prev_state_reg_{0xFFFF}; @@ -184,19 +174,22 @@ class HoermannHcp : public PollingComponent, public modbus::ModbusServerDevice { uint16_t command_reg_value_{0}; DoorState door_state_{DoorState::CLOSED}; - // Direction the door was started in for the current target. A target armed while the door is still travelling - // the other way must not be judged by the reported direction until the door has turned around. + // Direction the door was started in for the current target, judged only once the door reports moving that way. DoorState target_direction_{DoorState::STOPPED}; // Position as reported by the bus controller, 0..200 across the full travel. uint8_t position_raw_{0}; - uint8_t light_toggles_in_flight_{0}; bool target_started_{false}; bool valid_{false}; bool changed_{false}; bool light_on_{false}; bool light_seen_{false}; + bool light_requested_{false}; + bool light_command_sent_{false}; + bool light_target_{false}; bool door_state_seen_{false}; bool short_broadcast_logged_{false}; + // Only the read half right after a 0x17 write carries a command, so a second read without a new write does not. + bool status_poll_pending_{false}; #ifdef USE_HOERMANN_HCP_IDENTITY uint32_t identity_asked_at_{0}; diff --git a/esphome/components/hoermann_hcp/light/hoermann_hcp_light.cpp b/esphome/components/hoermann_hcp/light/hoermann_hcp_light.cpp index d3d784928d3..6195c5d63d8 100644 --- a/esphome/components/hoermann_hcp/light/hoermann_hcp_light.cpp +++ b/esphome/components/hoermann_hcp/light/hoermann_hcp_light.cpp @@ -4,7 +4,7 @@ namespace esphome::hoermann_hcp { -static const char *const TAG = "hoermann_hcp.light"; +ESPHOME_LOG_TAG(TAG, "hoermann_hcp.light"); light::LightTraits HoermannHcpLight::get_traits() { auto traits = light::LightTraits(); @@ -37,15 +37,10 @@ void HoermannHcpLight::write_state(light::LightState *state) { if (restored) { ESP_LOGD(TAG, "Ignoring the restored state, the door decides what the lamp is doing"); } else if (published != binary) { - if (!this->parent_->is_light_known()) { - // Commanding a lamp that has not been read could switch off one that is already on. - ESP_LOGW(TAG, "Door has not reported the lamp yet, ignoring the requested state"); - } else if (this->parent_->cancel_light_toggle() || this->parent_->toggle_light()) { - // A toggle the controller has not fetched is withdrawn outright rather than fought with a second one. + // Refused until the door has reported the lamp, as commanding an unread one could switch off a lit lamp. + if (this->parent_->set_light(binary)) return; - } else { - ESP_LOGW(TAG, "Light command was not accepted by the door"); - } + ESP_LOGW(TAG, "Door has not reported the lamp yet, ignoring the requested state"); } // Nothing was sent, so the entity has to go back to showing the lamp rather than the request. this->publish_lamp_state_(heading_on); diff --git a/esphome/components/hoermann_hcp/text_sensor/hoermann_hcp_text_sensor.cpp b/esphome/components/hoermann_hcp/text_sensor/hoermann_hcp_text_sensor.cpp index 765a80ae6f3..cc8d15d7234 100644 --- a/esphome/components/hoermann_hcp/text_sensor/hoermann_hcp_text_sensor.cpp +++ b/esphome/components/hoermann_hcp/text_sensor/hoermann_hcp_text_sensor.cpp @@ -7,7 +7,7 @@ namespace esphome::hoermann_hcp { -static const char *const TAG = "hoermann_hcp.text_sensor"; +ESPHOME_LOG_TAG(TAG, "hoermann_hcp.text_sensor"); // Indexed by DoorState. Each fits the 15 characters std::string keeps inline, so publishing never allocates. PROGMEM_STRING_TABLE(DoorStateStrings, "Open", "Opening", "Closed", "Closing", "Half open", "Moving to vent", diff --git a/esphome/components/homeassistant/__init__.py b/esphome/components/homeassistant/__init__.py index 1b66842f1eb..3851bc3c0bc 100644 --- a/esphome/components/homeassistant/__init__.py +++ b/esphome/components/homeassistant/__init__.py @@ -2,11 +2,13 @@ from collections.abc import Callable, Iterable import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_INTERNAL +from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_ID, CONF_INTERNAL from esphome.cpp_generator import MockObj +import esphome.final_validate as fv from esphome.types import ConfigType CODEOWNERS = ["@OttoWinter", "@esphome/core"] +DOMAIN = "homeassistant" homeassistant_ns = cg.esphome_ns.namespace("homeassistant") @@ -14,10 +16,12 @@ def validate_entity_domain( platform: str, supported_domains: Iterable[str] ) -> Callable[[ConfigType], ConfigType]: def validator(config: ConfigType) -> ConfigType: - domain = config[CONF_ENTITY_ID].split(".", 1)[0] - if domain not in supported_domains: + # A wizard input supplies the entity ID later; the wizard checks its domains + if (entity_id := config.get(CONF_ENTITY_ID)) is None: + return config + if entity_id.split(".", 1)[0] not in supported_domains: raise cv.Invalid( - f"Entity ID {config[CONF_ENTITY_ID]} is not supported by the {platform} platform." + f"Entity ID {entity_id} is not supported by the {platform} platform." ) return config @@ -26,7 +30,7 @@ def validate_entity_domain( HOME_ASSISTANT_IMPORT_SCHEMA = cv.Schema( { - cv.Required(CONF_ENTITY_ID): cv.entity_id, + cv.Optional(CONF_ENTITY_ID): cv.entity_id, cv.Optional(CONF_ATTRIBUTE): cv.string, cv.Optional(CONF_INTERNAL, default=True): cv.boolean, } @@ -34,14 +38,35 @@ HOME_ASSISTANT_IMPORT_SCHEMA = cv.Schema( HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA = cv.Schema( { - cv.Required(CONF_ENTITY_ID): cv.entity_id, + cv.Optional(CONF_ENTITY_ID): cv.entity_id, cv.Optional(CONF_INTERNAL, default=True): cv.boolean, } ) +def final_validate_entity_id(config: ConfigType) -> ConfigType: + """Without an entity_id, the entity must be a wizard input, as Home Assistant then supplies it.""" + if CONF_ENTITY_ID in config: + return config + from esphome.components.api import wizard + + if config[CONF_ID].id not in wizard.wizard_input_ids( + fv.full_config.get()[wizard.API_DOMAIN] + ): + raise cv.Invalid( + f"{CONF_ENTITY_ID} is required unless this entity is a wizard input" + ) + return config + + def setup_home_assistant_entity(var: MockObj, config: ConfigType) -> None: - cg.add(var.set_entity_id(config[CONF_ENTITY_ID])) + from esphome.components.api import wizard + + if (buffer := wizard.wizard_input_buffer(config[CONF_ID])) is not None: + # The entity ID is chosen in the wizard and lives in a buffer the API owns + cg.add(var.set_entity_id(cg.RawExpression(buffer))) + else: + cg.add(var.set_entity_id(config[CONF_ENTITY_ID])) if CONF_ATTRIBUTE in config: cg.add(var.set_attribute(config[CONF_ATTRIBUTE])) cg.add_define("USE_API_HOMEASSISTANT_STATES") diff --git a/esphome/components/homeassistant/binary_sensor/__init__.py b/esphome/components/homeassistant/binary_sensor/__init__.py index 6ea17b68313..310ade27a8e 100644 --- a/esphome/components/homeassistant/binary_sensor/__init__.py +++ b/esphome/components/homeassistant/binary_sensor/__init__.py @@ -4,6 +4,7 @@ from esphome.types import ConfigType from .. import ( HOME_ASSISTANT_IMPORT_SCHEMA, + final_validate_entity_id, homeassistant_ns, setup_home_assistant_entity, ) @@ -18,6 +19,8 @@ CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(HomeassistantBinarySensor).ex HOME_ASSISTANT_IMPORT_SCHEMA ) +FINAL_VALIDATE_SCHEMA = final_validate_entity_id + async def to_code(config: ConfigType) -> None: var = await binary_sensor.new_binary_sensor(config) diff --git a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp index 735fb9f5da1..d14158399c5 100644 --- a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp +++ b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp @@ -5,7 +5,7 @@ namespace esphome::homeassistant { -static const char *const TAG = "homeassistant.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "homeassistant.binary_sensor"); void HomeassistantBinarySensor::setup() { api::global_api_server->subscribe_home_assistant_state(this->entity_id_, this->attribute_, [this](StringRef state) { diff --git a/esphome/components/homeassistant/button/__init__.py b/esphome/components/homeassistant/button/__init__.py new file mode 100644 index 00000000000..f486a841263 --- /dev/null +++ b/esphome/components/homeassistant/button/__init__.py @@ -0,0 +1,40 @@ +import esphome.codegen as cg +from esphome.components import button +import esphome.config_validation as cv +from esphome.types import ConfigType + +from .. import ( + HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA, + final_validate_entity_id, + homeassistant_ns, + setup_home_assistant_entity, + validate_entity_domain, +) + +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["api"] + +SUPPORTED_DOMAINS = [ + "button", + "input_button", +] + +HomeassistantButton = homeassistant_ns.class_( + "HomeassistantButton", button.Button, cg.Component +) + +CONFIG_SCHEMA = cv.All( + button.button_schema(HomeassistantButton) + .extend(HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA) + .extend(cv.COMPONENT_SCHEMA), + validate_entity_domain("button", SUPPORTED_DOMAINS), +) + +FINAL_VALIDATE_SCHEMA = final_validate_entity_id + + +async def to_code(config: ConfigType) -> None: + cg.add_define("USE_API_HOMEASSISTANT_SERVICES") + var = await button.new_button(config) + await cg.register_component(var, config) + setup_home_assistant_entity(var, config) diff --git a/esphome/components/homeassistant/button/homeassistant_button.cpp b/esphome/components/homeassistant/button/homeassistant_button.cpp new file mode 100644 index 00000000000..6cf1c87eaa5 --- /dev/null +++ b/esphome/components/homeassistant/button/homeassistant_button.cpp @@ -0,0 +1,54 @@ +#include "homeassistant_button.h" + +#include + +#include "esphome/components/api/api_pb2.h" +#include "esphome/components/api/api_server.h" +#include "esphome/core/log.h" +#include "esphome/core/string_ref.h" + +namespace esphome::homeassistant { + +ESPHOME_LOG_TAG(TAG, "homeassistant.button"); + +void HomeassistantButton::dump_config() { + LOG_BUTTON("", "Homeassistant Button", this); + ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_); +} + +float HomeassistantButton::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } + +void HomeassistantButton::press_action() { + if (!api::global_api_server->is_connected()) { + ESP_LOGE(TAG, "No clients connected to API server"); + return; + } + +#ifdef USE_API_WIZARD_LINKED_INPUTS + if (this->entity_id_[0] == '\0') { + ESP_LOGW(TAG, "'%s': No entity ID set yet", this->get_name().c_str()); + return; + } +#endif + + static constexpr auto SERVICE_BUTTON = StringRef::from_lit("button.press"); + static constexpr auto SERVICE_INPUT_BUTTON = StringRef::from_lit("input_button.press"); + static constexpr auto ENTITY_ID_KEY = StringRef::from_lit("entity_id"); + static constexpr char INPUT_PREFIX[] = "input_"; + + api::HomeassistantActionRequest resp; + if (strncmp(this->entity_id_, INPUT_PREFIX, sizeof(INPUT_PREFIX) - 1) == 0) { + resp.service = SERVICE_INPUT_BUTTON; + } else { + resp.service = SERVICE_BUTTON; + } + + resp.data.init(1); + auto &entity_id_kv = resp.data.emplace_back(); + entity_id_kv.key = ENTITY_ID_KEY; + entity_id_kv.value = StringRef(this->entity_id_); + + api::global_api_server->send_homeassistant_action(resp); +} + +} // namespace esphome::homeassistant diff --git a/esphome/components/homeassistant/button/homeassistant_button.h b/esphome/components/homeassistant/button/homeassistant_button.h new file mode 100644 index 00000000000..cc7bbddfefa --- /dev/null +++ b/esphome/components/homeassistant/button/homeassistant_button.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/components/button/button.h" +#include "esphome/core/component.h" + +namespace esphome::homeassistant { + +class HomeassistantButton final : public button::Button, public Component { + public: + void set_entity_id(const char *entity_id) { this->entity_id_ = entity_id; } + + void dump_config() override; + float get_setup_priority() const override; + + protected: + void press_action() override; + + const char *entity_id_{nullptr}; +}; + +} // namespace esphome::homeassistant diff --git a/esphome/components/homeassistant/number/__init__.py b/esphome/components/homeassistant/number/__init__.py index 9658d3dc84e..6288db68805 100644 --- a/esphome/components/homeassistant/number/__init__.py +++ b/esphome/components/homeassistant/number/__init__.py @@ -5,6 +5,7 @@ from esphome.types import ConfigType from .. import ( HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA, + final_validate_entity_id, homeassistant_ns, setup_home_assistant_entity, validate_entity_domain, @@ -26,6 +27,8 @@ CONFIG_SCHEMA = cv.All( validate_entity_domain("number", SUPPORTED_DOMAINS), ) +FINAL_VALIDATE_SCHEMA = final_validate_entity_id + async def to_code(config: ConfigType) -> None: cg.add_define("USE_API_HOMEASSISTANT_SERVICES") diff --git a/esphome/components/homeassistant/number/homeassistant_number.cpp b/esphome/components/homeassistant/number/homeassistant_number.cpp index 553c2466f84..df92aad38eb 100644 --- a/esphome/components/homeassistant/number/homeassistant_number.cpp +++ b/esphome/components/homeassistant/number/homeassistant_number.cpp @@ -9,7 +9,7 @@ namespace esphome::homeassistant { -static const char *const TAG = "homeassistant.number"; +ESPHOME_LOG_TAG(TAG, "homeassistant.number"); void HomeassistantNumber::state_changed_(StringRef state) { auto number_value = parse_number(state.c_str()); @@ -80,6 +80,13 @@ void HomeassistantNumber::control(float value) { return; } +#ifdef USE_API_WIZARD_LINKED_INPUTS + if (this->entity_id_[0] == '\0') { + ESP_LOGW(TAG, "'%s': No entity ID set yet", this->get_name().c_str()); + return; + } +#endif + this->publish_state(value); static constexpr auto NUMBER_SERVICE_NAME = StringRef::from_lit("number.set_value"); diff --git a/esphome/components/homeassistant/select/__init__.py b/esphome/components/homeassistant/select/__init__.py new file mode 100644 index 00000000000..e8ad0fc23b8 --- /dev/null +++ b/esphome/components/homeassistant/select/__init__.py @@ -0,0 +1,57 @@ +import esphome.codegen as cg +from esphome.components import select +import esphome.config_validation as cv +from esphome.types import ConfigType + +from .. import ( + HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA, + final_validate_entity_id, + homeassistant_ns, + setup_home_assistant_entity, + validate_entity_domain, +) + +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["api"] + +CONF_MAX_OPTIONS = "max_options" +CONF_OPTIONS_BUFFER_SIZE = "options_buffer_size" + +SUPPORTED_DOMAINS = [ + "input_select", + "select", +] + +HomeassistantSelect = homeassistant_ns.class_( + "HomeassistantSelect", select.Select, cg.Component +) + +CONFIG_SCHEMA = cv.All( + select.select_schema(HomeassistantSelect) + .extend(HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA) + .extend( + { + cv.Optional(CONF_MAX_OPTIONS, default=16): cv.int_range(min=1, max=255), + cv.Optional(CONF_OPTIONS_BUFFER_SIZE, default=256): cv.int_range( + min=2, max=4096 + ), + } + ) + .extend(cv.COMPONENT_SCHEMA), + validate_entity_domain("select", SUPPORTED_DOMAINS), +) + +FINAL_VALIDATE_SCHEMA = final_validate_entity_id + + +async def to_code(config: ConfigType) -> None: + cg.add_define("USE_API_HOMEASSISTANT_SERVICES") + # Options come from Home Assistant at runtime, into storage reserved during setup + var = await select.new_select( + config, + config[CONF_MAX_OPTIONS], + config[CONF_OPTIONS_BUFFER_SIZE], + options=[], + ) + await cg.register_component(var, config) + setup_home_assistant_entity(var, config) diff --git a/esphome/components/homeassistant/select/homeassistant_select.cpp b/esphome/components/homeassistant/select/homeassistant_select.cpp new file mode 100644 index 00000000000..189a6b1f376 --- /dev/null +++ b/esphome/components/homeassistant/select/homeassistant_select.cpp @@ -0,0 +1,265 @@ +#include "homeassistant_select.h" + +#include + +#include "esphome/components/api/api_pb2.h" +#include "esphome/components/api/api_server.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/string_ref.h" + +namespace esphome::homeassistant { + +ESPHOME_LOG_TAG(TAG, "homeassistant.select"); + +namespace { + +/// Emit the UTF-8 encoding of a code point. Rejects NUL, which would end the option early. +template bool emit_utf8(uint32_t code_point, C &on_char) { + if (code_point == 0 || code_point > 0x10FFFF) + return false; + if (code_point < 0x80) { + on_char(static_cast(code_point)); + } else if (code_point < 0x800) { + on_char(static_cast(0xC0 | (code_point >> 6))); + on_char(static_cast(0x80 | (code_point & 0x3F))); + } else if (code_point < 0x10000) { + on_char(static_cast(0xE0 | (code_point >> 12))); + on_char(static_cast(0x80 | ((code_point >> 6) & 0x3F))); + on_char(static_cast(0x80 | (code_point & 0x3F))); + } else { + on_char(static_cast(0xF0 | (code_point >> 18))); + on_char(static_cast(0x80 | ((code_point >> 12) & 0x3F))); + on_char(static_cast(0x80 | ((code_point >> 6) & 0x3F))); + on_char(static_cast(0x80 | (code_point & 0x3F))); + } + return true; +} + +/** Walk a list attribute as Home Assistant sends it: the Python repr of a list of strings, such as + * `['Low', "It's high", 'caf\xe9']`. + * + * Calls on_char for each decoded byte of an option and on_option at the end of each option. Returns false + * on malformed input; the callbacks may already have run by then. + */ +template bool parse_option_list(StringRef src, C &&on_char, O &&on_option) { + const char *pos = src.c_str(); + const char *const end = pos + src.size(); + auto skip_spaces = [&]() { + while (pos < end && *pos == ' ') + pos++; + }; + + skip_spaces(); + if (pos == end || *pos != '[') + return false; + pos++; + skip_spaces(); + if (pos < end && *pos == ']') { + pos++; + } else { + while (true) { + skip_spaces(); + if (pos == end || (*pos != '\'' && *pos != '"')) + return false; + const char quote = *pos++; + while (true) { + if (pos == end) + return false; + const char c = *pos++; + if (c == quote) + break; + if (c != '\\') { + on_char(c); + continue; + } + if (pos == end) + return false; + const char escape = *pos++; + switch (escape) { + case '\\': + case '\'': + case '"': + on_char(escape); + break; + case 'n': + on_char('\n'); + break; + case 'r': + on_char('\r'); + break; + case 't': + on_char('\t'); + break; + case 'x': + case 'u': + case 'U': { + const size_t digits = escape == 'x' ? 2 : (escape == 'u' ? 4 : 8); + if (static_cast(end - pos) < digits) + return false; + auto code_point = parse_hex(pos, digits); + if (!code_point.has_value() || !emit_utf8(*code_point, on_char)) + return false; + pos += digits; + break; + } + default: + return false; + } + } + on_option(); + skip_spaces(); + if (pos == end) + return false; + const char separator = *pos++; + if (separator == ']') + break; + if (separator != ',') + return false; + } + } + skip_spaces(); + return pos == end; +} + +} // namespace + +void HomeassistantSelect::setup() { + this->options_buffer_ = std::make_unique(this->options_buffer_size_); + this->option_list_ = std::make_unique(this->max_options_); + + // Subscribe to the options first: Home Assistant answers subscriptions in order, so the options are + // known by the time the first state arrives. + api::global_api_server->subscribe_home_assistant_state( + this->entity_id_, "options", [this](StringRef options) { this->options_changed_(options); }); + api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullptr, + [this](StringRef state) { this->state_changed_(state); }); +} + +void HomeassistantSelect::options_changed_(StringRef options) { + // First pass: validate and measure the new options, and look for the active option among them, before + // anything is overwritten. A list that does not fit leaves the current options in place. + const char *active = this->has_state() ? this->option_at(this->active_index_) : nullptr; + size_t count = 0; + size_t bytes = 0; + size_t match_len = 0; + bool matching = active != nullptr; + optional new_active; + bool valid = parse_option_list( + options, + [&](char c) { + bytes++; + if (matching && active[match_len] == c) { + match_len++; + } else { + matching = false; + } + }, + [&]() { + bytes++; // NUL terminator + if (matching && active[match_len] == '\0' && !new_active.has_value()) + new_active = count; + count++; + match_len = 0; + matching = active != nullptr; + }); + if (!valid) { + ESP_LOGW(TAG, "'%s': Can't parse options %s", this->entity_id_, options.c_str()); + return; + } + if (count > this->max_options_) { + ESP_LOGE(TAG, "'%s': %zu options exceed max_options (%u)", this->entity_id_, count, this->max_options_); + return; + } + if (bytes > this->options_buffer_size_) { + ESP_LOGE(TAG, "'%s': Options need %zu bytes, more than options_buffer_size (%u)", this->entity_id_, bytes, + this->options_buffer_size_); + return; + } + + // Second pass: the input is known to be valid and to fit, so write it out + char *buffer = this->options_buffer_.get(); + size_t offset = 0; + size_t option_start = 0; + size_t index = 0; + parse_option_list( + options, [&](char c) { buffer[offset++] = c; }, + [&]() { + buffer[offset++] = '\0'; + this->option_list_[index++] = buffer + option_start; + option_start = offset; + }); + // The traits only point at the list, which this select owns; set_options() would copy it to the heap + this->traits.set_options_static(this->option_list_.get(), count); + + // The active option may have moved to another index, or may be gone + if (new_active.has_value()) { + this->active_index_ = *new_active; + } else { + this->set_has_state(false); + } + ESP_LOGD(TAG, "'%s': Got %zu options", this->entity_id_, count); +} + +void HomeassistantSelect::state_changed_(StringRef state) { + auto index = this->index_of(state.c_str(), state.size()); + if (!index.has_value()) { + ESP_LOGW(TAG, "'%s': State '%s' is not one of the options", this->entity_id_, state.c_str()); + return; + } + if (this->has_state() && this->active_index_ == *index) + return; + ESP_LOGD(TAG, "'%s': Got state '%s'", this->entity_id_, state.c_str()); + this->publish_state(*index); +} + +void HomeassistantSelect::dump_config() { + LOG_SELECT("", "Homeassistant Select", this); + ESP_LOGCONFIG(TAG, + " Entity ID: '%s'\n" + " Max options: %u\n" + " Options buffer size: %u", + this->entity_id_, this->max_options_, this->options_buffer_size_); +} + +float HomeassistantSelect::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } + +void HomeassistantSelect::control(size_t index) { + if (!api::global_api_server->is_connected()) { + ESP_LOGE(TAG, "No clients connected to API server"); + return; + } + +#ifdef USE_API_WIZARD_LINKED_INPUTS + if (this->entity_id_[0] == '\0') { + ESP_LOGW(TAG, "'%s': No entity ID set yet", this->get_name().c_str()); + return; + } +#endif + + static constexpr auto SERVICE_SELECT = StringRef::from_lit("select.select_option"); + static constexpr auto SERVICE_INPUT_SELECT = StringRef::from_lit("input_select.select_option"); + static constexpr auto ENTITY_ID_KEY = StringRef::from_lit("entity_id"); + static constexpr auto OPTION_KEY = StringRef::from_lit("option"); + static constexpr char INPUT_PREFIX[] = "input_"; + + api::HomeassistantActionRequest resp; + if (strncmp(this->entity_id_, INPUT_PREFIX, sizeof(INPUT_PREFIX) - 1) == 0) { + resp.service = SERVICE_INPUT_SELECT; + } else { + resp.service = SERVICE_SELECT; + } + + resp.data.init(2); + auto &entity_id = resp.data.emplace_back(); + entity_id.key = ENTITY_ID_KEY; + entity_id.value = StringRef(this->entity_id_); + + auto &option = resp.data.emplace_back(); + option.key = OPTION_KEY; + option.value = StringRef(this->option_at(index)); + + api::global_api_server->send_homeassistant_action(resp); +} + +} // namespace esphome::homeassistant diff --git a/esphome/components/homeassistant/select/homeassistant_select.h b/esphome/components/homeassistant/select/homeassistant_select.h new file mode 100644 index 00000000000..843e07e60ef --- /dev/null +++ b/esphome/components/homeassistant/select/homeassistant_select.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +#include "esphome/components/select/select.h" +#include "esphome/core/component.h" +#include "esphome/core/string_ref.h" + +namespace esphome::homeassistant { + +/** Mirrors a Home Assistant `select` or `input_select` entity. + * + * The options are read from the entity's `options` attribute at runtime. They are stored in a buffer of + * `options_buffer_size` bytes and a list of at most `max_options` entries, both reserved in setup(), so a + * change of options never allocates. + */ +class HomeassistantSelect final : public select::Select, public Component { + public: + HomeassistantSelect(uint8_t max_options, uint16_t options_buffer_size) + : options_buffer_size_(options_buffer_size), max_options_(max_options) {} + + void set_entity_id(const char *entity_id) { this->entity_id_ = entity_id; } + + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + + protected: + void options_changed_(StringRef options); + void state_changed_(StringRef state); + + void control(size_t index) override; + + const char *entity_id_{nullptr}; + + private: + // The traits point at option_list_, whose entries point into options_buffer_; both are sized once in + // setup() and must match options_buffer_size_ and max_options_ + std::unique_ptr options_buffer_; + std::unique_ptr option_list_; + uint16_t options_buffer_size_; + uint8_t max_options_; +}; + +} // namespace esphome::homeassistant diff --git a/esphome/components/homeassistant/sensor/__init__.py b/esphome/components/homeassistant/sensor/__init__.py index abee957fda3..1417c57bb54 100644 --- a/esphome/components/homeassistant/sensor/__init__.py +++ b/esphome/components/homeassistant/sensor/__init__.py @@ -4,6 +4,7 @@ from esphome.types import ConfigType from .. import ( HOME_ASSISTANT_IMPORT_SCHEMA, + final_validate_entity_id, homeassistant_ns, setup_home_assistant_entity, ) @@ -18,6 +19,8 @@ CONFIG_SCHEMA = sensor.sensor_schema(HomeassistantSensor, accuracy_decimals=1).e HOME_ASSISTANT_IMPORT_SCHEMA ) +FINAL_VALIDATE_SCHEMA = final_validate_entity_id + async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) diff --git a/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp b/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp index b79a56953ae..8256dad484f 100644 --- a/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp +++ b/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp @@ -5,7 +5,7 @@ namespace esphome::homeassistant { -static const char *const TAG = "homeassistant.sensor"; +ESPHOME_LOG_TAG(TAG, "homeassistant.sensor"); void HomeassistantSensor::setup() { api::global_api_server->subscribe_home_assistant_state(this->entity_id_, this->attribute_, [this](StringRef state) { diff --git a/esphome/components/homeassistant/switch/__init__.py b/esphome/components/homeassistant/switch/__init__.py index 55854cd6591..495779b6ed3 100644 --- a/esphome/components/homeassistant/switch/__init__.py +++ b/esphome/components/homeassistant/switch/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType from .. import ( HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA, + final_validate_entity_id, homeassistant_ns, setup_home_assistant_entity, validate_entity_domain, @@ -36,6 +37,8 @@ CONFIG_SCHEMA = cv.All( validate_entity_domain("switch", SUPPORTED_DOMAINS), ) +FINAL_VALIDATE_SCHEMA = final_validate_entity_id + async def to_code(config: ConfigType) -> None: cg.add_define("USE_API_HOMEASSISTANT_SERVICES") diff --git a/esphome/components/homeassistant/switch/homeassistant_switch.cpp b/esphome/components/homeassistant/switch/homeassistant_switch.cpp index 8a4ea19f2e5..841f16df200 100644 --- a/esphome/components/homeassistant/switch/homeassistant_switch.cpp +++ b/esphome/components/homeassistant/switch/homeassistant_switch.cpp @@ -5,7 +5,7 @@ namespace esphome::homeassistant { -static const char *const TAG = "homeassistant.switch"; +ESPHOME_LOG_TAG(TAG, "homeassistant.switch"); using namespace esphome::switch_; @@ -40,6 +40,13 @@ void HomeassistantSwitch::write_state(bool state) { return; } +#ifdef USE_API_WIZARD_LINKED_INPUTS + if (this->entity_id_[0] == '\0') { + ESP_LOGW(TAG, "'%s': No entity ID set yet", this->get_name().c_str()); + return; + } +#endif + static constexpr auto SERVICE_ON = StringRef::from_lit("homeassistant.turn_on"); static constexpr auto SERVICE_OFF = StringRef::from_lit("homeassistant.turn_off"); static constexpr auto ENTITY_ID_KEY = StringRef::from_lit("entity_id"); diff --git a/esphome/components/homeassistant/text/__init__.py b/esphome/components/homeassistant/text/__init__.py new file mode 100644 index 00000000000..67377e02d05 --- /dev/null +++ b/esphome/components/homeassistant/text/__init__.py @@ -0,0 +1,40 @@ +import esphome.codegen as cg +from esphome.components import text +import esphome.config_validation as cv +from esphome.types import ConfigType + +from .. import ( + HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA, + final_validate_entity_id, + homeassistant_ns, + setup_home_assistant_entity, + validate_entity_domain, +) + +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["api"] + +SUPPORTED_DOMAINS = [ + "input_text", + "text", +] + +HomeassistantText = homeassistant_ns.class_( + "HomeassistantText", text.Text, cg.Component +) + +CONFIG_SCHEMA = cv.All( + text.text_schema(HomeassistantText, mode="TEXT") + .extend(HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA) + .extend(cv.COMPONENT_SCHEMA), + validate_entity_domain("text", SUPPORTED_DOMAINS), +) + +FINAL_VALIDATE_SCHEMA = final_validate_entity_id + + +async def to_code(config: ConfigType) -> None: + cg.add_define("USE_API_HOMEASSISTANT_SERVICES") + var = await text.new_text(config) + await cg.register_component(var, config) + setup_home_assistant_entity(var, config) diff --git a/esphome/components/homeassistant/text/homeassistant_text.cpp b/esphome/components/homeassistant/text/homeassistant_text.cpp new file mode 100644 index 00000000000..7155f6ee189 --- /dev/null +++ b/esphome/components/homeassistant/text/homeassistant_text.cpp @@ -0,0 +1,114 @@ +#include "homeassistant_text.h" + +#include + +#include "esphome/components/api/api_pb2.h" +#include "esphome/components/api/api_server.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/string_ref.h" + +namespace esphome::homeassistant { + +ESPHOME_LOG_TAG(TAG, "homeassistant.text"); + +void HomeassistantText::state_changed_(StringRef state) { + if (state == this->state) { + return; + } + ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_, state.c_str()); + this->publish_state(state.c_str(), state.size()); +} + +void HomeassistantText::min_retrieved_(StringRef min) { + auto min_value = parse_number(min.c_str()); + if (!min_value.has_value()) { + ESP_LOGE(TAG, "'%s': Can't convert 'min' value '%s' to number!", this->entity_id_, min.c_str()); + return; + } + ESP_LOGD(TAG, "'%s': Min retrieved: %s", this->entity_id_, min.c_str()); + this->traits.set_min_length(min_value.value()); +} + +void HomeassistantText::max_retrieved_(StringRef max) { + auto max_value = parse_number(max.c_str()); + if (!max_value.has_value()) { + ESP_LOGE(TAG, "'%s': Can't convert 'max' value '%s' to number!", this->entity_id_, max.c_str()); + return; + } + ESP_LOGD(TAG, "'%s': Max retrieved: %s", this->entity_id_, max.c_str()); + this->traits.set_max_length(max_value.value()); +} + +void HomeassistantText::mode_retrieved_(StringRef mode) { + if (mode == "text") { + this->traits.set_mode(text::TEXT_MODE_TEXT); + } else if (mode == "password") { + this->traits.set_mode(text::TEXT_MODE_PASSWORD); + } else { + ESP_LOGW(TAG, "'%s': Unknown 'mode' value '%s'", this->entity_id_, mode.c_str()); + return; + } + ESP_LOGD(TAG, "'%s': Mode retrieved: %s", this->entity_id_, mode.c_str()); +} + +void HomeassistantText::setup() { + api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullptr, + [this](StringRef state) { this->state_changed_(state); }); + + api::global_api_server->get_home_assistant_state(this->entity_id_, "min", + [this](StringRef min) { this->min_retrieved_(min); }); + api::global_api_server->get_home_assistant_state(this->entity_id_, "max", + [this](StringRef max) { this->max_retrieved_(max); }); + api::global_api_server->get_home_assistant_state(this->entity_id_, "mode", + [this](StringRef mode) { this->mode_retrieved_(mode); }); + // The "pattern" attribute is not fetched: Home Assistant sends "None" when it is unset, + // and a runtime pattern would need its own buffer. +} + +void HomeassistantText::dump_config() { + LOG_TEXT("", "Homeassistant Text", this); + ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_); +} + +float HomeassistantText::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } + +void HomeassistantText::control(const std::string &value) { + if (!api::global_api_server->is_connected()) { + ESP_LOGE(TAG, "No clients connected to API server"); + return; + } + +#ifdef USE_API_WIZARD_LINKED_INPUTS + if (this->entity_id_[0] == '\0') { + ESP_LOGW(TAG, "'%s': No entity ID set yet", this->get_name().c_str()); + return; + } +#endif + + static constexpr auto SERVICE_TEXT = StringRef::from_lit("text.set_value"); + static constexpr auto SERVICE_INPUT_TEXT = StringRef::from_lit("input_text.set_value"); + static constexpr auto ENTITY_ID_KEY = StringRef::from_lit("entity_id"); + static constexpr auto VALUE_KEY = StringRef::from_lit("value"); + static constexpr char INPUT_PREFIX[] = "input_"; + + api::HomeassistantActionRequest resp; + if (strncmp(this->entity_id_, INPUT_PREFIX, sizeof(INPUT_PREFIX) - 1) == 0) { + resp.service = SERVICE_INPUT_TEXT; + } else { + resp.service = SERVICE_TEXT; + } + + resp.data.init(2); + auto &entity_id_kv = resp.data.emplace_back(); + entity_id_kv.key = ENTITY_ID_KEY; + entity_id_kv.value = StringRef(this->entity_id_); + + auto &value_kv = resp.data.emplace_back(); + value_kv.key = VALUE_KEY; + value_kv.value = StringRef(value.data(), value.size()); + + api::global_api_server->send_homeassistant_action(resp); +} + +} // namespace esphome::homeassistant diff --git a/esphome/components/homeassistant/text/homeassistant_text.h b/esphome/components/homeassistant/text/homeassistant_text.h new file mode 100644 index 00000000000..978dfdae01e --- /dev/null +++ b/esphome/components/homeassistant/text/homeassistant_text.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/components/text/text.h" +#include "esphome/core/component.h" +#include "esphome/core/string_ref.h" + +namespace esphome::homeassistant { + +class HomeassistantText final : public text::Text, public Component { + public: + void set_entity_id(const char *entity_id) { this->entity_id_ = entity_id; } + + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + + protected: + void state_changed_(StringRef state); + void min_retrieved_(StringRef min); + void max_retrieved_(StringRef max); + void mode_retrieved_(StringRef mode); + + void control(const std::string &value) override; + + const char *entity_id_{nullptr}; +}; + +} // namespace esphome::homeassistant diff --git a/esphome/components/homeassistant/text_sensor/__init__.py b/esphome/components/homeassistant/text_sensor/__init__.py index 265250c6959..f57b398111e 100644 --- a/esphome/components/homeassistant/text_sensor/__init__.py +++ b/esphome/components/homeassistant/text_sensor/__init__.py @@ -4,6 +4,7 @@ from esphome.types import ConfigType from .. import ( HOME_ASSISTANT_IMPORT_SCHEMA, + final_validate_entity_id, homeassistant_ns, setup_home_assistant_entity, ) @@ -18,6 +19,8 @@ CONFIG_SCHEMA = text_sensor.text_sensor_schema(HomeassistantTextSensor).extend( HOME_ASSISTANT_IMPORT_SCHEMA ) +FINAL_VALIDATE_SCHEMA = final_validate_entity_id + async def to_code(config: ConfigType) -> None: var = await text_sensor.new_text_sensor(config) diff --git a/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp b/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp index 40d7455ece1..853b0d9b38c 100644 --- a/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp +++ b/esphome/components/homeassistant/text_sensor/homeassistant_text_sensor.cpp @@ -5,7 +5,7 @@ namespace esphome::homeassistant { -static const char *const TAG = "homeassistant.text_sensor"; +ESPHOME_LOG_TAG(TAG, "homeassistant.text_sensor"); void HomeassistantTextSensor::setup() { api::global_api_server->subscribe_home_assistant_state(this->entity_id_, this->attribute_, [this](StringRef state) { diff --git a/esphome/components/homeassistant/time/homeassistant_time.cpp b/esphome/components/homeassistant/time/homeassistant_time.cpp index c08bd73d625..a668d66800b 100644 --- a/esphome/components/homeassistant/time/homeassistant_time.cpp +++ b/esphome/components/homeassistant/time/homeassistant_time.cpp @@ -3,7 +3,7 @@ namespace esphome::homeassistant { -static const char *const TAG = "homeassistant.time"; +ESPHOME_LOG_TAG(TAG, "homeassistant.time"); void HomeassistantTime::dump_config() { ESP_LOGCONFIG(TAG, "Home Assistant Time"); diff --git a/esphome/components/honeywell_hih_i2c/__init__.py b/esphome/components/honeywell_hih_i2c/__init__.py index 8d13fcb1528..5808cb6fc8e 100644 --- a/esphome/components/honeywell_hih_i2c/__init__.py +++ b/esphome/components/honeywell_hih_i2c/__init__.py @@ -1,3 +1,4 @@ """Support for Honeywell HumidIcon HIH""" CODEOWNERS = ["@Benichou34"] +DOMAIN = "honeywell_hih_i2c" diff --git a/esphome/components/honeywell_hih_i2c/honeywell_hih.cpp b/esphome/components/honeywell_hih_i2c/honeywell_hih.cpp index 7f075847fdc..352d3f7f01f 100644 --- a/esphome/components/honeywell_hih_i2c/honeywell_hih.cpp +++ b/esphome/components/honeywell_hih_i2c/honeywell_hih.cpp @@ -7,7 +7,9 @@ namespace esphome::honeywell_hih_i2c { -static const char *const TAG = "honeywell_hih.i2c"; +ESPHOME_LOG_TAG(TAG, "honeywell_hih.i2c"); + +static constexpr uint32_t MEASUREMENT_TIMEOUT_ID = 0; static const uint8_t REQUEST_CMD[1] = {0x00}; // Measurement Request Format static const uint16_t MAX_COUNT = 0x3FFE; // 2^14 - 2 @@ -68,13 +70,13 @@ void HoneywellHIComponent::update() { this->start_measurement_(); // The measurement cycle duration is typically 36.65 ms for temperature and humidity readings. - this->set_timeout("meas_timeout", 100, [this] { this->measurement_timeout_(); }); + this->set_timeout(MEASUREMENT_TIMEOUT_ID, 100, [this] { this->measurement_timeout_(); }); } void HoneywellHIComponent::loop() { if (this->measurement_running_ && this->is_measurement_ready_()) { this->measurement_running_ = false; - this->cancel_timeout("meas_timeout"); + this->cancel_timeout(MEASUREMENT_TIMEOUT_ID); this->read_sensor_data_(); } } diff --git a/esphome/components/honeywellabp/__init__.py b/esphome/components/honeywellabp/__init__.py index 03440d675d4..e6e1fd89156 100644 --- a/esphome/components/honeywellabp/__init__.py +++ b/esphome/components/honeywellabp/__init__.py @@ -1 +1,3 @@ """Support for Honeywell ABP""" + +DOMAIN = "honeywellabp" diff --git a/esphome/components/honeywellabp/honeywellabp.cpp b/esphome/components/honeywellabp/honeywellabp.cpp index dd86b95c787..27f3635a739 100644 --- a/esphome/components/honeywellabp/honeywellabp.cpp +++ b/esphome/components/honeywellabp/honeywellabp.cpp @@ -3,7 +3,7 @@ namespace esphome::honeywellabp { -static const char *const TAG = "honeywellabp"; +ESPHOME_LOG_TAG(TAG, "honeywellabp"); const float MIN_COUNT = 1638.4; // 1638 counts (10% of 2^14 counts or 0x0666) const float MAX_COUNT = 14745.6; // 14745 counts (90% of 2^14 counts or 0x3999) diff --git a/esphome/components/honeywellabp2_i2c/__init__.py b/esphome/components/honeywellabp2_i2c/__init__.py index 29a910eca98..1205355bc4e 100644 --- a/esphome/components/honeywellabp2_i2c/__init__.py +++ b/esphome/components/honeywellabp2_i2c/__init__.py @@ -1,3 +1,4 @@ """Support for Honeywell ABP2""" CODEOWNERS = ["@jpfaff"] +DOMAIN = "honeywellabp2_i2c" diff --git a/esphome/components/honeywellabp2_i2c/honeywellabp2.cpp b/esphome/components/honeywellabp2_i2c/honeywellabp2.cpp index bfab045838d..2ca3fb821cc 100644 --- a/esphome/components/honeywellabp2_i2c/honeywellabp2.cpp +++ b/esphome/components/honeywellabp2_i2c/honeywellabp2.cpp @@ -9,7 +9,9 @@ static const uint8_t STATUS_BIT_BUSY = 5; static const uint8_t STATUS_BIT_ERROR = 2; static const uint8_t STATUS_MATH_SAT = 0; -static const char *const TAG = "honeywellabp2"; +ESPHOME_LOG_TAG(TAG, "honeywellabp2"); + +static constexpr uint32_t MEASUREMENT_TIMEOUT_ID = 0; void HONEYWELLABP2Sensor::read_sensor_data() { if (this->read(raw_data_, 7) != i2c::ERROR_OK) { @@ -62,7 +64,7 @@ float HONEYWELLABP2Sensor::get_temperature() { return this->last_temperature_; } void HONEYWELLABP2Sensor::loop() { if (this->measurement_running_) { if (this->is_measurement_ready()) { - this->cancel_timeout("meas_timeout"); + this->cancel_timeout(MEASUREMENT_TIMEOUT_ID); this->read_sensor_data(); if (pressure_sensor_ != nullptr) { @@ -79,7 +81,7 @@ void HONEYWELLABP2Sensor::update() { ESP_LOGV(TAG, "Update Honeywell ABP2 Sensor"); this->start_measurement(); - this->set_timeout("meas_timeout", 100, [this] { this->measurement_timeout(); }); + this->set_timeout(MEASUREMENT_TIMEOUT_ID, 100, [this] { this->measurement_timeout(); }); } void HONEYWELLABP2Sensor::dump_config() { diff --git a/esphome/components/host/__init__.py b/esphome/components/host/__init__.py index 16e4c81d344..d0ec6f25350 100644 --- a/esphome/components/host/__init__.py +++ b/esphome/components/host/__init__.py @@ -22,6 +22,7 @@ from .gpio import host_pin_to_code # noqa: F401 CODEOWNERS = ["@esphome/core", "@clydebarrow"] AUTO_LOAD = ["network", "preferences"] +DOMAIN = "host" IS_TARGET_PLATFORM = True diff --git a/esphome/components/host/core.cpp b/esphome/components/host/core.cpp index 9292cd77f61..f753534efd9 100644 --- a/esphome/components/host/core.cpp +++ b/esphome/components/host/core.cpp @@ -90,6 +90,8 @@ int main(int argc, char **argv) { // Install signal handlers for graceful shutdown (flushes preferences to disk) std::signal(SIGINT, signal_handler); std::signal(SIGTERM, signal_handler); + // A write to a closed peer must fail with EPIPE, as on the embedded stacks, not kill the process + std::signal(SIGPIPE, SIG_IGN); esphome::host::setup_preferences(); setup(); diff --git a/esphome/components/host/gpio.cpp b/esphome/components/host/gpio.cpp index f99b82bcc20..b57bd411883 100644 --- a/esphome/components/host/gpio.cpp +++ b/esphome/components/host/gpio.cpp @@ -1,12 +1,13 @@ #ifdef USE_HOST #include "gpio.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" namespace esphome { namespace host { -static const char *const TAG = "host"; +ESPHOME_LOG_TAG(TAG, "host"); struct ISRPinArg { uint8_t pin; @@ -20,7 +21,7 @@ ISRInternalGPIOPin HostGPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } -void HostGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { +void HostGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { ESP_LOGD(TAG, "Attaching interrupt %p to pin %d and mode %d", func, pin_, (uint32_t) type); } void HostGPIOPin::pin_mode(gpio::Flags flags) { ESP_LOGD(TAG, "Setting pin %d mode to %02X", pin_, (uint32_t) flags); } diff --git a/esphome/components/host/gpio.h b/esphome/components/host/gpio.h index bd2d09257bb..fd3ebefe1f6 100644 --- a/esphome/components/host/gpio.h +++ b/esphome/components/host/gpio.h @@ -2,11 +2,11 @@ #ifdef USE_HOST -#include "esphome/core/hal.h" +#include "esphome/core/gpio_pin.h" namespace esphome::host { -class HostGPIOPin final : public InternalGPIOPin { +class HostGPIOPin final : public GPIOPin { public: void set_pin(uint8_t pin) { pin_ = pin; } void set_inverted(bool inverted) { inverted_ = inverted; } @@ -17,14 +17,18 @@ class HostGPIOPin final : public InternalGPIOPin { bool digital_read() override; void digital_write(bool value) override; size_t dump_summary(char *buffer, size_t len) const override; - void detach_interrupt() const override; - ISRInternalGPIOPin to_isr() const override; - uint8_t get_pin() const override { return pin_; } + void detach_interrupt() const; + template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { + this->attach_interrupt_(reinterpret_cast(func), arg, type); + } + ISRInternalGPIOPin to_isr() const; + uint8_t get_pin() const { return pin_; } gpio::Flags get_flags() const override { return flags_; } - bool is_inverted() const override { return inverted_; } + bool is_inverted() const { return inverted_; } + bool is_internal() override { return true; } protected: - void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const; uint8_t pin_; bool inverted_{}; diff --git a/esphome/components/host/hal.h b/esphome/components/host/hal.h index 12abf6684d3..c7676327729 100644 --- a/esphome/components/host/hal.h +++ b/esphome/components/host/hal.h @@ -3,6 +3,7 @@ #ifdef USE_HOST #include +#include #include #define IRAM_ATTR @@ -16,6 +17,16 @@ namespace esphome { /// Host has no ISR concept. __attribute__((always_inline)) inline bool in_isr_context() { return false; } +inline pthread_t &main_loop_thread() { + static pthread_t thread{}; + return thread; +} + +/// arch_init() stores the thread before any component runs, so no unset case exists. +__attribute__((always_inline)) inline bool is_main_loop_thread() { + return pthread_equal(pthread_self(), main_loop_thread()) != 0; +} + __attribute__((always_inline)) inline void yield() { ::sched_yield(); } void delay(uint32_t ms); @@ -25,7 +36,7 @@ uint64_t millis_64(); void delayMicroseconds(uint32_t us); // NOLINT(readability-identifier-naming) uint32_t arch_get_cpu_cycle_count(); -__attribute__((always_inline)) inline void arch_init() {} +__attribute__((always_inline)) inline void arch_init() { main_loop_thread() = pthread_self(); } __attribute__((always_inline)) inline void arch_feed_wdt() {} __attribute__((always_inline)) inline uint32_t arch_get_cpu_freq_hz() { return 1000000000U; } diff --git a/esphome/components/host/helpers.cpp b/esphome/components/host/helpers.cpp index 7274d9de57e..9e203efcb9c 100644 --- a/esphome/components/host/helpers.cpp +++ b/esphome/components/host/helpers.cpp @@ -14,7 +14,7 @@ namespace esphome { -static const char *const TAG = "helpers.host"; +ESPHOME_LOG_TAG(TAG, "helpers.host"); bool random_bytes(uint8_t *data, size_t len) { FILE *fp = fopen("/dev/urandom", "r"); diff --git a/esphome/components/host/preferences.cpp b/esphome/components/host/preferences.cpp index 497b9d11e58..9a8208e96ea 100644 --- a/esphome/components/host/preferences.cpp +++ b/esphome/components/host/preferences.cpp @@ -9,7 +9,7 @@ namespace esphome::host { namespace fs = std::filesystem; -static const char *const TAG = "preferences"; +ESPHOME_LOG_TAG(TAG, "preferences"); void HostPreferences::setup_() { if (this->setup_complete_) diff --git a/esphome/components/hrxl_maxsonar_wr/__init__.py b/esphome/components/hrxl_maxsonar_wr/__init__.py index e69de29bb2d..2210989d9f3 100644 --- a/esphome/components/hrxl_maxsonar_wr/__init__.py +++ b/esphome/components/hrxl_maxsonar_wr/__init__.py @@ -0,0 +1 @@ +DOMAIN = "hrxl_maxsonar_wr" diff --git a/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp index b323dd04360..1d58a4317a6 100644 --- a/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp +++ b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp @@ -11,7 +11,7 @@ namespace esphome::hrxl_maxsonar_wr { -static const char *const TAG = "hrxl.maxsonar.wr.sensor"; +ESPHOME_LOG_TAG(TAG, "hrxl.maxsonar.wr.sensor"); static const uint8_t ASCII_CR = 0x0D; static const uint8_t ASCII_NBSP = 0xFF; static const int MAX_DATA_LENGTH_BYTES = 6; diff --git a/esphome/components/hte501/__init__.py b/esphome/components/hte501/__init__.py index e69de29bb2d..18bffe2b222 100644 --- a/esphome/components/hte501/__init__.py +++ b/esphome/components/hte501/__init__.py @@ -0,0 +1 @@ +DOMAIN = "hte501" diff --git a/esphome/components/hte501/hte501.cpp b/esphome/components/hte501/hte501.cpp index 67bfb1917eb..e955531b570 100644 --- a/esphome/components/hte501/hte501.cpp +++ b/esphome/components/hte501/hte501.cpp @@ -4,7 +4,7 @@ namespace esphome::hte501 { -static const char *const TAG = "hte501"; +ESPHOME_LOG_TAG(TAG, "hte501"); static constexpr size_t HTE501_SERIAL_NUMBER_SIZE = 7; diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index de35d52a409..92359714025 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -30,6 +30,7 @@ from esphome.types import ConfigType DEPENDENCIES = ["network"] AUTO_LOAD = ["json", "watchdog"] +DOMAIN = "http_request" http_request_ns = cg.esphome_ns.namespace("http_request") HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component) @@ -202,11 +203,7 @@ async def to_code(config: ConfigType) -> None: cg.add(var.set_watchdog_timeout(timeout_ms)) if CORE.is_esp32: - # Re-enable ESP-IDF's HTTP client (excluded by default to save compile time). - # esp-tls is re-enabled too because http_request includes - # directly and esp_http_client only pulls it in as a private dependency. - esp32.include_builtin_idf_component("esp_http_client") - esp32.include_builtin_idf_component("esp-tls") + esp32.request_http_client() cg.add(var.set_buffer_size_rx(config[CONF_BUFFER_SIZE_RX])) cg.add(var.set_buffer_size_tx(config[CONF_BUFFER_SIZE_TX])) diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index 37e0de5d6a0..e689c82d2fb 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -6,7 +6,7 @@ namespace esphome::http_request { -static const char *const TAG = "http_request"; +ESPHOME_LOG_TAG(TAG, "http_request"); void HttpRequestComponent::dump_config() { ESP_LOGCONFIG(TAG, diff --git a/esphome/components/http_request/http_request_arduino.cpp b/esphome/components/http_request/http_request_arduino.cpp index 0d968222e9e..deb9c0fec25 100644 --- a/esphome/components/http_request/http_request_arduino.cpp +++ b/esphome/components/http_request/http_request_arduino.cpp @@ -18,7 +18,7 @@ namespace esphome::http_request { -static const char *const TAG = "http_request"; +ESPHOME_LOG_TAG(TAG, "http_request"); #ifdef USE_ESP8266 // ESP8266 Arduino core (WiFiClientSecureBearSSL.cpp) returns -1000 on OOM static constexpr int ESP8266_SSL_ERR_OOM = -1000; @@ -28,7 +28,7 @@ std::shared_ptr HttpRequestArduino::perform(const char *url, cons const std::vector
&request_headers, const std::vector &lower_case_collect_headers) { if (!network::is_connected()) { - this->status_momentary_error("failed", 1000); + this->status_momentary_error(1000); ESP_LOGW(TAG, "HTTP Request failed; Not connected to network"); return nullptr; } @@ -85,7 +85,7 @@ std::shared_ptr HttpRequestArduino::perform(const char *url, cons if (!status) { ESP_LOGW(TAG, "HTTP Request failed; URL: %s", url); container->end(); - this->status_momentary_error("failed", 1000); + this->status_momentary_error(1000); return nullptr; } @@ -143,13 +143,13 @@ std::shared_ptr HttpRequestArduino::perform(const char *url, cons ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s", url, HTTPClient::errorToString(container->status_code).c_str()); - this->status_momentary_error("failed", 1000); + this->status_momentary_error(1000); container->end(); return nullptr; } if (!is_success(container->status_code)) { ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url, container->status_code); - this->status_momentary_error("failed", 1000); + this->status_momentary_error(1000); // Still return the container, so it can be used to get the status code and error message } diff --git a/esphome/components/http_request/http_request_host.cpp b/esphome/components/http_request/http_request_host.cpp index a7889702023..0130445acdc 100644 --- a/esphome/components/http_request/http_request_host.cpp +++ b/esphome/components/http_request/http_request_host.cpp @@ -16,13 +16,13 @@ namespace esphome::http_request { -static const char *const TAG = "http_request"; +ESPHOME_LOG_TAG(TAG, "http_request"); std::shared_ptr HttpRequestHost::perform(const char *url, const char *method, const std::string &body, const std::vector
&request_headers, const std::vector &lower_case_collect_headers) { if (!network::is_connected()) { - this->status_momentary_error("failed", 1000); + this->status_momentary_error(1000); ESP_LOGW(TAG, "HTTP Request failed; Not connected to network"); return nullptr; } @@ -101,7 +101,7 @@ std::shared_ptr HttpRequestHost::perform(const char *url, const c if (!result) { ESP_LOGW(TAG, "HTTP Request failed; URL: %s, error code: %u", url, (unsigned) result.error()); container->end(); - this->status_momentary_error("failed", 1000); + this->status_momentary_error(1000); return nullptr; } App.feed_wdt(); @@ -109,7 +109,7 @@ std::shared_ptr HttpRequestHost::perform(const char *url, const c container->status_code = response.status; if (!is_success(response.status)) { ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url, response.status); - this->status_momentary_error("failed", 1000); + this->status_momentary_error(1000); // Still return the container, so it can be used to get the status code and error message } diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index 4e5a2c42b57..e429df9b971 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -18,7 +18,7 @@ namespace esphome::http_request { -static const char *const TAG = "http_request"; +ESPHOME_LOG_TAG(TAG, "http_request"); static constexpr uint32_t ERROR_DURATION_MS = 1000; void HttpRequestIDF::dump_config() { @@ -54,7 +54,7 @@ std::shared_ptr HttpRequestIDF::perform(const char *url, const ch const std::vector
&request_headers, const std::vector &lower_case_collect_headers) { if (!network::is_connected()) { - this->status_momentary_error("failed", ERROR_DURATION_MS); + this->status_momentary_error(ERROR_DURATION_MS); ESP_LOGE(TAG, "HTTP Request failed; Not connected to network"); return nullptr; } @@ -71,7 +71,7 @@ std::shared_ptr HttpRequestIDF::perform(const char *url, const ch } else if (strcmp(method, "PATCH") == 0) { method_idf = HTTP_METHOD_PATCH; } else { - this->status_momentary_error("failed", ERROR_DURATION_MS); + this->status_momentary_error(ERROR_DURATION_MS); ESP_LOGE(TAG, "HTTP Request failed; Unsupported method"); return nullptr; } @@ -110,7 +110,7 @@ std::shared_ptr HttpRequestIDF::perform(const char *url, const ch esp_http_client_handle_t client = esp_http_client_init(&config); if (client == nullptr) { - this->status_momentary_error("failed", ERROR_DURATION_MS); + this->status_momentary_error(ERROR_DURATION_MS); ESP_LOGE(TAG, "HTTP Request failed; client could not be initialized"); return nullptr; } @@ -131,7 +131,7 @@ std::shared_ptr HttpRequestIDF::perform(const char *url, const ch esp_err_t err = esp_http_client_open(client, body_len); if (err != ESP_OK) { - this->status_momentary_error("failed", ERROR_DURATION_MS); + this->status_momentary_error(ERROR_DURATION_MS); ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err)); esp_http_client_cleanup(client); return nullptr; @@ -154,7 +154,7 @@ std::shared_ptr HttpRequestIDF::perform(const char *url, const ch } if (err != ESP_OK) { - this->status_momentary_error("failed", ERROR_DURATION_MS); + this->status_momentary_error(ERROR_DURATION_MS); ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err)); esp_http_client_cleanup(client); return nullptr; @@ -179,7 +179,7 @@ std::shared_ptr HttpRequestIDF::perform(const char *url, const ch err = esp_http_client_set_redirection(client); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_http_client_set_redirection failed: %s", esp_err_to_name(err)); - this->status_momentary_error("failed", ERROR_DURATION_MS); + this->status_momentary_error(ERROR_DURATION_MS); esp_http_client_cleanup(client); return nullptr; } @@ -192,7 +192,7 @@ std::shared_ptr HttpRequestIDF::perform(const char *url, const ch err = esp_http_client_open(client, 0); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_http_client_open failed: %s", esp_err_to_name(err)); - this->status_momentary_error("failed", ERROR_DURATION_MS); + this->status_momentary_error(ERROR_DURATION_MS); esp_http_client_cleanup(client); return nullptr; } @@ -220,7 +220,7 @@ std::shared_ptr HttpRequestIDF::perform(const char *url, const ch } ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url, container->status_code); - this->status_momentary_error("failed", ERROR_DURATION_MS); + this->status_momentary_error(ERROR_DURATION_MS); return container; } diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index 7e7594c3c3a..a26c366fa61 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -11,7 +11,7 @@ namespace esphome::http_request { -static const char *const TAG = "http_request.ota"; +ESPHOME_LOG_TAG(TAG, "http_request.ota"); void OtaHttpRequestComponent::dump_config() { ESP_LOGCONFIG(TAG, "Over-The-Air updates via HTTP request"); }; diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 6a74c00e8e5..bedb0e98e70 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -22,7 +22,7 @@ namespace esphome::http_request { #define UPDATE_RETURN return #endif -static const char *const TAG = "http_request.update"; +ESPHOME_LOG_TAG(TAG, "http_request.update"); // Wraps UpdateInfo + error for the task→main-loop handoff. struct TaskResult { diff --git a/esphome/components/htu21d/__init__.py b/esphome/components/htu21d/__init__.py index e69de29bb2d..10e6e9d66b3 100644 --- a/esphome/components/htu21d/__init__.py +++ b/esphome/components/htu21d/__init__.py @@ -0,0 +1 @@ +DOMAIN = "htu21d" diff --git a/esphome/components/htu21d/htu21d.cpp b/esphome/components/htu21d/htu21d.cpp index 8a3b2cb5d5c..468a7f8e6a2 100644 --- a/esphome/components/htu21d/htu21d.cpp +++ b/esphome/components/htu21d/htu21d.cpp @@ -4,7 +4,7 @@ namespace esphome::htu21d { -static const char *const TAG = "htu21d"; +ESPHOME_LOG_TAG(TAG, "htu21d"); static const uint8_t HTU21D_ADDRESS = 0x40; static const uint8_t HTU21D_REGISTER_RESET = 0xFE; diff --git a/esphome/components/htu31d/__init__.py b/esphome/components/htu31d/__init__.py index 039693cb30f..bc17a0d8867 100644 --- a/esphome/components/htu31d/__init__.py +++ b/esphome/components/htu31d/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@betterengineering"] +DOMAIN = "htu31d" diff --git a/esphome/components/htu31d/htu31d.cpp b/esphome/components/htu31d/htu31d.cpp index 6821b8b69ef..86c9876b2b6 100644 --- a/esphome/components/htu31d/htu31d.cpp +++ b/esphome/components/htu31d/htu31d.cpp @@ -17,7 +17,7 @@ namespace esphome::htu31d { /** Logging prefix */ -static const char *const TAG = "htu31d"; +ESPHOME_LOG_TAG(TAG, "htu31d"); /** Default I2C address for the HTU31D. */ static const uint8_t HTU31D_DEFAULT_I2CADDR = 0x40; diff --git a/esphome/components/hub75/__init__.py b/esphome/components/hub75/__init__.py index cd5441f749c..3ee6a864153 100644 --- a/esphome/components/hub75/__init__.py +++ b/esphome/components/hub75/__init__.py @@ -1,6 +1,7 @@ from esphome.cpp_generator import MockObj CODEOWNERS = ["@stuartparmenter"] +DOMAIN = "hub75" # Use fully-qualified namespace to avoid collision with external hub75 library's global ::hub75 namespace hub75_ns = MockObj("::esphome::hub75", "::") diff --git a/esphome/components/hub75/hub75.cpp b/esphome/components/hub75/hub75.cpp index d36928a83af..674e23cb344 100644 --- a/esphome/components/hub75/hub75.cpp +++ b/esphome/components/hub75/hub75.cpp @@ -6,7 +6,7 @@ namespace esphome::hub75 { -static const char *const TAG = "hub75"; +ESPHOME_LOG_TAG(TAG, "hub75"); // ======================================== // Constructor diff --git a/esphome/components/hx711/__init__.py b/esphome/components/hx711/__init__.py index e69de29bb2d..f9402be78ea 100644 --- a/esphome/components/hx711/__init__.py +++ b/esphome/components/hx711/__init__.py @@ -0,0 +1 @@ +DOMAIN = "hx711" diff --git a/esphome/components/hx711/hx711.cpp b/esphome/components/hx711/hx711.cpp index fe2fb4c350b..48afc0192de 100644 --- a/esphome/components/hx711/hx711.cpp +++ b/esphome/components/hx711/hx711.cpp @@ -4,7 +4,7 @@ namespace esphome::hx711 { -static const char *const TAG = "hx711"; +ESPHOME_LOG_TAG(TAG, "hx711"); void HX711Sensor::setup() { this->sck_pin_->setup(); diff --git a/esphome/components/hydreon_rgxx/__init__.py b/esphome/components/hydreon_rgxx/__init__.py index b488bfc1b41..313ce9158c1 100644 --- a/esphome/components/hydreon_rgxx/__init__.py +++ b/esphome/components/hydreon_rgxx/__init__.py @@ -3,6 +3,7 @@ from esphome.components import uart CODEOWNERS = ["@functionpointer"] DEPENDENCIES = ["uart"] +DOMAIN = "hydreon_rgxx" hydreon_rgxx_ns = cg.esphome_ns.namespace("hydreon_rgxx") RGModel = hydreon_rgxx_ns.enum("RGModel") diff --git a/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp b/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp index 05557111fce..36a7b1b3511 100644 --- a/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp +++ b/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp @@ -3,7 +3,10 @@ namespace esphome::hydreon_rgxx { -static const char *const TAG = "hydreon_rgxx.sensor"; +ESPHOME_LOG_TAG(TAG, "hydreon_rgxx.sensor"); + +static constexpr uint32_t REBOOT_INTERVAL_ID = 0; + static const int MAX_DATA_LENGTH_BYTES = 80; static const uint8_t ASCII_LF = 0x0A; #define HYDREON_RGXX_COMMA , @@ -138,7 +141,7 @@ void HydreonRGxxComponent::loop() { */ void HydreonRGxxComponent::schedule_reboot_() { this->boot_count_ = 0; - this->set_interval("reboot", 5000, [this]() { + this->set_interval(REBOOT_INTERVAL_ID, 5000, [this]() { if (this->boot_count_ < 0) { ESP_LOGW(TAG, "hydreon_rgxx failed to boot %d times", -this->boot_count_); } @@ -188,7 +191,7 @@ void HydreonRGxxComponent::process_line_() { } else { this->boot_count_++; } - this->cancel_interval("reboot"); + this->cancel_interval(REBOOT_INTERVAL_ID); this->no_response_count_ = 0; ESP_LOGI(TAG, "Boot detected: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); diff --git a/esphome/components/hyt271/__init__.py b/esphome/components/hyt271/__init__.py index 2e88d4f3660..70be33a00ec 100644 --- a/esphome/components/hyt271/__init__.py +++ b/esphome/components/hyt271/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@Philippe12"] +DOMAIN = "hyt271" diff --git a/esphome/components/hyt271/hyt271.cpp b/esphome/components/hyt271/hyt271.cpp index 7b2c960a5f9..a8374c4115e 100644 --- a/esphome/components/hyt271/hyt271.cpp +++ b/esphome/components/hyt271/hyt271.cpp @@ -4,7 +4,9 @@ namespace esphome::hyt271 { -static const char *const TAG = "hyt271"; +ESPHOME_LOG_TAG(TAG, "hyt271"); + +static constexpr uint32_t WAIT_CONVERT_TIMEOUT_ID = 0; static const uint8_t HYT271_ADDRESS = 0x28; @@ -23,7 +25,7 @@ void HYT271Component::update() { ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); return; } - this->set_timeout("wait_convert", 50, [this]() { + this->set_timeout(WAIT_CONVERT_TIMEOUT_ID, 50, [this]() { uint8_t raw_data[4]; if (this->read(raw_data, 4) != i2c::ERROR_OK) { this->status_set_warning(); diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index b053125446e..453c2513fa7 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -38,7 +38,6 @@ from esphome.const import ( CONF_ADDRESS, CONF_DEVICE, CONF_FREQUENCY, - CONF_I2C, CONF_I2C_ID, CONF_ID, CONF_LOW_POWER_MODE, @@ -58,6 +57,7 @@ from esphome.cpp_generator import MockObj import esphome.final_validate as fv from esphome.types import ConfigType +DOMAIN = "i2c" LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@esphome/core"] i2c_ns = cg.esphome_ns.namespace("i2c") @@ -232,7 +232,7 @@ CONFIG_SCHEMA = cv.All( def _final_validate(config: ConfigType) -> None: - full_config = fv.full_config.get()[CONF_I2C] + full_config = fv.full_config.get()[DOMAIN] if CORE.using_zephyr and len(full_config) > 1: raise cv.Invalid("Second i2c is not implemented on Zephyr yet") if CORE.is_rp2: diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index 76b3ec3b4d9..8fc0a503276 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -7,7 +7,7 @@ namespace esphome::i2c { -static const char *const TAG = "i2c"; +ESPHOME_LOG_TAG(TAG, "i2c"); void I2CBus::i2c_scan_() { for (uint8_t address = 8; address != 120; address++) { diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 39a6aec7742..95aee2bddb2 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -9,7 +9,7 @@ namespace esphome::i2c { -static const char *const TAG = "i2c"; +ESPHOME_LOG_TAG(TAG, "i2c"); // Maximum bytes to log in hex format (truncates larger transfers) static constexpr size_t I2C_MAX_LOG_BYTES = 32; diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 7ca9537e2de..b13b525abbb 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -12,7 +12,7 @@ namespace esphome::i2c { -static const char *const TAG = "i2c"; +ESPHOME_LOG_TAG(TAG, "i2c"); // Maximum bytes to log in hex format (truncates larger transfers) static constexpr size_t I2C_MAX_LOG_BYTES = 32; diff --git a/esphome/components/i2c/i2c_bus_host.cpp b/esphome/components/i2c/i2c_bus_host.cpp index 303944636bf..672bc9cc5d1 100644 --- a/esphome/components/i2c/i2c_bus_host.cpp +++ b/esphome/components/i2c/i2c_bus_host.cpp @@ -16,7 +16,7 @@ namespace esphome::i2c { -static const char *const TAG = "i2c"; +ESPHOME_LOG_TAG(TAG, "i2c"); HostI2CBus::~HostI2CBus() { if (this->file_descriptor_ != -1) { diff --git a/esphome/components/i2c/i2c_bus_zephyr.cpp b/esphome/components/i2c/i2c_bus_zephyr.cpp index ffdd2ba8bb2..4145cd58592 100644 --- a/esphome/components/i2c/i2c_bus_zephyr.cpp +++ b/esphome/components/i2c/i2c_bus_zephyr.cpp @@ -6,7 +6,7 @@ namespace esphome::i2c { -static const char *const TAG = "i2c"; +ESPHOME_LOG_TAG(TAG, "i2c"); static const char *get_speed(uint32_t dev_config) { switch (I2C_SPEED_GET(dev_config)) { diff --git a/esphome/components/i2c_device/__init__.py b/esphome/components/i2c_device/__init__.py index f890fefdb9d..a118cb3f172 100644 --- a/esphome/components/i2c_device/__init__.py +++ b/esphome/components/i2c_device/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType DEPENDENCIES = ["i2c"] CODEOWNERS = ["@gabest11"] +DOMAIN = "i2c_device" MULTI_CONF = True i2c_device_ns = cg.esphome_ns.namespace("i2c_device") diff --git a/esphome/components/i2c_device/i2c_device.cpp b/esphome/components/i2c_device/i2c_device.cpp index 2f68a9e96a5..379a25574e7 100644 --- a/esphome/components/i2c_device/i2c_device.cpp +++ b/esphome/components/i2c_device/i2c_device.cpp @@ -5,7 +5,7 @@ namespace esphome::i2c_device { -static const char *const TAG = "i2c_device"; +ESPHOME_LOG_TAG(TAG, "i2c_device"); void I2CDeviceComponent::dump_config() { ESP_LOGCONFIG(TAG, "I2CDevice"); diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py index c5e82beb46d..6e4e35590c1 100644 --- a/esphome/components/i2s_audio/__init__.py +++ b/esphome/components/i2s_audio/__init__.py @@ -18,6 +18,7 @@ from esphome.components.esp32.const import ( VARIANT_ESP32S2, VARIANT_ESP32S3, ) +from esphome.components.microphone import DOMAIN as MICROPHONE_DOMAIN import esphome.config_validation as cv from esphome.const import CONF_BITS_PER_SAMPLE, CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE from esphome.core import CORE @@ -27,6 +28,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@jesserockz"] DEPENDENCIES = ["esp32"] +DOMAIN = "i2s_audio" MULTI_CONF = True CONF_PDM = "pdm" @@ -39,7 +41,6 @@ CONF_I2S_MCLK_PIN = "i2s_mclk_pin" CONF_I2S_BCLK_PIN = "i2s_bclk_pin" CONF_I2S_LRCLK_PIN = "i2s_lrclk_pin" -CONF_I2S_AUDIO = "i2s_audio" CONF_I2S_AUDIO_ID = "i2s_audio_id" CONF_I2S_MODE = "i2s_mode" @@ -218,9 +219,9 @@ class I2SAudioData: def _get_data() -> I2SAudioData: - if CONF_I2S_AUDIO not in CORE.data: - CORE.data[CONF_I2S_AUDIO] = I2SAudioData() - return CORE.data[CONF_I2S_AUDIO] + if DOMAIN not in CORE.data: + CORE.data[DOMAIN] = I2SAudioData() + return CORE.data[DOMAIN] def _assign_ports() -> None: @@ -234,12 +235,12 @@ def _assign_ports() -> None: return full_config = fv.full_config.get() - i2s_configs = full_config[CONF_I2S_AUDIO] + i2s_configs = full_config[DOMAIN] # Find i2s_audio instances with microphones that require port 0 # (PDM and internal ADC only work on I2S port 0) port0_parent_id = None - for mic_config in full_config.get("microphone", []): + for mic_config in full_config.get(MICROPHONE_DOMAIN, []): if CONF_I2S_AUDIO_ID not in mic_config: continue if mic_config.get(CONF_PDM) or mic_config.get(CONF_ADC_TYPE) == "internal": @@ -262,7 +263,7 @@ def _assign_ports() -> None: def _final_validate(_: ConfigType) -> None: - i2s_audio_configs = fv.full_config.get()[CONF_I2S_AUDIO] + i2s_audio_configs = fv.full_config.get()[DOMAIN] variant = get_esp32_variant() if variant not in I2S_PORTS: raise cv.Invalid(f"Unsupported variant {variant}") diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp index c577ed092ed..1345f0187b3 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp @@ -19,7 +19,7 @@ static const uint32_t READ_DURATION_MS = 16; static const size_t TASK_STACK_SIZE = 4096; static const ssize_t TASK_PRIORITY = 23; -static const char *const TAG = "i2s_audio.microphone"; +ESPHOME_LOG_TAG(TAG, "i2s_audio.microphone"); enum MicrophoneEventGroupBits : uint32_t { COMMAND_STOP = (1 << 0), // stops the microphone task, set and cleared by ``loop`` @@ -397,7 +397,7 @@ void I2SAudioMicrophone::loop() { if (!this->start_driver_()) { ESP_LOGE(TAG, "Driver failed to start; retrying in 1 second"); - this->status_momentary_error("driver_fail", 1000); + this->status_momentary_error(1000); this->stop_driver_(); // Stop/frees whatever possibly started break; } @@ -408,7 +408,7 @@ void I2SAudioMicrophone::loop() { if (this->task_handle_ == nullptr) { ESP_LOGE(TAG, "Task failed to start, retrying in 1 second"); - this->status_momentary_error("task_fail", 1000); + this->status_momentary_error(1000); this->stop_driver_(); // Stops the driver to return the lock; will be reloaded in next attempt } } diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_spdif.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_spdif.cpp index ec4e459be78..5054478ceb2 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_spdif.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_spdif.cpp @@ -14,7 +14,7 @@ namespace esphome::i2s_audio { -static const char *const TAG = "i2s_audio.spdif"; +ESPHOME_LOG_TAG(TAG, "i2s_audio.spdif"); // SPDIF mode adds overhead as each sample is encapsulated in a subframe; // each DMA buffer can hold only 192 samples (~4ms each vs. ~15ms for standard I2S). diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp index 7ae58b3e440..8d586ea4e01 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp @@ -18,7 +18,7 @@ namespace esphome::i2s_audio { -static const char *const TAG = "i2s_audio.speaker"; +ESPHOME_LOG_TAG(TAG, "i2s_audio.speaker"); // Software volume control maps the user-facing (0.0, 1.0) range linearly to a dB reduction in // [-49.0, 0.0] dB; 0.0 is silence. @@ -40,6 +40,16 @@ void I2SAudioSpeakerBase::setup() { // When no audio_dac is configured, this initializes software volume control. this->set_volume(this->volume_); this->set_mute_state(this->mute_state_); + + // Until the I2S driver first starts, dout sits in its reset state (often pulled high, or a JTAG + // function on the ESP32-S3), which keeps a SPDIF optical transmitter lit. Park it low now. + this->park_dout_pin_(); +} + +void I2SAudioSpeakerBase::park_dout_pin_() { + gpio_reset_pin(this->dout_pin_); + gpio_set_direction(this->dout_pin_, GPIO_MODE_OUTPUT); + gpio_set_level(this->dout_pin_, 0); } void I2SAudioSpeakerBase::dump_config() { @@ -130,7 +140,7 @@ void I2SAudioSpeakerBase::loop() { if (this->start_i2s_driver(this->audio_stream_info_) != ESP_OK) { ESP_LOGE(TAG, "Driver failed to start; retrying in 1 second"); - this->status_momentary_error("driver-failure", 1000); + this->status_momentary_error(1000); break; } @@ -143,7 +153,7 @@ void I2SAudioSpeakerBase::loop() { if (this->speaker_task_handle_ == nullptr) { ESP_LOGE(TAG, "Task failed to start, retrying in 1 second"); - this->status_momentary_error("task-failure", 1000); + this->status_momentary_error(1000); this->stop_i2s_driver_(); // Stops the driver to return the lock; will be reloaded in next attempt } break; @@ -303,9 +313,7 @@ void I2SAudioSpeakerBase::stop_i2s_driver_() { // setup installed. If another speaker reuses this port (shared bus), its audio still reaches our // dout. Detach the pin and drive it low so a stale output stops driving downstream hardware: a // SPDIF optical transmitter would otherwise stay lit, and an analog DAC would emit noise. - gpio_reset_pin(this->dout_pin_); - gpio_set_direction(this->dout_pin_, GPIO_MODE_OUTPUT); - gpio_set_level(this->dout_pin_, 0); + this->park_dout_pin_(); } this->parent_->unlock(); } diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h index b443166ea1b..965eede0201 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker.h @@ -129,6 +129,9 @@ class I2SAudioSpeakerBase : public I2SAudioOut, public speaker::Speaker, public /// @brief Stops the I2S driver and unlocks the I2S port void stop_i2s_driver_(); + /// @brief Detaches dout from the I2S signal and drives it low + void park_dout_pin_(); + /// @brief Called in loop() when the task has stopped. Override for mode-specific cleanup. virtual void on_task_stopped() {} diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_speaker_standard.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_speaker_standard.cpp index b4b6173458b..fbc1edbde93 100644 --- a/esphome/components/i2s_audio/speaker/i2s_audio_speaker_standard.cpp +++ b/esphome/components/i2s_audio/speaker/i2s_audio_speaker_standard.cpp @@ -18,7 +18,7 @@ namespace esphome::i2s_audio { -static const char *const TAG = "i2s_audio.speaker.std"; +ESPHOME_LOG_TAG(TAG, "i2s_audio.speaker.std"); static constexpr uint32_t DMA_BUFFER_DURATION_MS = 10; static constexpr size_t DMA_BUFFERS_COUNT = 5; diff --git a/esphome/components/i2s_audio/speaker/spdif_encoder.cpp b/esphome/components/i2s_audio/speaker/spdif_encoder.cpp index 30146e0a70c..c98cbcb20ab 100644 --- a/esphome/components/i2s_audio/speaker/spdif_encoder.cpp +++ b/esphome/components/i2s_audio/speaker/spdif_encoder.cpp @@ -6,7 +6,7 @@ namespace esphome::i2s_audio { -static const char *const TAG = "i2s_audio.spdif_encoder"; +ESPHOME_LOG_TAG(TAG, "i2s_audio.spdif_encoder"); // S/PDIF preamble patterns (8 BMC bits each) // These are the BMC-encoded sync patterns that violate normal BMC rules for easy detection. diff --git a/esphome/components/iaqcore/__init__.py b/esphome/components/iaqcore/__init__.py index e69de29bb2d..216921250f9 100644 --- a/esphome/components/iaqcore/__init__.py +++ b/esphome/components/iaqcore/__init__.py @@ -0,0 +1 @@ +DOMAIN = "iaqcore" diff --git a/esphome/components/iaqcore/iaqcore.cpp b/esphome/components/iaqcore/iaqcore.cpp index 7397d0975cf..48935c24681 100644 --- a/esphome/components/iaqcore/iaqcore.cpp +++ b/esphome/components/iaqcore/iaqcore.cpp @@ -5,7 +5,7 @@ namespace esphome::iaqcore { -static const char *const TAG = "iaqcore"; +ESPHOME_LOG_TAG(TAG, "iaqcore"); enum IAQCoreErrorCode : uint8_t { ERROR_OK = 0, ERROR_RUNIN = 0x10, ERROR_BUSY = 0x01, ERROR_ERROR = 0x80 }; diff --git a/esphome/components/icnt86/__init__.py b/esphome/components/icnt86/__init__.py index 07f3b4e31ca..7736bfd3936 100644 --- a/esphome/components/icnt86/__init__.py +++ b/esphome/components/icnt86/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@danepowell"] +DOMAIN = "icnt86" diff --git a/esphome/components/icnt86/icnt86.cpp b/esphome/components/icnt86/icnt86.cpp index 62a4586ebc3..42585269f84 100644 --- a/esphome/components/icnt86/icnt86.cpp +++ b/esphome/components/icnt86/icnt86.cpp @@ -3,7 +3,7 @@ namespace esphome::icnt86 { -static const char *const TAG = "icnt86"; +ESPHOME_LOG_TAG(TAG, "icnt86"); static constexpr uint16_t REG_TOUCH_NUM = 0x1001; static constexpr uint16_t REG_POINT1 = 0x1002; static constexpr uint8_t MAX_TOUCHES = 5; diff --git a/esphome/components/ili9341/__init__.py b/esphome/components/ili9341/__init__.py index e69de29bb2d..95ff0c3056d 100644 --- a/esphome/components/ili9341/__init__.py +++ b/esphome/components/ili9341/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ili9341" diff --git a/esphome/components/ili9xxx/__init__.py b/esphome/components/ili9xxx/__init__.py index 84888bbabcb..2803b439156 100644 --- a/esphome/components/ili9xxx/__init__.py +++ b/esphome/components/ili9xxx/__init__.py @@ -1,3 +1,5 @@ +DOMAIN = "ili9xxx" + DEPRECATED_COMPONENT = """ The 'ili9xxx' component is deprecated and no new models will be added to it. New model PRs should target the newer and more performant 'mipi_spi' component. diff --git a/esphome/components/improv_base/__init__.py b/esphome/components/improv_base/__init__.py index 9b57b6561f9..1acc5e6ca0f 100644 --- a/esphome/components/improv_base/__init__.py +++ b/esphome/components/improv_base/__init__.py @@ -8,6 +8,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] +DOMAIN = "improv_base" CONF_NEXT_URL = "next_url" diff --git a/esphome/components/improv_base/improv_base.cpp b/esphome/components/improv_base/improv_base.cpp index 6745f8064b1..e25980545cf 100644 --- a/esphome/components/improv_base/improv_base.cpp +++ b/esphome/components/improv_base/improv_base.cpp @@ -9,7 +9,7 @@ namespace esphome::improv_base { #ifdef USE_IMPROV_NEXT_URL -static const char *const TAG = "improv_base"; +ESPHOME_LOG_TAG(TAG, "improv_base"); static constexpr const char DEVICE_NAME_PLACEHOLDER[] = "{{device_name}}"; static constexpr size_t DEVICE_NAME_PLACEHOLDER_LEN = sizeof(DEVICE_NAME_PLACEHOLDER) - 1; diff --git a/esphome/components/improv_ble/__init__.py b/esphome/components/improv_ble/__init__.py index 72ac5866286..9dfc700f748 100644 --- a/esphome/components/improv_ble/__init__.py +++ b/esphome/components/improv_ble/__init__.py @@ -2,13 +2,7 @@ from esphome import automation import esphome.codegen as cg from esphome.components import binary_sensor, improv_base, output import esphome.config_validation as cv -from esphome.const import ( - CONF_ID, - CONF_ON_START, - CONF_ON_STATE, - CONF_TRIGGER_ID, - PLATFORM_ESP32, -) +from esphome.const import CONF_ID, CONF_ON_START, CONF_ON_STATE, PLATFORM_ESP32 from esphome.core import CORE from esphome.types import ConfigType @@ -31,6 +25,7 @@ def AUTO_LOAD() -> list[str]: CODEOWNERS = ["@jesserockz"] DEPENDENCIES = ["wifi"] +DOMAIN = "improv_ble" # Legacy top-level YAML key that routes here; esphome/loader.py and # esphome/config.py handle the warning and the key rename. @@ -58,20 +53,35 @@ State = improv_ns.enum("State") improv_ble_ns = cg.esphome_ns.namespace("improv_ble") ImprovBLEComponent = improv_ble_ns.class_("ImprovBLEComponent", cg.Component) -ImprovBLEProvisionedTrigger = improv_ble_ns.class_( - "ImprovBLEProvisionedTrigger", automation.Trigger.template() -) -ImprovBLEProvisioningTrigger = improv_ble_ns.class_( - "ImprovBLEProvisioningTrigger", automation.Trigger.template() -) -ImprovBLEStartTrigger = improv_ble_ns.class_( - "ImprovBLEStartTrigger", automation.Trigger.template() -) -ImprovBLEStateTrigger = improv_ble_ns.class_( - "ImprovBLEStateTrigger", automation.Trigger.template() -) -ImprovBLEStoppedTrigger = improv_ble_ns.class_( - "ImprovBLEStoppedTrigger", automation.Trigger.template() + +_STATE_PARAMS = [(State, "state"), (Error, "error")] +_NOT_FAILED = "!{parent}->is_failed()" + + +def _state_automation( + conf_key: str, args: list, states: str | None +) -> automation.CallbackAutomation: + # Every automation hangs off the state callback and is silent while the component has failed. + return automation.CallbackAutomation( + conf_key, + "add_on_state_callback", + args, + params=_STATE_PARAMS, + forward=[name for _, name in args], + when=_NOT_FAILED if states is None else f"({states}) && {_NOT_FAILED}", + ) + + +_CALLBACK_AUTOMATIONS = ( + _state_automation(CONF_ON_PROVISIONED, [], "state == improv::STATE_PROVISIONED"), + _state_automation(CONF_ON_PROVISIONING, [], "state == improv::STATE_PROVISIONING"), + _state_automation( + CONF_ON_START, + [], + "state == improv::STATE_AUTHORIZED || state == improv::STATE_AWAITING_AUTHORIZATION", + ), + _state_automation(CONF_ON_STATE, _STATE_PARAMS, None), + _state_automation(CONF_ON_STOP, [], "state == improv::STATE_STOPPED"), ) @@ -92,41 +102,11 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_WIFI_TIMEOUT, default=DEFAULT_WIFI_TIMEOUT ): cv.positive_time_period_milliseconds, - cv.Optional(CONF_ON_PROVISIONED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - ImprovBLEProvisionedTrigger - ), - } - ), - cv.Optional(CONF_ON_PROVISIONING): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - ImprovBLEProvisioningTrigger - ), - } - ), - cv.Optional(CONF_ON_START): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - ImprovBLEStartTrigger - ), - } - ), - cv.Optional(CONF_ON_STATE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - ImprovBLEStateTrigger - ), - } - ), - cv.Optional(CONF_ON_STOP): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - ImprovBLEStoppedTrigger - ), - } - ), + cv.Optional(CONF_ON_PROVISIONED): automation.validate_automation({}), + cv.Optional(CONF_ON_PROVISIONING): automation.validate_automation({}), + cv.Optional(CONF_ON_START): automation.validate_automation({}), + cv.Optional(CONF_ON_STATE): automation.validate_automation({}), + cv.Optional(CONF_ON_STOP): automation.validate_automation({}), } ) .extend(improv_base.IMPROV_SCHEMA) @@ -163,28 +143,6 @@ async def to_code(config: ConfigType) -> None: status_indicator = await cg.get_variable(config[CONF_STATUS_INDICATOR]) cg.add(var.set_status_indicator(status_indicator)) - use_state_callback = False - for conf in config.get(CONF_ON_PROVISIONED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - use_state_callback = True - for conf in config.get(CONF_ON_PROVISIONING, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - use_state_callback = True - for conf in config.get(CONF_ON_START, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - use_state_callback = True - for conf in config.get(CONF_ON_STATE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(State, "state"), (Error, "error")], conf - ) - use_state_callback = True - for conf in config.get(CONF_ON_STOP, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - use_state_callback = True - if use_state_callback: + if any(config.get(entry.conf_key) for entry in _CALLBACK_AUTOMATIONS): cg.add_define("USE_IMPROV_BLE_STATE_CALLBACK") + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) diff --git a/esphome/components/improv_ble/automation.h b/esphome/components/improv_ble/automation.h deleted file mode 100644 index 223a1292384..00000000000 --- a/esphome/components/improv_ble/automation.h +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once -#ifdef USE_ESP32 -#ifdef USE_IMPROV_BLE_STATE_CALLBACK -#include "improv_ble_component.h" - -#include "esphome/core/automation.h" - -#include - -namespace esphome::improv_ble { - -class ImprovBLEProvisionedTrigger final : public Trigger<> { - public: - explicit ImprovBLEProvisionedTrigger(ImprovBLEComponent *parent) : parent_(parent) { - parent->add_on_state_callback([this](improv::State state, improv::Error error) { - if (state == improv::STATE_PROVISIONED && !this->parent_->is_failed()) { - this->trigger(); - } - }); - } - - protected: - ImprovBLEComponent *parent_; -}; - -class ImprovBLEProvisioningTrigger final : public Trigger<> { - public: - explicit ImprovBLEProvisioningTrigger(ImprovBLEComponent *parent) : parent_(parent) { - parent->add_on_state_callback([this](improv::State state, improv::Error error) { - if (state == improv::STATE_PROVISIONING && !this->parent_->is_failed()) { - this->trigger(); - } - }); - } - - protected: - ImprovBLEComponent *parent_; -}; - -class ImprovBLEStartTrigger final : public Trigger<> { - public: - explicit ImprovBLEStartTrigger(ImprovBLEComponent *parent) : parent_(parent) { - parent->add_on_state_callback([this](improv::State state, improv::Error error) { - if ((state == improv::STATE_AUTHORIZED || state == improv::STATE_AWAITING_AUTHORIZATION) && - !this->parent_->is_failed()) { - this->trigger(); - } - }); - } - - protected: - ImprovBLEComponent *parent_; -}; - -class ImprovBLEStateTrigger final : public Trigger { - public: - explicit ImprovBLEStateTrigger(ImprovBLEComponent *parent) : parent_(parent) { - parent->add_on_state_callback([this](improv::State state, improv::Error error) { - if (!this->parent_->is_failed()) { - this->trigger(state, error); - } - }); - } - - protected: - ImprovBLEComponent *parent_; -}; - -class ImprovBLEStoppedTrigger final : public Trigger<> { - public: - explicit ImprovBLEStoppedTrigger(ImprovBLEComponent *parent) : parent_(parent) { - parent->add_on_state_callback([this](improv::State state, improv::Error error) { - if (state == improv::STATE_STOPPED && !this->parent_->is_failed()) { - this->trigger(); - } - }); - } - - protected: - ImprovBLEComponent *parent_; -}; - -} // namespace esphome::improv_ble - -#endif -#endif diff --git a/esphome/components/improv_ble/improv_ble_component.cpp b/esphome/components/improv_ble/improv_ble_component.cpp index 0a20beb33c8..2029058899f 100644 --- a/esphome/components/improv_ble/improv_ble_component.cpp +++ b/esphome/components/improv_ble/improv_ble_component.cpp @@ -20,7 +20,11 @@ namespace esphome::improv_ble { using namespace bytebuffer; -static const char *const TAG = "improv_ble.component"; +ESPHOME_LOG_TAG(TAG, "improv_ble.component"); + +static constexpr uint32_t STOP_SERVICE_TIMEOUT_ID = 0; +static constexpr uint32_t WIFI_CONNECT_TIMEOUT_ID = 1; + static constexpr size_t IMPROV_MAX_LOG_BYTES = 128; static constexpr char ESPHOME_MY_LINK[] = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome"; // command + data length + trailing byte @@ -322,7 +326,7 @@ void ImprovBLEComponent::stop() { // Wait before stopping the service to ensure all BLE clients see the state change. // This prevents clients from repeatedly reconnecting and wasting resources by allowing // them to observe that the device is provisioned before the service disappears. - this->set_timeout("end-service", STOP_ADVERTISING_DELAY, [this] { + this->set_timeout(STOP_SERVICE_TIMEOUT_ID, STOP_ADVERTISING_DELAY, [this] { if (this->state_ == improv::STATE_STOPPED || this->service_ == nullptr) return; // Release first so removing the service UUID does not restart advertising on the way out @@ -398,7 +402,7 @@ void ImprovBLEComponent::process_incoming_data_() { ESP_LOGD(TAG, "Received Improv Wi-Fi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(), command.password.c_str()); - this->set_timeout("wifi-connect-timeout", 30000, [this]() { this->on_wifi_connect_timeout_(); }); + this->set_timeout(WIFI_CONNECT_TIMEOUT_ID, 30000, [this]() { this->on_wifi_connect_timeout_(); }); this->incoming_data_.clear(); break; } @@ -438,7 +442,7 @@ void ImprovBLEComponent::check_wifi_connection_() { if (this->state_ == improv::STATE_PROVISIONING) { wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(), this->connecting_sta_.get_password()); this->connecting_sta_ = {}; - this->cancel_timeout("wifi-connect-timeout"); + this->cancel_timeout(WIFI_CONNECT_TIMEOUT_ID); // Build the URL list directly into a stack buffer with no heap allocation std::array buf; diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py index 0231791e9b3..3e1aef21bb6 100644 --- a/esphome/components/improv_serial/__init__.py +++ b/esphome/components/improv_serial/__init__.py @@ -1,15 +1,9 @@ import esphome.codegen as cg from esphome.components import improv_base, uart from esphome.components.esp32 import VARIANT_ESP32S3, get_esp32_variant -from esphome.components.logger import USB_CDC +from esphome.components.logger import DOMAIN as LOGGER_DOMAIN, USB_CDC import esphome.config_validation as cv -from esphome.const import ( - CONF_BAUD_RATE, - CONF_HARDWARE_UART, - CONF_ID, - CONF_LOGGER, - CONF_UART_ID, -) +from esphome.const import CONF_BAUD_RATE, CONF_HARDWARE_UART, CONF_ID, CONF_UART_ID from esphome.core import CORE import esphome.final_validate as fv from esphome.types import ConfigType @@ -17,6 +11,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["improv_base"] CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["logger", "network"] +DOMAIN = "improv_serial" improv_serial_ns = cg.esphome_ns.namespace("improv_serial") @@ -52,7 +47,7 @@ def validate_transport(config: ConfigType) -> None: # The host logger has no serial port for Improv to share if CORE.is_host: raise cv.Invalid("improv_serial on the host platform requires uart_id") - logger_conf = fv.full_config.get()[CONF_LOGGER] + logger_conf = fv.full_config.get()[LOGGER_DOMAIN] if logger_conf[CONF_BAUD_RATE] == 0: raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0") if CORE.is_esp32 and ( diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index 3827fb6ed45..10454678669 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -16,7 +16,9 @@ namespace esphome::improv_serial { -static const char *const TAG = "improv_serial"; +ESPHOME_LOG_TAG(TAG, "improv_serial"); + +static constexpr uint32_t WIFI_CONNECT_TIMEOUT_ID = 0; void ImprovSerialComponent::setup() { global_improv_serial_component = this; @@ -73,7 +75,7 @@ void ImprovSerialComponent::loop() { wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(), this->connecting_sta_.get_password()); this->connecting_sta_ = {}; - this->cancel_timeout("wifi-connect-timeout"); + this->cancel_timeout(WIFI_CONNECT_TIMEOUT_ID); this->set_state_(improv::STATE_PROVISIONED); this->send_settings_response_(improv::WIFI_SETTINGS); @@ -302,7 +304,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command ESP_LOGD(TAG, "Received settings: SSID=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(), command.password.c_str()); - this->set_timeout("wifi-connect-timeout", switching ? WIFI_SWITCH_TIMEOUT_MS : WIFI_CONNECT_TIMEOUT_MS, + this->set_timeout(WIFI_CONNECT_TIMEOUT_ID, switching ? WIFI_SWITCH_TIMEOUT_MS : WIFI_CONNECT_TIMEOUT_MS, [this]() { this->on_wifi_connect_timeout_(); }); #else // No Wi-Fi support compiled in; there is nothing to provision. diff --git a/esphome/components/ina219/__init__.py b/esphome/components/ina219/__init__.py index e69de29bb2d..5e9d2cef174 100644 --- a/esphome/components/ina219/__init__.py +++ b/esphome/components/ina219/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ina219" diff --git a/esphome/components/ina219/ina219.cpp b/esphome/components/ina219/ina219.cpp index 833d1989c8a..a675099b4dc 100644 --- a/esphome/components/ina219/ina219.cpp +++ b/esphome/components/ina219/ina219.cpp @@ -4,7 +4,7 @@ namespace esphome::ina219 { -static const char *const TAG = "ina219"; +ESPHOME_LOG_TAG(TAG, "ina219"); // | A0 | A1 | Address | // | GND | GND | 0x40 | diff --git a/esphome/components/ina226/__init__.py b/esphome/components/ina226/__init__.py index ca1f2caa31c..9998623c82c 100644 --- a/esphome/components/ina226/__init__.py +++ b/esphome/components/ina226/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@Sergio303", "@latonita"] +DOMAIN = "ina226" diff --git a/esphome/components/ina226/ina226.cpp b/esphome/components/ina226/ina226.cpp index c22237d144b..97b3caf949b 100644 --- a/esphome/components/ina226/ina226.cpp +++ b/esphome/components/ina226/ina226.cpp @@ -5,7 +5,7 @@ namespace esphome::ina226 { -static const char *const TAG = "ina226"; +ESPHOME_LOG_TAG(TAG, "ina226"); // | A0 | A1 | Address | // | GND | GND | 0x40 | diff --git a/esphome/components/ina260/__init__.py b/esphome/components/ina260/__init__.py index e69de29bb2d..4728ef80f8b 100644 --- a/esphome/components/ina260/__init__.py +++ b/esphome/components/ina260/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ina260" diff --git a/esphome/components/ina260/ina260.cpp b/esphome/components/ina260/ina260.cpp index 05039f0e331..82a4e4fba14 100644 --- a/esphome/components/ina260/ina260.cpp +++ b/esphome/components/ina260/ina260.cpp @@ -4,7 +4,7 @@ namespace esphome::ina260 { -static const char *const TAG = "ina260"; +ESPHOME_LOG_TAG(TAG, "ina260"); // | A0 | A1 | Address | // | GND | GND | 0x40 | diff --git a/esphome/components/ina2xx_base/__init__.py b/esphome/components/ina2xx_base/__init__.py index b6cf0e38f30..7fdfd90bb6f 100644 --- a/esphome/components/ina2xx_base/__init__.py +++ b/esphome/components/ina2xx_base/__init__.py @@ -34,6 +34,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@latonita"] +DOMAIN = "ina2xx_base" CONF_ADC_AVERAGING = "adc_averaging" CONF_ADC_RANGE = "adc_range" diff --git a/esphome/components/ina2xx_base/ina2xx_base.cpp b/esphome/components/ina2xx_base/ina2xx_base.cpp index fec5cd2f131..58e0598b06c 100644 --- a/esphome/components/ina2xx_base/ina2xx_base.cpp +++ b/esphome/components/ina2xx_base/ina2xx_base.cpp @@ -7,7 +7,7 @@ namespace esphome::ina2xx_base { -static const char *const TAG = "ina2xx"; +ESPHOME_LOG_TAG(TAG, "ina2xx"); #define OKFAILED(b) ((b) ? "OK" : "FAILED") diff --git a/esphome/components/ina2xx_i2c/__init__.py b/esphome/components/ina2xx_i2c/__init__.py index e69de29bb2d..d6c784bad77 100644 --- a/esphome/components/ina2xx_i2c/__init__.py +++ b/esphome/components/ina2xx_i2c/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ina2xx_i2c" diff --git a/esphome/components/ina2xx_i2c/ina2xx_i2c.cpp b/esphome/components/ina2xx_i2c/ina2xx_i2c.cpp index 4fc3b00a215..61c7744cca7 100644 --- a/esphome/components/ina2xx_i2c/ina2xx_i2c.cpp +++ b/esphome/components/ina2xx_i2c/ina2xx_i2c.cpp @@ -3,7 +3,7 @@ namespace esphome::ina2xx_i2c { -static const char *const TAG = "ina2xx_i2c"; +ESPHOME_LOG_TAG(TAG, "ina2xx_i2c"); void INA2XXI2C::setup() { auto err = this->write(nullptr, 0); diff --git a/esphome/components/ina2xx_spi/__init__.py b/esphome/components/ina2xx_spi/__init__.py index e69de29bb2d..ae72c2d1950 100644 --- a/esphome/components/ina2xx_spi/__init__.py +++ b/esphome/components/ina2xx_spi/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ina2xx_spi" diff --git a/esphome/components/ina2xx_spi/ina2xx_spi.cpp b/esphome/components/ina2xx_spi/ina2xx_spi.cpp index 43eb6762367..b9b7a6c774e 100644 --- a/esphome/components/ina2xx_spi/ina2xx_spi.cpp +++ b/esphome/components/ina2xx_spi/ina2xx_spi.cpp @@ -3,7 +3,7 @@ namespace esphome::ina2xx_spi { -static const char *const TAG = "ina2xx_spi"; +ESPHOME_LOG_TAG(TAG, "ina2xx_spi"); void INA2XXSPI::setup() { this->spi_setup(); diff --git a/esphome/components/ina3221/__init__.py b/esphome/components/ina3221/__init__.py index e69de29bb2d..f8f491ddec5 100644 --- a/esphome/components/ina3221/__init__.py +++ b/esphome/components/ina3221/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ina3221" diff --git a/esphome/components/ina3221/ina3221.cpp b/esphome/components/ina3221/ina3221.cpp index b88480fc001..f3647c4d1ca 100644 --- a/esphome/components/ina3221/ina3221.cpp +++ b/esphome/components/ina3221/ina3221.cpp @@ -4,7 +4,7 @@ namespace esphome::ina3221 { -static const char *const TAG = "ina3221"; +ESPHOME_LOG_TAG(TAG, "ina3221"); static const uint8_t INA3221_REGISTER_CONFIG = 0x00; static const uint8_t INA3221_REGISTER_CHANNEL1_SHUNT_VOLTAGE = 0x01; diff --git a/esphome/components/infrared/__init__.py b/esphome/components/infrared/__init__.py index d04c82ea965..2d3d478e583 100644 --- a/esphome/components/infrared/__init__.py +++ b/esphome/components/infrared/__init__.py @@ -9,20 +9,22 @@ Once the API is considered stable, this warning will be removed. """ import esphome.codegen as cg +from esphome.components import ir_rf_base import esphome.config_validation as cv from esphome.const import CONF_ID -from esphome.core import CORE, coroutine_with_priority -from esphome.core.entity_helpers import queue_entity_register, setup_entity +from esphome.core import coroutine_with_priority +from esphome.core.entity_helpers import setup_entity from esphome.coroutine import CoroPriority from esphome.types import ConfigType, SafeExpType CODEOWNERS = ["@kbx81"] -AUTO_LOAD = ["remote_base"] +AUTO_LOAD = ["ir_rf_base"] +DOMAIN = "infrared" IS_PLATFORM_COMPONENT = True infrared_ns = cg.esphome_ns.namespace("infrared") -Infrared = infrared_ns.class_("Infrared", cg.EntityBase, cg.Component) +Infrared = infrared_ns.class_("Infrared", ir_rf_base.IrRfEntity) InfraredCall = infrared_ns.class_("InfraredCall") InfraredTraits = infrared_ns.class_("InfraredTraits") @@ -52,11 +54,8 @@ async def setup_infrared_core_(var: cg.MockObj, config: ConfigType) -> None: async def register_infrared(var: cg.MockObj, config: ConfigType) -> None: """Register an infrared device with the core.""" - cg.add_define("USE_IR_RF") - await cg.register_component(var, config) - queue_entity_register("infrared", config) + await ir_rf_base.register_ir_rf_entity(var, config, "infrared") await setup_infrared_core_(var, config) - CORE.register_platform_component("infrared", var) async def new_infrared(config: ConfigType, *args: SafeExpType) -> cg.MockObj: diff --git a/esphome/components/infrared/infrared.cpp b/esphome/components/infrared/infrared.cpp index 83039a5a9bf..9b766a8996c 100644 --- a/esphome/components/infrared/infrared.cpp +++ b/esphome/components/infrared/infrared.cpp @@ -1,161 +1,18 @@ #include "infrared.h" -#include - #include "esphome/core/log.h" -#ifdef USE_API -#include "esphome/components/api/api_server.h" -#endif - namespace esphome::infrared { -static const char *const TAG = "infrared"; - -// ========== InfraredCall ========== - -InfraredCall &InfraredCall::set_carrier_frequency(uint32_t frequency) { - this->carrier_frequency_ = frequency; - return *this; -} - -InfraredCall &InfraredCall::set_raw_timings(const std::vector &timings) { - this->raw_timings_ = &timings; - this->packed_data_ = nullptr; - this->base64url_ptr_ = nullptr; - return *this; -} - -InfraredCall &InfraredCall::set_raw_timings_base64url(const std::string &base64url) { - this->base64url_ptr_ = &base64url; - this->raw_timings_ = nullptr; - this->packed_data_ = nullptr; - return *this; -} - -InfraredCall &InfraredCall::set_raw_timings_packed(const uint8_t *data, uint16_t length, uint16_t count) { - this->packed_data_ = data; - this->packed_length_ = length; - this->packed_count_ = count; - this->raw_timings_ = nullptr; - this->base64url_ptr_ = nullptr; - return *this; -} - -InfraredCall &InfraredCall::set_repeat_count(uint32_t count) { - this->repeat_count_ = count; - return *this; -} - -void InfraredCall::perform() { - if (this->parent_ != nullptr) { - this->parent_->control(*this); - } -} - -// ========== Infrared ========== - -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()); -} +ESPHOME_LOG_TAG(TAG, "infrared"); void Infrared::dump_config() { ESP_LOGCONFIG(TAG, "Infrared '%s'\n" " Supports Transmitter: %s\n" " Supports Receiver: %s", - this->get_name().c_str(), YESNO(this->traits_.get_supports_transmitter()), - YESNO(this->traits_.get_supports_receiver())); -} - -void Infrared::control(const InfraredCall &call) { - if (this->transmitter_ == nullptr) { - ESP_LOGW(TAG, "No transmitter configured"); - return; - } - - if (!call.has_raw_timings()) { - ESP_LOGE(TAG, "No raw timings provided"); - return; - } - - // Create transmit data object - auto transmit_call = this->transmitter_->transmit(); - auto *transmit_data = transmit_call.get_data(); - - // Set carrier frequency - auto freq = call.get_carrier_frequency(); - if (freq.has_value()) { - transmit_data->set_carrier_frequency(*freq); - } - - // Set timings based on format - if (call.is_packed()) { - // Zero-copy from packed protobuf data - transmit_data->set_data_from_packed_sint32(call.get_packed_data(), call.get_packed_length(), - call.get_packed_count()); - ESP_LOGD(TAG, "Transmitting packed raw timings: count=%" PRIu16 ", repeat=%" PRIu32, call.get_packed_count(), - call.get_repeat_count()); - } else if (call.is_base64url()) { - // Decode base64url (URL-safe) into transmit buffer - if (!transmit_data->set_data_from_base64url(call.get_base64url_data())) { - ESP_LOGE(TAG, "Invalid base64url data"); - return; - } - // Sanity check: validate timing values are within reasonable bounds - constexpr int32_t max_timing_us = 500000; // 500ms absolute max - for (int32_t timing : transmit_data->get_data()) { - int32_t abs_timing = timing < 0 ? -timing : timing; - if (abs_timing > max_timing_us) { - ESP_LOGE(TAG, "Invalid timing value: %" PRId32 " µs (max %" PRId32 ")", timing, max_timing_us); - return; - } - } - ESP_LOGD(TAG, "Transmitting base64url raw timings: count=%zu, repeat=%" PRIu32, transmit_data->get_data().size(), - call.get_repeat_count()); - } else { - // From vector (lambdas/automations) - transmit_data->set_data(call.get_raw_timings()); - ESP_LOGD(TAG, "Transmitting raw timings: count=%zu, repeat=%" PRIu32, call.get_raw_timings().size(), - call.get_repeat_count()); - } - - // Set repeat count - if (call.get_repeat_count() > 0) { - transmit_call.set_send_times(call.get_repeat_count()); - } - - // Perform transmission - transmit_call.perform(); -} - -uint32_t Infrared::get_capability_flags() const { - uint32_t flags = 0; - - // Add transmit/receive capability based on traits - if (this->traits_.get_supports_transmitter()) - flags |= InfraredCapability::CAPABILITY_TRANSMITTER; - if (this->traits_.get_supports_receiver()) - flags |= InfraredCapability::CAPABILITY_RECEIVER; - - return flags; -} - -bool Infrared::on_receive(remote_base::RemoteReceiveData data) { - // Forward received IR data to API server -#if defined(USE_API) && defined(USE_IR_RF) - if (api::global_api_server != nullptr) { -#ifdef USE_DEVICES - uint32_t device_id = this->get_device_id(); -#else - uint32_t device_id = 0; -#endif - api::global_api_server->send_infrared_rf_receive_event(device_id, this->get_object_id_hash(), &data.get_raw_data()); - } -#endif - return false; // Don't consume the event, allow other listeners to process it + this->get_name().c_str(), YESNO(this->get_supports_transmitter()), + YESNO(this->get_supports_receiver())); } } // namespace esphome::infrared diff --git a/esphome/components/infrared/infrared.h b/esphome/components/infrared/infrared.h index afbde57be2f..d81e3e2e38d 100644 --- a/esphome/components/infrared/infrared.h +++ b/esphome/components/infrared/infrared.h @@ -4,131 +4,48 @@ // without following the normal breaking changes policy. Use at your own risk. // Once the API is considered stable, this warning will be removed. -#include "esphome/core/component.h" -#include "esphome/core/entity_base.h" -#include "esphome/components/remote_base/remote_base.h" - -#include +#include "esphome/components/ir_rf_base/ir_rf_base.h" namespace esphome::infrared { -/// Capability flags for individual infrared instances -enum InfraredCapability : uint32_t { - CAPABILITY_TRANSMITTER = 1 << 0, // Can transmit signals - CAPABILITY_RECEIVER = 1 << 1, // Can receive signals -}; +using ir_rf_base::CAPABILITY_RECEIVER; +using ir_rf_base::CAPABILITY_TRANSMITTER; -/// Forward declarations class Infrared; /// InfraredCall - Builder pattern for transmitting infrared signals -class InfraredCall { +class InfraredCall : public ir_rf_base::IrRfCall { public: - explicit InfraredCall(Infrared *parent) : parent_(parent) {} + explicit InfraredCall(Infrared *parent) : IrRfCall(parent) {} /// Set the carrier frequency in Hz - InfraredCall &set_carrier_frequency(uint32_t frequency); - - // ===== Raw Timings Methods ===== - // All set_raw_timings_* methods store pointers/references to external data. - // The referenced data must remain valid until perform() completes. - // Safe pattern: call.set_raw_timings_xxx(data); call.perform(); // synchronous - // Unsafe pattern: call.set_raw_timings_xxx(data); defer([call]() { call.perform(); }); // data may be gone! - - /// Set the raw timings from a vector (positive = mark, negative = space) - /// @note Lifetime: Stores a pointer to the vector. The vector must outlive perform(). - /// @note Usage: Primarily for lambdas/automations where the vector is in scope. - InfraredCall &set_raw_timings(const std::vector &timings); - - /// Set the raw timings from base64url-encoded little-endian int32 data - /// @note Lifetime: Stores a pointer to the string. The string must outlive perform(). - /// @note Usage: For web_server - base64url is fully URL-safe (uses '-' and '_'). - /// @note Decoding happens at perform() time, directly into the transmit buffer. - InfraredCall &set_raw_timings_base64url(const std::string &base64url); - - /// Set the raw timings from packed protobuf sint32 data (zigzag + varint encoded) - /// @note Lifetime: Stores a pointer to the buffer. The buffer must outlive perform(). - /// @note Usage: For API component where data comes directly from the protobuf message. - InfraredCall &set_raw_timings_packed(const uint8_t *data, uint16_t length, uint16_t count); - - /// Set the number of times to repeat transmission (1 = transmit once, 2 = transmit twice, etc.) - InfraredCall &set_repeat_count(uint32_t count); - - /// Perform the transmission - void perform(); - + InfraredCall &set_carrier_frequency(uint32_t frequency) { + this->carrier_frequency_ = frequency; + return *this; + } /// Get the carrier frequency const optional &get_carrier_frequency() const { return this->carrier_frequency_; } - /// Get the raw timings (only valid if set via set_raw_timings) - const std::vector &get_raw_timings() const { return *this->raw_timings_; } - /// Check if raw timings have been set (any format) - bool has_raw_timings() const { - return this->raw_timings_ != nullptr || this->packed_data_ != nullptr || this->base64url_ptr_ != nullptr; - } - /// Check if using packed data format - bool is_packed() const { return this->packed_data_ != nullptr; } - /// Check if using base64url data format - bool is_base64url() const { return this->base64url_ptr_ != nullptr; } - /// Get the base64url data string - const std::string &get_base64url_data() const { return *this->base64url_ptr_; } - /// Get packed data (only valid if set via set_raw_timings_packed) - const uint8_t *get_packed_data() const { return this->packed_data_; } - uint16_t get_packed_length() const { return this->packed_length_; } - uint16_t get_packed_count() const { return this->packed_count_; } - /// Get the repeat count - uint32_t get_repeat_count() const { return this->repeat_count_; } protected: - uint32_t repeat_count_{1}; - Infrared *parent_; optional carrier_frequency_; - // Pointer to vector-based timings (caller-owned, must outlive perform()) - const std::vector *raw_timings_{nullptr}; - // Pointer to base64url-encoded string (caller-owned, must outlive perform()) - const std::string *base64url_ptr_{nullptr}; - // Pointer to packed protobuf buffer (caller-owned, must outlive perform()) - const uint8_t *packed_data_{nullptr}; - uint16_t packed_length_{0}; - uint16_t packed_count_{0}; }; /// InfraredTraits - Describes the capabilities of an infrared implementation class InfraredTraits { public: - bool get_supports_transmitter() const { return this->supports_transmitter_; } - void set_supports_transmitter(bool supports) { this->supports_transmitter_ = supports; } - - bool get_supports_receiver() const { return this->supports_receiver_; } - void set_supports_receiver(bool supports) { this->supports_receiver_ = supports; } - uint32_t get_receiver_frequency_hz() const { return this->receiver_frequency_hz_; } void set_receiver_frequency_hz(uint32_t freq) { this->receiver_frequency_hz_ = freq; } protected: - bool supports_transmitter_{false}; - bool supports_receiver_{false}; uint32_t receiver_frequency_hz_{0}; // Demodulation frequency of the IR receiver in Hz (0 = unspecified) }; /// Infrared - Base class for infrared remote control implementations -class Infrared : public Component, public EntityBase, public remote_base::RemoteReceiverListener { +class Infrared : public ir_rf_base::IrRfEntity { public: Infrared() = default; - void setup() override; void dump_config() override; - float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; } - - /// 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; } - - /// Check if this infrared has a transmitter configured - bool has_transmitter() const { return this->transmitter_ != nullptr; } - /// Check if this infrared has a receiver configured - bool has_receiver() const { return this->receiver_ != nullptr; } /// Get the traits for this infrared implementation InfraredTraits &get_traits() { return this->traits_; } @@ -137,21 +54,16 @@ class Infrared : public Component, public EntityBase, public remote_base::Remote /// Create a call object for transmitting InfraredCall make_call() { return InfraredCall(this); } - /// Get capability flags for this infrared instance - uint32_t get_capability_flags() const; - - /// Called when IR data is received (from RemoteReceiverListener) - bool on_receive(remote_base::RemoteReceiveData data) override; - protected: - friend class InfraredCall; + friend class ir_rf_base::IrRfCall; - /// Perform the actual transmission (called by InfraredCall) - virtual void control(const InfraredCall &call); - - // Underlying hardware components - remote_base::RemoteReceiverBase *receiver_{nullptr}; - remote_base::RemoteTransmitterBase *transmitter_{nullptr}; + void on_call_(const InfraredCall &) {} + /// Perform the actual transmission (called by InfraredCall); false only when no frame was handed + /// to the transmitter, in which case no completion follows + /// Without a remote_base transmitter, call api_transmit_done_() once the frame is out + virtual bool control(const InfraredCall &call) { + return this->transmit_raw_(call, call.get_carrier_frequency().value_or(0)); + } // Traits describing capabilities InfraredTraits traits_; diff --git a/esphome/components/inkbird_ibsth1_mini/__init__.py b/esphome/components/inkbird_ibsth1_mini/__init__.py index e69de29bb2d..e0fbed17ca2 100644 --- a/esphome/components/inkbird_ibsth1_mini/__init__.py +++ b/esphome/components/inkbird_ibsth1_mini/__init__.py @@ -0,0 +1 @@ +DOMAIN = "inkbird_ibsth1_mini" diff --git a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp index d360142bcba..ef6600b4ebc 100644 --- a/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp +++ b/esphome/components/inkbird_ibsth1_mini/inkbird_ibsth1_mini.cpp @@ -3,7 +3,7 @@ namespace esphome::inkbird_ibsth1_mini { -static const char *const TAG = "inkbird_ibsth1_mini"; +ESPHOME_LOG_TAG(TAG, "inkbird_ibsth1_mini"); void InkbirdIbstH1Mini::dump_config() { ESP_LOGCONFIG(TAG, "Inkbird IBS TH1 MINI"); diff --git a/esphome/components/inkplate/__init__.py b/esphome/components/inkplate/__init__.py index 1c6013793a7..36740332a50 100644 --- a/esphome/components/inkplate/__init__.py +++ b/esphome/components/inkplate/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@jesserockz", "@JosipKuci"] +DOMAIN = "inkplate" diff --git a/esphome/components/inkplate/inkplate.cpp b/esphome/components/inkplate/inkplate.cpp index 2e837fb614e..b1e8535d206 100644 --- a/esphome/components/inkplate/inkplate.cpp +++ b/esphome/components/inkplate/inkplate.cpp @@ -9,7 +9,7 @@ namespace esphome::inkplate { -static const char *const TAG = "inkplate"; +ESPHOME_LOG_TAG(TAG, "inkplate"); void Inkplate::setup() { for (uint32_t i = 0; i < 256; i++) { diff --git a/esphome/components/inkplate6/__init__.py b/esphome/components/inkplate6/__init__.py index e69de29bb2d..2b198f666bc 100644 --- a/esphome/components/inkplate6/__init__.py +++ b/esphome/components/inkplate6/__init__.py @@ -0,0 +1 @@ +DOMAIN = "inkplate6" diff --git a/esphome/components/integration/__init__.py b/esphome/components/integration/__init__.py index 71a87b6ae50..4afc7ce9e1f 100644 --- a/esphome/components/integration/__init__.py +++ b/esphome/components/integration/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@OttoWinter"] +DOMAIN = "integration" diff --git a/esphome/components/integration/integration_sensor.cpp b/esphome/components/integration/integration_sensor.cpp index bc26fb0f193..c7b6173e39b 100644 --- a/esphome/components/integration/integration_sensor.cpp +++ b/esphome/components/integration/integration_sensor.cpp @@ -5,7 +5,7 @@ namespace esphome::integration { -static const char *const TAG = "integration"; +ESPHOME_LOG_TAG(TAG, "integration"); void IntegrationSensor::setup() { if (this->restore_) { diff --git a/esphome/components/internal_temperature/__init__.py b/esphome/components/internal_temperature/__init__.py index 9433ade13f8..aa1671a0154 100644 --- a/esphome/components/internal_temperature/__init__.py +++ b/esphome/components/internal_temperature/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@Mat931"] +DOMAIN = "internal_temperature" diff --git a/esphome/components/internal_temperature/internal_temperature_bk72xx.cpp b/esphome/components/internal_temperature/internal_temperature_bk72xx.cpp index 91f47d831fc..00f3b97ec0a 100644 --- a/esphome/components/internal_temperature/internal_temperature_bk72xx.cpp +++ b/esphome/components/internal_temperature/internal_temperature_bk72xx.cpp @@ -9,7 +9,7 @@ uint32_t temp_single_get_current_temperature(uint32_t *temp_value); namespace esphome::internal_temperature { -static const char *const TAG = "internal_temperature"; +ESPHOME_LOG_TAG(TAG, "internal_temperature"); void InternalTemperatureSensor::update() { float temperature = NAN; diff --git a/esphome/components/internal_temperature/internal_temperature_common.cpp b/esphome/components/internal_temperature/internal_temperature_common.cpp index 89a7d34333b..51c55d4adb5 100644 --- a/esphome/components/internal_temperature/internal_temperature_common.cpp +++ b/esphome/components/internal_temperature/internal_temperature_common.cpp @@ -3,7 +3,7 @@ namespace esphome::internal_temperature { -static const char *const TAG = "internal_temperature"; +ESPHOME_LOG_TAG(TAG, "internal_temperature"); void InternalTemperatureSensor::dump_config() { LOG_SENSOR("", "Internal Temperature Sensor", this); } diff --git a/esphome/components/internal_temperature/internal_temperature_esp32.cpp b/esphome/components/internal_temperature/internal_temperature_esp32.cpp index 2c6fda2af49..4da062ac05a 100644 --- a/esphome/components/internal_temperature/internal_temperature_esp32.cpp +++ b/esphome/components/internal_temperature/internal_temperature_esp32.cpp @@ -16,7 +16,7 @@ uint8_t temprature_sens_read(); namespace esphome::internal_temperature { -static const char *const TAG = "internal_temperature"; +ESPHOME_LOG_TAG(TAG, "internal_temperature"); void InternalTemperatureSensor::update() { float temperature = NAN; diff --git a/esphome/components/internal_temperature/internal_temperature_rp2.cpp b/esphome/components/internal_temperature/internal_temperature_rp2.cpp index c4ab33b0a56..b0ccf991456 100644 --- a/esphome/components/internal_temperature/internal_temperature_rp2.cpp +++ b/esphome/components/internal_temperature/internal_temperature_rp2.cpp @@ -16,7 +16,7 @@ namespace esphome::internal_temperature { -static const char *const TAG = "internal_temperature"; +ESPHOME_LOG_TAG(TAG, "internal_temperature"); // The on-die temperature sensor sits on the last ADC channel: input 4 on RP2040 // and RP2350A, but input 8 on RP2350B, which has eight external channels rather diff --git a/esphome/components/internal_temperature/internal_temperature_zephyr.cpp b/esphome/components/internal_temperature/internal_temperature_zephyr.cpp index 50c597f6f1b..5c3e81d10fa 100644 --- a/esphome/components/internal_temperature/internal_temperature_zephyr.cpp +++ b/esphome/components/internal_temperature/internal_temperature_zephyr.cpp @@ -8,7 +8,7 @@ namespace esphome::internal_temperature { -static const char *const TAG = "internal_temperature"; +ESPHOME_LOG_TAG(TAG, "internal_temperature"); static const struct device *const DIE_TEMPERATURE_SENSOR = DEVICE_DT_GET_ONE(nordic_nrf_temp); diff --git a/esphome/components/interval/__init__.py b/esphome/components/interval/__init__.py index 11c3e15b0d4..adbe4349c66 100644 --- a/esphome/components/interval/__init__.py +++ b/esphome/components/interval/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_ID, CONF_INTERVAL, CONF_STARTUP_DELAY from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] +DOMAIN = "interval" interval_ns = cg.esphome_ns.namespace("interval") IntervalTrigger = interval_ns.class_( "IntervalTrigger", automation.Trigger.template(), cg.PollingComponent diff --git a/esphome/components/ir_rf_base/__init__.py b/esphome/components/ir_rf_base/__init__.py new file mode 100644 index 00000000000..7a4cde3677a --- /dev/null +++ b/esphome/components/ir_rf_base/__init__.py @@ -0,0 +1,39 @@ +"""Shared base for the infrared and radio_frequency entity components.""" + +import esphome.codegen as cg +from esphome.components import remote_base +from esphome.components.api import DOMAIN as API_DOMAIN +from esphome.core import CORE +from esphome.core.entity_helpers import queue_entity_register +from esphome.types import ConfigType + +CODEOWNERS = ["@kbx81", "@bdraco"] +AUTO_LOAD = ["remote_base"] +DOMAIN = "ir_rf_base" + +ir_rf_base_ns = cg.esphome_ns.namespace("ir_rf_base") +IrRfEntity = ir_rf_base_ns.class_("IrRfEntity", cg.EntityBase, cg.Component) + + +async def attach_transmitter(var: cg.MockObj, config: ConfigType, key: str) -> None: + """Link the configured transmitter to an entity. + + With the API configured this also compiles in the transmit completion + tracking that answers API transmit requests; the transmitter platform has + to report completion through notify_complete_(), as remote_transmitter + does. Without this call anywhere in the build a request is answered as + soon as the frame is handed over. + """ + await remote_base.register_transmittable(var, config, key) + if API_DOMAIN in CORE.config: + cg.add_define("USE_IR_RF_TRANSMIT_COMPLETE") + + +async def register_ir_rf_entity( + var: cg.MockObj, config: ConfigType, domain: str +) -> None: + """Register an infrared or radio_frequency entity; USE_IR_RF covers both kinds.""" + cg.add_define("USE_IR_RF") + await cg.register_component(var, config) + queue_entity_register(domain, config) + CORE.register_platform_component(domain, var) diff --git a/esphome/components/ir_rf_base/ir_rf_base.cpp b/esphome/components/ir_rf_base/ir_rf_base.cpp new file mode 100644 index 00000000000..91db4252c07 --- /dev/null +++ b/esphome/components/ir_rf_base/ir_rf_base.cpp @@ -0,0 +1,237 @@ +#include "ir_rf_base.h" + +#include +#include + +#include "esphome/core/log.h" + +#ifdef USE_API +#include "esphome/components/api/api_connection.h" +#include "esphome/components/api/api_server.h" +#include "esphome/core/application.h" +#endif + +namespace esphome::ir_rf_base { + +ESPHOME_LOG_TAG(TAG, "ir_rf"); + +#if defined(USE_API) && defined(USE_IR_RF) +// A missing completion is answered as failed this long after the frame should have left the +// wire, and an owed reply the client never reads is dropped this long after the first refusal +static constexpr uint32_t API_REPLY_TIMEOUT_MS = 30000; +#endif +#ifdef USE_IR_RF_TRANSMIT_COMPLETE +// Longest air time added to the deadline, in 16 ms ticks (8 min); with the 30 s above the +// deadline stays within the signed 16 bit tick window loop() compares against +static constexpr uint16_t API_REPLY_MAX_AIR_TICKS = 30000; +#endif + +#if defined(USE_API) && defined(USE_IR_RF) +static uint16_t api_reply_ticks_from_now() { + return static_cast((App.get_loop_component_start_time() >> 4) + (API_REPLY_TIMEOUT_MS >> 4)); +} +#endif + +void IrRfEntity::setup() { + // merged, not assigned: a platform may have set a flag for its own hardware before setup() + if (this->has_transmitter()) + this->supports_transmitter_ = true; + if (this->has_receiver()) + this->supports_receiver_ = true; +} + +bool IrRfEntity::transmit_raw_(const IrRfCallData &call, uint32_t carrier_frequency_hz) { + if (this->transmitter_ == nullptr) { + ESP_LOGW(TAG, "No transmitter configured"); + return false; + } + + if (!call.has_raw_timings()) { + ESP_LOGE(TAG, "No raw timings provided"); + return false; + } + + auto transmit_call = this->transmitter_->transmit(); + auto *transmit_data = transmit_call.get_data(); + transmit_data->set_carrier_frequency(carrier_frequency_hz); + + if (call.is_packed()) { + // Zero-copy from packed protobuf data + transmit_data->set_data_from_packed_sint32(call.get_packed_data(), call.get_packed_length(), + call.get_packed_count()); + ESP_LOGD(TAG, "Transmitting packed raw timings: count=%" PRIu16 ", repeat=%" PRIu32, call.get_packed_count(), + call.get_repeat_count()); + } else if (call.is_base64url()) { + // Decode base64url (URL-safe) into transmit buffer + if (!transmit_data->set_data_from_base64url(call.get_base64url_data())) { + ESP_LOGE(TAG, "Invalid base64url data"); + return false; + } + constexpr int32_t max_timing_us = 500000; // 500ms absolute max + for (int32_t timing : transmit_data->get_data()) { + int32_t abs_timing = timing < 0 ? -timing : timing; + if (abs_timing > max_timing_us) { + ESP_LOGE(TAG, "Invalid timing value: %" PRId32 " µs (max %" PRId32 ")", timing, max_timing_us); + return false; + } + } + ESP_LOGD(TAG, "Transmitting base64url raw timings: count=%zu, repeat=%" PRIu32, transmit_data->get_data().size(), + call.get_repeat_count()); + } else { + // From vector (lambdas/automations) + transmit_data->set_data(call.get_raw_timings()); + ESP_LOGD(TAG, "Transmitting raw timings: count=%zu, repeat=%" PRIu32, call.get_raw_timings().size(), + call.get_repeat_count()); + } + + // one answer for every backend: a frame that decoded to nothing is refused here + if (transmit_data->get_data().empty()) { + ESP_LOGE(TAG, "No raw timings provided"); + return false; + } + + if (call.get_repeat_count() > 0) { + transmit_call.set_send_times(call.get_repeat_count()); + } + +#ifdef USE_IR_RF_TRANSMIT_COMPLETE + // only the API frame expect_api_reply_() armed claims the seq and extends its deadline + if (call.wants_api_reply() && this->api_reply_ == ApiReply::API_REPLY_WAITING) { + this->inflight_seq_ = transmit_call.get_seq(); + // a long frame must not be answered as failed while still on the wire: the 30 s safety net + // starts after this frame's own air time (capped so the tick comparison cannot wrap) + uint64_t frame_us = 0; + for (const int64_t timing : transmit_data->get_data()) + frame_us += timing < 0 ? -timing : timing; + const uint64_t air_ticks = (frame_us * std::max(call.get_repeat_count(), 1) / 1000) >> 4; + this->api_reply_deadline_ += static_cast(std::min(air_ticks, API_REPLY_MAX_AIR_TICKS)); + } +#endif + transmit_call.perform(); + return true; +} + +bool IrRfEntity::on_receive(remote_base::RemoteReceiveData data) { +#if defined(USE_API) && defined(USE_IR_RF) + if (api::global_api_server != nullptr) { +#ifdef USE_DEVICES + uint32_t device_id = this->get_device_id(); +#else + uint32_t device_id = 0; +#endif + api::global_api_server->send_infrared_rf_receive_event(device_id, this->get_object_id_hash(), &data.get_raw_data()); + } +#endif + return false; // Don't consume the event, allow other listeners to process it +} + +#if defined(USE_API) && defined(USE_IR_RF) + +#ifdef USE_IR_RF_TRANSMIT_COMPLETE +void IrRfEntity::on_transmit_complete(remote_base::RemoteTransmitterBase *transmitter, uint16_t seq, bool sent) { + // only the frame this entity submitted; YAML automations and other entities share the transmitter + if (transmitter == this->transmitter_ && seq == this->inflight_seq_ && + this->api_reply_ == ApiReply::API_REPLY_WAITING) + this->finish_api_reply_(sent); +} +#endif + +bool IrRfEntity::expect_api_reply_(api::APIConnection *conn) { + // only an unpaced client gets here: its earlier request is answered as not started, and a reply + // the buffer still owes gets one last try; if that fails too the slot stays with it and the + // new request is refused rather than answered to nobody + if (this->api_reply_ == ApiReply::API_REPLY_WAITING) + this->finish_api_reply_(false); + if (this->api_reply_ != ApiReply::API_REPLY_NONE && !this->send_api_reply_()) { + ESP_LOGW(TAG, "'%s': transmit %s", this->get_name().c_str(), LOG_STR_LITERAL("refused, reply still owed")); + this->refuse_api_call_(conn); + return false; + } + this->api_reply_connection_ = conn; + this->api_reply_deadline_ = api_reply_ticks_from_now(); + this->api_reply_ = ApiReply::API_REPLY_WAITING; + return true; +} + +void IrRfEntity::refuse_api_call_(api::APIConnection *conn) { + uint32_t device_id = 0; +#ifdef USE_DEVICES + device_id = this->get_device_id(); +#endif + // no slot to retry from; a drop here is covered by the client's own timeout + if (!conn->send_infrared_rf_transmit_complete(device_id, this->get_object_id_hash(), false)) { + API_LOG_MSG_DROPPED(TAG, "IR/RF reply"); + } +} + +void IrRfEntity::finish_api_reply_(bool success) { + this->api_reply_ = success ? ApiReply::API_REPLY_OWED_OK : ApiReply::API_REPLY_OWED_FAILED; + if (!this->send_api_reply_()) { + // the retry gets its own window + this->api_reply_deadline_ = api_reply_ticks_from_now(); + } +} + +bool IrRfEntity::send_api_reply_() { + uint32_t device_id = 0; +#ifdef USE_DEVICES + device_id = this->get_device_id(); +#endif + // Refused by a full TCP buffer: the reply stays owed and loop() retries it, since a lost + // reply would stall the client's pacing for good (same shape as bluetooth_proxy) + if (!this->api_reply_connection_->send_infrared_rf_transmit_complete(device_id, this->get_object_id_hash(), + this->api_reply_ == ApiReply::API_REPLY_OWED_OK)) + return false; + this->clear_api_reply_(); + return true; +} + +// Only runs while an API reply is pending: retries an owed one until it goes out or the client is +// gone, and expires a transmit that never reported (a platform overriding control() without wiring +// its transmitter's completion) +void IrRfEntity::loop() { + if (this->api_reply_ == ApiReply::API_REPLY_NONE) { + this->disable_loop(); + return; + } + const auto remaining = static_cast(this->api_reply_deadline_ - (App.get_loop_component_start_time() >> 4)); + if (this->api_reply_ != ApiReply::API_REPLY_WAITING) { + if (this->send_api_reply_() || remaining > 0) + return; + ESP_LOGW(TAG, "'%s': transmit %s", this->get_name().c_str(), LOG_STR_LITERAL("reply dropped, client not reading")); + this->clear_api_reply_(); + return; + } + if (remaining > 0) + return; + ESP_LOGW(TAG, "'%s': transmit %s", this->get_name().c_str(), LOG_STR_LITERAL("never reported completion")); + this->finish_api_reply_(false); +} + +void IrRfEntity::on_api_connection_closed(api::APIConnection *conn) { + if (this->api_reply_connection_ == conn) { + this->clear_api_reply_(); + } +} +#endif + +} // namespace esphome::ir_rf_base + +#ifdef USE_IR_RF_TRANSMIT_COMPLETE +namespace esphome::remote_base { + +void ir_rf_transmit_complete(RemoteTransmitterBase *transmitter, uint16_t seq, bool sent) { +#ifdef USE_INFRARED + for (auto *entity : App.get_infrareds()) { + entity->on_transmit_complete(transmitter, seq, sent); + } +#endif +#ifdef USE_RADIO_FREQUENCY + for (auto *entity : App.get_radio_frequencies()) { + entity->on_transmit_complete(transmitter, seq, sent); + } +#endif +} + +} // namespace esphome::remote_base +#endif diff --git a/esphome/components/ir_rf_base/ir_rf_base.h b/esphome/components/ir_rf_base/ir_rf_base.h new file mode 100644 index 00000000000..c6dc8776968 --- /dev/null +++ b/esphome/components/ir_rf_base/ir_rf_base.h @@ -0,0 +1,273 @@ +#pragma once + +// WARNING: This component is EXPERIMENTAL. The API may change at any time +// without following the normal breaking changes policy. Use at your own risk. +// Once the API is considered stable, this warning will be removed. + +#include "esphome/core/component.h" +#include "esphome/core/entity_base.h" +#include "esphome/core/helpers.h" +#include "esphome/components/remote_base/remote_base.h" + +#include +#include + +#if defined(USE_API) && defined(USE_IR_RF) +namespace esphome::api { +class APIConnection; +} // namespace esphome::api +#endif + +namespace esphome::ir_rf_base { + +/// Capability flags reported by infrared and radio frequency entities +enum IrRfCapability : uint32_t { + CAPABILITY_TRANSMITTER = 1 << 0, // Can transmit signals + CAPABILITY_RECEIVER = 1 << 1, // Can receive signals +}; + +/// Raw timings of a transmit call, in one of three caller-owned forms +class IrRfCallData { + public: + /// Get the raw timings (only valid if set via set_raw_timings) + const std::vector &get_raw_timings() const { return *this->raw_timings_; } + /// Check if raw timings have been set (any format) + bool has_raw_timings() const { + return this->raw_timings_ != nullptr || this->packed_data_ != nullptr || this->base64url_ptr_ != nullptr; + } + /// Check if using packed data format + bool is_packed() const { return this->packed_data_ != nullptr; } + /// Check if using base64url data format + bool is_base64url() const { return this->base64url_ptr_ != nullptr; } + /// Get the base64url data string + const std::string &get_base64url_data() const { return *this->base64url_ptr_; } + /// Get packed data (only valid if set via set_raw_timings_packed) + const uint8_t *get_packed_data() const { return this->packed_data_; } + uint16_t get_packed_length() const { return this->packed_length_; } + uint16_t get_packed_count() const { return this->packed_count_; } + /// Get the repeat count + uint32_t get_repeat_count() const { return this->repeat_count_; } + +#if defined(USE_API) && defined(USE_IR_RF) + /// True for a frame an API client is waiting on + bool wants_api_reply() const { return this->api_connection_ != nullptr; } +#endif + + protected: + uint32_t repeat_count_{1}; +#if defined(USE_API) && defined(USE_IR_RF) + api::APIConnection *api_connection_{nullptr}; +#endif + // Pointer to vector-based timings (caller-owned, must outlive perform()) + const std::vector *raw_timings_{nullptr}; + // Pointer to base64url-encoded string (caller-owned, must outlive perform()) + const std::string *base64url_ptr_{nullptr}; + // Pointer to packed protobuf buffer (caller-owned, must outlive perform()) + const uint8_t *packed_data_{nullptr}; + uint16_t packed_length_{0}; + uint16_t packed_count_{0}; +}; + +template class IrRfCall; + +/// Everything an infrared or radio frequency entity does that does not depend on the medium: +/// the remote_base transport, forwarding received frames to the API, and answering the API +/// once a transmit it started has left the transmitter. +class IrRfEntity : public Component, public EntityBase, public remote_base::RemoteReceiverListener { + public: + /// Reports the configured transports, listens on the receiver and hooks the transmitter's + /// completion; a platform with its own setup() calls it first + void setup() override; + float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; } + + /// 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; } + bool has_transmitter() const { return this->transmitter_ != nullptr; } + bool has_receiver() const { return this->receiver_ != nullptr; } + + /// What the entity can do; platforms with their own hardware set these from their own + /// setup(), remote_base ones get them from IrRfEntity::setup() + bool get_supports_transmitter() const { return this->supports_transmitter_; } + void set_supports_transmitter(bool supports) { this->supports_transmitter_ = supports; } + bool get_supports_receiver() const { return this->supports_receiver_; } + void set_supports_receiver(bool supports) { this->supports_receiver_ = supports; } + /// Capability flags as reported to the API and web server + uint32_t get_capability_flags() const { + uint32_t flags = 0; + if (this->supports_transmitter_) + flags |= CAPABILITY_TRANSMITTER; + if (this->supports_receiver_) + flags |= CAPABILITY_RECEIVER; + return flags; + } + + /// Forwards a received frame to the API; never consumes it, so other listeners still run + bool on_receive(remote_base::RemoteReceiveData data) override; +#ifdef USE_IR_RF_TRANSMIT_COMPLETE + /// Called for every finished frame on every transmitter; answers the API request when the + /// frame is the one this entity submitted for it, with sent as the outcome + void on_transmit_complete(remote_base::RemoteTransmitterBase *transmitter, uint16_t seq, bool sent); +#endif + +#if defined(USE_API) && defined(USE_IR_RF) + void loop() override; + /// The API server calls this when a client disconnects, so no reply goes to a stale pointer + void on_api_connection_closed(api::APIConnection *conn); +#endif + + protected: + template friend class IrRfCall; + + /// Hands the call's timings to the transmitter; the default transmit path of both entity types + bool transmit_raw_(const IrRfCallData &call, uint32_t carrier_frequency_hz); +#if defined(USE_API) && defined(USE_IR_RF) + // One reply slot: a pacing client has at most one transmit outstanding, and a second request + // from an unpaced client displaces the first. Retried and expired from loop(), which only + // runs while a reply is pending; polling the expiry there costs nothing while idle, where a + // scheduler timeout would allocate per frame. + enum class ApiReply : uint8_t { + API_REPLY_NONE, + API_REPLY_WAITING, // frame handed to the transmitter, completion not reported yet + API_REPLY_OWED_OK, // reply refused by a full TCP buffer; loop() retries it + API_REPLY_OWED_FAILED // same, for a transmit that did not start + }; + /// Claims the reply slot for conn; false when it still owes a reply that cannot be sent + bool expect_api_reply_(api::APIConnection *conn); + void refuse_api_call_(api::APIConnection *conn); + /// After control(): a false start is answered now, and the loop only runs once something is + /// pending, since a blocking transmitter has already answered inside control() + void settle_api_reply_(bool started) { +#ifdef USE_IR_RF_TRANSMIT_COMPLETE + if (!started) + this->finish_api_reply_(false); +#else + // no transmitter in this build reports completion, so the hand-over is the answer + this->finish_api_reply_(started); +#endif + if (this->api_reply_ != ApiReply::API_REPLY_NONE) + this->enable_loop(); + } + void finish_api_reply_(bool success); + /// Answers the API request for a control() override that transmits without a remote_base transmitter + void api_transmit_done_(bool sent) { + if (this->api_reply_ == ApiReply::API_REPLY_WAITING) + this->finish_api_reply_(sent); + } + bool send_api_reply_(); + void clear_api_reply_() { + this->api_reply_ = ApiReply::API_REPLY_NONE; + this->api_reply_connection_ = nullptr; + this->disable_loop(); + } + api::APIConnection *api_reply_connection_{nullptr}; +#endif + + remote_base::RemoteReceiverBase *receiver_{nullptr}; + remote_base::RemoteTransmitterBase *transmitter_{nullptr}; +#if defined(USE_API) && defined(USE_IR_RF) + // 16 ms ticks: 30 s for completion (plus capped air time) or for delivering an owed reply + uint16_t api_reply_deadline_{0}; +#endif +#ifdef USE_IR_RF_TRANSMIT_COMPLETE + uint16_t inflight_seq_{0}; // seq of the API frame this entity submitted last +#endif + // short members last, so the derived traits start on the next word without a gap + bool supports_transmitter_{false}; + bool supports_receiver_{false}; +#if defined(USE_API) && defined(USE_IR_RF) + ApiReply api_reply_{ApiReply::API_REPLY_NONE}; +#endif +}; + +/// Builder for a transmit; Call is the concrete call type and Entity its entity, so the fluent +/// setters return the concrete type and control() sees the medium specific fields +template class IrRfCall : public IrRfCallData { + // only the concrete call may construct the CRTP base + friend Call; + explicit IrRfCall(Entity *parent) : parent_(parent) {} + + public: + // ===== Raw Timings Methods ===== + // All set_raw_timings_* methods store pointers/references to external data. + // The referenced data must remain valid until perform() completes. + // Safe pattern: call.set_raw_timings_xxx(data); call.perform(); // synchronous + // Unsafe pattern: call.set_raw_timings_xxx(data); defer([call]() { call.perform(); }); // data may be gone! + + /// Set the raw timings from a vector (positive = mark, negative = space) + /// @note Lifetime: Stores a pointer to the vector. The vector must outlive perform(). + /// @note Usage: Primarily for lambdas/automations where the vector is in scope. + Call &set_raw_timings(const std::vector &timings) { + this->raw_timings_ = &timings; + this->packed_data_ = nullptr; + this->base64url_ptr_ = nullptr; + return this->self_(); + } + + /// Set the raw timings from base64url-encoded little-endian int32 data + /// @note Lifetime: Stores a pointer to the string. The string must outlive perform(). + /// @note Usage: For web_server - base64url is fully URL-safe (uses '-' and '_'). + /// @note Decoding happens at perform() time, directly into the transmit buffer. + Call &set_raw_timings_base64url(const std::string &base64url) { + this->base64url_ptr_ = &base64url; + this->raw_timings_ = nullptr; + this->packed_data_ = nullptr; + return this->self_(); + } + + /// Set the raw timings from packed protobuf sint32 data (zigzag + varint encoded) + /// @note Lifetime: Stores a pointer to the buffer. The buffer must outlive perform(). + /// @note Usage: For API component where data comes directly from the protobuf message. + Call &set_raw_timings_packed(const uint8_t *data, uint16_t length, uint16_t count) { + this->packed_data_ = data; + this->packed_length_ = length; + this->packed_count_ = count; + this->raw_timings_ = nullptr; + this->base64url_ptr_ = nullptr; + return this->self_(); + } + + /// Set the number of times to repeat transmission (1 = transmit once, 2 = transmit twice, etc.) + Call &set_repeat_count(uint32_t count) { + this->repeat_count_ = count; + return this->self_(); + } + +#if defined(USE_API) && defined(USE_IR_RF) + /// Reply to this API client once the frame has left the transmitter (API 1.18+) + Call &set_api_connection(api::APIConnection *conn) { + this->api_connection_ = conn; + return this->self_(); + } +#endif + + /// Perform the transmission; returns true if a frame was handed to the transmitter + bool perform() { + // make_call() always sets the parent + Entity *parent = this->parent_; + if (parent == nullptr) + return false; + parent->on_call_(this->self_()); +#if defined(USE_API) && defined(USE_IR_RF) + // Before control(): blocking transmitters report completion from inside it, and the + // non-blocking RMT path flushes the previous frame's completion there + if (this->api_connection_ != nullptr && !parent->expect_api_reply_(this->api_connection_)) + return false; +#endif + const bool started = parent->control(this->self_()); +#if defined(USE_API) && defined(USE_IR_RF) + if (this->api_connection_ != nullptr) + parent->settle_api_reply_(started); +#endif + return started; + } + + protected: + Call &self_() { return static_cast(*this); } + + Entity *parent_; +}; + +} // namespace esphome::ir_rf_base diff --git a/esphome/components/ir_rf_proxy/__init__.py b/esphome/components/ir_rf_proxy/__init__.py index bc4079ede7e..45bce4ac6f3 100644 --- a/esphome/components/ir_rf_proxy/__init__.py +++ b/esphome/components/ir_rf_proxy/__init__.py @@ -3,6 +3,7 @@ import esphome.codegen as cg CODEOWNERS = ["@kbx81"] +DOMAIN = "ir_rf_proxy" # Namespace and constants exported for infrared.py platform ir_rf_proxy_ns = cg.esphome_ns.namespace("ir_rf_proxy") diff --git a/esphome/components/ir_rf_proxy/infrared.py b/esphome/components/ir_rf_proxy/infrared.py index 288bd916738..548ba2b1326 100644 --- a/esphome/components/ir_rf_proxy/infrared.py +++ b/esphome/components/ir_rf_proxy/infrared.py @@ -3,12 +3,7 @@ from typing import Any import esphome.codegen as cg -from esphome.components import ( - infrared, - remote_base, - remote_receiver, - remote_transmitter, -) +from esphome.components import infrared, ir_rf_base, remote_base, remote_receiver from esphome.components.const import CONF_RECEIVER_FREQUENCY import esphome.config_validation as cv from esphome.const import CONF_CARRIER_DUTY_PERCENT, CONF_FREQUENCY @@ -30,7 +25,7 @@ CONFIG_SCHEMA = cv.All( remote_receiver.RemoteReceiverComponent ), cv.Optional(CONF_REMOTE_TRANSMITTER_ID): cv.use_id( - remote_transmitter.RemoteTransmitterComponent + remote_base.RemoteTransmitterBase ), } ), @@ -82,8 +77,7 @@ async def to_code(config: dict[str, Any]) -> None: # Link transmitter if specified if CONF_REMOTE_TRANSMITTER_ID in config: - transmitter = await cg.get_variable(config[CONF_REMOTE_TRANSMITTER_ID]) - cg.add(var.set_transmitter(transmitter)) + await ir_rf_base.attach_transmitter(var, config, CONF_REMOTE_TRANSMITTER_ID) # Link receiver if specified if CONF_REMOTE_RECEIVER_ID in config: diff --git a/esphome/components/ir_rf_proxy/ir_rf_proxy.cpp b/esphome/components/ir_rf_proxy/ir_rf_proxy.cpp index ceb4c9a67c3..0eae11d1a51 100644 --- a/esphome/components/ir_rf_proxy/ir_rf_proxy.cpp +++ b/esphome/components/ir_rf_proxy/ir_rf_proxy.cpp @@ -6,72 +6,19 @@ namespace esphome::ir_rf_proxy { -static const char *const TAG = "ir_rf_proxy"; - -// ========== Shared transmit helper ========== -// Static template: all instantiations occur in this translation unit. - -template -static void transmit_raw_timings(remote_base::RemoteTransmitterBase *transmitter, uint32_t carrier_frequency, - const CallT &call) { - if (transmitter == nullptr) { - ESP_LOGW(TAG, "No transmitter configured"); - return; - } - - if (!call.has_raw_timings()) { - ESP_LOGE(TAG, "No raw timings provided"); - return; - } - - auto transmit_call = transmitter->transmit(); - auto *transmit_data = transmit_call.get_data(); - transmit_data->set_carrier_frequency(carrier_frequency); - - if (call.is_packed()) { - transmit_data->set_data_from_packed_sint32(call.get_packed_data(), call.get_packed_length(), - call.get_packed_count()); - ESP_LOGD(TAG, "Transmitting packed raw timings: count=%" PRIu16 ", repeat=%" PRIu32, call.get_packed_count(), - call.get_repeat_count()); - } else if (call.is_base64url()) { - if (!transmit_data->set_data_from_base64url(call.get_base64url_data())) { - ESP_LOGE(TAG, "Invalid base64url data"); - return; - } - constexpr int32_t max_timing_us = 500000; - for (int32_t timing : transmit_data->get_data()) { - int32_t abs_timing = timing < 0 ? -timing : timing; - if (abs_timing > max_timing_us) { - ESP_LOGE(TAG, "Invalid timing value: %" PRId32 " µs (max %" PRId32 ")", timing, max_timing_us); - return; - } - } - ESP_LOGD(TAG, "Transmitting base64url raw timings: count=%zu, repeat=%" PRIu32, transmit_data->get_data().size(), - call.get_repeat_count()); - } else { - transmit_data->set_data(call.get_raw_timings()); - ESP_LOGD(TAG, "Transmitting raw timings: count=%zu, repeat=%" PRIu32, call.get_raw_timings().size(), - call.get_repeat_count()); - } - - if (call.get_repeat_count() > 0) { - transmit_call.set_send_times(call.get_repeat_count()); - } - - transmit_call.perform(); -} +ESPHOME_LOG_TAG(TAG, "ir_rf_proxy"); // ========== IrRfProxy (Infrared platform) ========== -#ifdef USE_IR_RF +#ifdef USE_INFRARED void IrRfProxy::dump_config() { ESP_LOGCONFIG(TAG, "IR Proxy '%s'\n" " Supports Transmitter: %s\n" " Supports Receiver: %s", - this->get_name().c_str(), YESNO(this->traits_.get_supports_transmitter()), - YESNO(this->traits_.get_supports_receiver())); + this->get_name().c_str(), YESNO(this->get_supports_transmitter()), + YESNO(this->get_supports_receiver())); if (this->is_rf()) { ESP_LOGCONFIG(TAG, " Hardware Type: RF (%.3f MHz)", this->frequency_khz_ / 1e3f); @@ -80,21 +27,14 @@ void IrRfProxy::dump_config() { } } -void IrRfProxy::control(const infrared::InfraredCall &call) { - uint32_t carrier = call.get_carrier_frequency().value_or(0); - transmit_raw_timings(this->transmitter_, carrier, call); -} - -#endif // USE_IR_RF +#endif // USE_INFRARED // ========== RfProxy (Radio Frequency platform) ========== #ifdef USE_RADIO_FREQUENCY void RfProxy::setup() { - this->traits_.set_supports_transmitter(this->transmitter_ != nullptr); - this->traits_.set_supports_receiver(this->receiver_ != nullptr); - + ir_rf_base::IrRfEntity::setup(); // remote_transmitter/receiver always uses OOK (on-off keying) this->traits_.add_supported_modulation(radio_frequency::RadioFrequencyModulation::RADIO_FREQUENCY_MODULATION_OOK); } @@ -104,8 +44,8 @@ void RfProxy::dump_config() { "RF Proxy '%s'\n" " Supports Transmitter: %s\n" " Supports Receiver: %s", - this->get_name().c_str(), YESNO(this->traits_.get_supports_transmitter()), - YESNO(this->traits_.get_supports_receiver())); + this->get_name().c_str(), YESNO(this->get_supports_transmitter()), + YESNO(this->get_supports_receiver())); const auto &traits = this->traits_; if (traits.get_frequency_min_hz() > 0) { @@ -118,11 +58,11 @@ void RfProxy::dump_config() { } } -void RfProxy::control(const radio_frequency::RadioFrequencyCall &call) { +bool RfProxy::control(const radio_frequency::RadioFrequencyCall &call) { // RF: no IR carrier modulation. Any RF front-end coordination (state turnaround, retuning) // happens via the radio_frequency entity's on_control trigger and remote_transmitter's // on_transmit/on_complete triggers — wired up in user YAML. - transmit_raw_timings(this->transmitter_, 0, call); + return this->transmit_raw_(call, 0); } #endif // USE_RADIO_FREQUENCY diff --git a/esphome/components/ir_rf_proxy/ir_rf_proxy.h b/esphome/components/ir_rf_proxy/ir_rf_proxy.h index 1aa4394fe84..a45b0ac4722 100644 --- a/esphome/components/ir_rf_proxy/ir_rf_proxy.h +++ b/esphome/components/ir_rf_proxy/ir_rf_proxy.h @@ -6,7 +6,7 @@ #include "esphome/components/remote_base/remote_base.h" -#ifdef USE_IR_RF +#ifdef USE_INFRARED #include "esphome/components/infrared/infrared.h" #endif @@ -16,7 +16,7 @@ namespace esphome::ir_rf_proxy { -#ifdef USE_IR_RF +#ifdef USE_INFRARED /// IrRfProxy - Infrared platform implementation using remote_transmitter/receiver as backend class IrRfProxy final : public infrared::Infrared { public: @@ -35,12 +35,10 @@ class IrRfProxy final : public infrared::Infrared { void set_receiver_frequency(uint32_t frequency_hz) { this->get_traits().set_receiver_frequency_hz(frequency_hz); } protected: - void control(const infrared::InfraredCall &call) override; - // RF frequency in kHz (Hz / 1000); 0 = infrared, non-zero = RF uint32_t frequency_khz_{0}; }; -#endif // USE_IR_RF +#endif // USE_INFRARED #ifdef USE_RADIO_FREQUENCY /// RfProxy - Radio Frequency platform implementation using remote_transmitter/receiver as backend. @@ -54,20 +52,11 @@ class RfProxy final : public radio_frequency::RadioFrequency { void setup() override; void dump_config() override; - /// Set the remote transmitter component - void set_transmitter(remote_base::RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; } - /// 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) void set_frequency_hz(uint32_t freq_hz) { this->traits_.set_fixed_frequency_hz(freq_hz); } protected: - void control(const radio_frequency::RadioFrequencyCall &call) override; - - remote_base::RemoteTransmitterBase *transmitter_{nullptr}; - remote_base::RemoteReceiverBase *receiver_{nullptr}; + bool control(const radio_frequency::RadioFrequencyCall &call) override; }; #endif // USE_RADIO_FREQUENCY diff --git a/esphome/components/ir_rf_proxy/radio_frequency.py b/esphome/components/ir_rf_proxy/radio_frequency.py index 28b8fd5953a..1519fe9908a 100644 --- a/esphome/components/ir_rf_proxy/radio_frequency.py +++ b/esphome/components/ir_rf_proxy/radio_frequency.py @@ -1,12 +1,7 @@ """Radio Frequency platform implementation using remote_base (remote_transmitter/receiver).""" import esphome.codegen as cg -from esphome.components import ( - radio_frequency, - remote_base, - remote_receiver, - remote_transmitter, -) +from esphome.components import ir_rf_base, radio_frequency, remote_base, remote_receiver import esphome.config_validation as cv from esphome.const import CONF_CARRIER_DUTY_PERCENT, CONF_FREQUENCY import esphome.final_validate as fv @@ -27,7 +22,7 @@ CONFIG_SCHEMA = cv.All( remote_receiver.RemoteReceiverComponent ), cv.Optional(CONF_REMOTE_TRANSMITTER_ID): cv.use_id( - remote_transmitter.RemoteTransmitterComponent + remote_base.RemoteTransmitterBase ), } ), @@ -67,8 +62,7 @@ async def to_code(config: ConfigType) -> None: cg.add(var.set_frequency_hz(int(config[CONF_FREQUENCY]))) if CONF_REMOTE_TRANSMITTER_ID in config: - transmitter = await cg.get_variable(config[CONF_REMOTE_TRANSMITTER_ID]) - cg.add(var.set_transmitter(transmitter)) + await ir_rf_base.attach_transmitter(var, config, CONF_REMOTE_TRANSMITTER_ID) if CONF_REMOTE_RECEIVER_ID in config: await remote_base.attach_receiver(var, config, CONF_REMOTE_RECEIVER_ID) diff --git a/esphome/components/it8951/__init__.py b/esphome/components/it8951/__init__.py index 7fc4ae2cd0e..94bdf451d32 100644 --- a/esphome/components/it8951/__init__.py +++ b/esphome/components/it8951/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@Passific", "@koosoli", "@limengdu"] +DOMAIN = "it8951" diff --git a/esphome/components/it8951/it8951.cpp b/esphome/components/it8951/it8951.cpp index a6511b63c41..2cfcf111ea3 100644 --- a/esphome/components/it8951/it8951.cpp +++ b/esphome/components/it8951/it8951.cpp @@ -10,7 +10,7 @@ namespace esphome::it8951 { -static const char *const TAG = "it8951"; +ESPHOME_LOG_TAG(TAG, "it8951"); // Soft cap for time spent in a single XFER_ROWS Op so we yield back to the // loop within one tick budget. diff --git a/esphome/components/jsn_sr04t/__init__.py b/esphome/components/jsn_sr04t/__init__.py index ef6335f3167..672487d32aa 100644 --- a/esphome/components/jsn_sr04t/__init__.py +++ b/esphome/components/jsn_sr04t/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@Mafus1"] +DOMAIN = "jsn_sr04t" diff --git a/esphome/components/jsn_sr04t/jsn_sr04t.cpp b/esphome/components/jsn_sr04t/jsn_sr04t.cpp index c67771a0b60..0c7dd277381 100644 --- a/esphome/components/jsn_sr04t/jsn_sr04t.cpp +++ b/esphome/components/jsn_sr04t/jsn_sr04t.cpp @@ -6,7 +6,7 @@ namespace esphome::jsn_sr04t { -static const char *const TAG = "jsn_sr04t.sensor"; +ESPHOME_LOG_TAG(TAG, "jsn_sr04t.sensor"); void Jsnsr04tComponent::update() { this->write_byte((this->model_ == AJ_SR04M) ? 0x01 : 0x55); diff --git a/esphome/components/json/__init__.py b/esphome/components/json/__init__.py index 618c56faa14..cac06a29d24 100644 --- a/esphome/components/json/__init__.py +++ b/esphome/components/json/__init__.py @@ -4,6 +4,7 @@ from esphome.core import CORE, CoroPriority, coroutine_with_priority from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] +DOMAIN = "json" json_ns = cg.esphome_ns.namespace("json") CONFIG_SCHEMA = cv.All( diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 3ecfce8b699..28ba34d6ca5 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -7,7 +7,7 @@ namespace esphome::json { -static const char *const TAG = "json"; +ESPHOME_LOG_TAG(TAG, "json"); #ifdef USE_PSRAM // Global allocator that outlives all JsonDocuments returned by parse_json() diff --git a/esphome/components/kalman_combinator/__init__.py b/esphome/components/kalman_combinator/__init__.py index e69de29bb2d..35782ff625c 100644 --- a/esphome/components/kalman_combinator/__init__.py +++ b/esphome/components/kalman_combinator/__init__.py @@ -0,0 +1 @@ +DOMAIN = "kalman_combinator" diff --git a/esphome/components/kamstrup_kmp/__init__.py b/esphome/components/kamstrup_kmp/__init__.py index e69de29bb2d..2446761ce9b 100644 --- a/esphome/components/kamstrup_kmp/__init__.py +++ b/esphome/components/kamstrup_kmp/__init__.py @@ -0,0 +1 @@ +DOMAIN = "kamstrup_kmp" diff --git a/esphome/components/kamstrup_kmp/kamstrup_kmp.cpp b/esphome/components/kamstrup_kmp/kamstrup_kmp.cpp index 24e5d259218..9713c72b613 100644 --- a/esphome/components/kamstrup_kmp/kamstrup_kmp.cpp +++ b/esphome/components/kamstrup_kmp/kamstrup_kmp.cpp @@ -5,7 +5,7 @@ namespace esphome::kamstrup_kmp { -static const char *const TAG = "kamstrup_kmp"; +ESPHOME_LOG_TAG(TAG, "kamstrup_kmp"); void KamstrupKMPComponent::dump_config() { ESP_LOGCONFIG(TAG, "kamstrup_kmp:"); diff --git a/esphome/components/key_collector/__init__.py b/esphome/components/key_collector/__init__.py index dedd960c368..8dafb5da070 100644 --- a/esphome/components/key_collector/__init__.py +++ b/esphome/components/key_collector/__init__.py @@ -19,6 +19,7 @@ from esphome.cpp_generator import MockObj, literal from esphome.types import ConfigType, TemplateArgsType CODEOWNERS = ["@ssieb"] +DOMAIN = "key_collector" MULTI_CONF = True diff --git a/esphome/components/key_collector/key_collector.cpp b/esphome/components/key_collector/key_collector.cpp index 42f02d39d4f..bc201a15146 100644 --- a/esphome/components/key_collector/key_collector.cpp +++ b/esphome/components/key_collector/key_collector.cpp @@ -4,7 +4,7 @@ namespace esphome::key_collector { -static const char *const TAG = "key_collector"; +ESPHOME_LOG_TAG(TAG, "key_collector"); void KeyCollector::loop() { if ((this->timeout_ == 0) || this->result_.empty() || (millis() - this->last_key_time_ < this->timeout_)) diff --git a/esphome/components/key_provider/__init__.py b/esphome/components/key_provider/__init__.py index f397382ff24..3008545f40e 100644 --- a/esphome/components/key_provider/__init__.py +++ b/esphome/components/key_provider/__init__.py @@ -1,6 +1,7 @@ import esphome.codegen as cg CODEOWNERS = ["@ssieb"] +DOMAIN = "key_provider" key_provider_ns = cg.esphome_ns.namespace("key_provider") KeyProvider = key_provider_ns.class_("KeyProvider") diff --git a/esphome/components/kmeteriso/__init__.py b/esphome/components/kmeteriso/__init__.py index e69de29bb2d..33163580966 100644 --- a/esphome/components/kmeteriso/__init__.py +++ b/esphome/components/kmeteriso/__init__.py @@ -0,0 +1 @@ +DOMAIN = "kmeteriso" diff --git a/esphome/components/kmeteriso/kmeteriso.cpp b/esphome/components/kmeteriso/kmeteriso.cpp index d6934a97ac0..8bd8b47e21a 100644 --- a/esphome/components/kmeteriso/kmeteriso.cpp +++ b/esphome/components/kmeteriso/kmeteriso.cpp @@ -5,7 +5,7 @@ namespace esphome::kmeteriso { -static const char *const TAG = "kmeteriso.sensor"; +ESPHOME_LOG_TAG(TAG, "kmeteriso.sensor"); static const uint8_t KMETER_ERROR_STATUS_REG = 0x20; static const uint8_t KMETER_TEMP_VAL_REG = 0x00; diff --git a/esphome/components/kuntze/__init__.py b/esphome/components/kuntze/__init__.py index e69de29bb2d..34a9637a090 100644 --- a/esphome/components/kuntze/__init__.py +++ b/esphome/components/kuntze/__init__.py @@ -0,0 +1 @@ +DOMAIN = "kuntze" diff --git a/esphome/components/kuntze/kuntze.cpp b/esphome/components/kuntze/kuntze.cpp index cb04afe4377..647227524e6 100644 --- a/esphome/components/kuntze/kuntze.cpp +++ b/esphome/components/kuntze/kuntze.cpp @@ -3,7 +3,7 @@ namespace esphome::kuntze { -static const char *const TAG = "kuntze"; +ESPHOME_LOG_TAG(TAG, "kuntze"); static constexpr uint16_t REGISTER_PH = 4136; static constexpr uint16_t REGISTER_TEMPERATURE = 4160; diff --git a/esphome/components/lc709203f/__init__.py b/esphome/components/lc709203f/__init__.py index 3be68d174fb..3076e385274 100644 --- a/esphome/components/lc709203f/__init__.py +++ b/esphome/components/lc709203f/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@ilikecake"] +DOMAIN = "lc709203f" diff --git a/esphome/components/lc709203f/lc709203f.cpp b/esphome/components/lc709203f/lc709203f.cpp index 36e5bce8e3c..a97f5217a85 100644 --- a/esphome/components/lc709203f/lc709203f.cpp +++ b/esphome/components/lc709203f/lc709203f.cpp @@ -4,7 +4,7 @@ namespace esphome::lc709203f { -static const char *const TAG = "lc709203f.sensor"; +ESPHOME_LOG_TAG(TAG, "lc709203f.sensor"); // Device I2C address. This address is fixed. static const uint8_t LC709203F_I2C_ADDR_DEFAULT = 0x0B; diff --git a/esphome/components/lcd_base/__init__.py b/esphome/components/lcd_base/__init__.py index 08ec3957200..58efbd4884c 100644 --- a/esphome/components/lcd_base/__init__.py +++ b/esphome/components/lcd_base/__init__.py @@ -7,6 +7,8 @@ from esphome.const import CONF_DATA, CONF_DIMENSIONS, CONF_POSITION from esphome.cpp_generator import MockObj from esphome.types import ConfigType +DOMAIN = "lcd_base" + CONF_USER_CHARACTERS = "user_characters" lcd_base_ns = cg.esphome_ns.namespace("lcd_base") diff --git a/esphome/components/lcd_base/lcd_display.cpp b/esphome/components/lcd_base/lcd_display.cpp index 0890da85c13..101e858bef3 100644 --- a/esphome/components/lcd_base/lcd_display.cpp +++ b/esphome/components/lcd_base/lcd_display.cpp @@ -5,7 +5,7 @@ namespace esphome::lcd_base { -static const char *const TAG = "lcd"; +ESPHOME_LOG_TAG(TAG, "lcd"); // First set bit determines command, bits after that are the data. static const uint8_t LCD_DISPLAY_COMMAND_CLEAR_DISPLAY = 0x01; diff --git a/esphome/components/lcd_gpio/__init__.py b/esphome/components/lcd_gpio/__init__.py index e69de29bb2d..9df8039c3bf 100644 --- a/esphome/components/lcd_gpio/__init__.py +++ b/esphome/components/lcd_gpio/__init__.py @@ -0,0 +1 @@ +DOMAIN = "lcd_gpio" diff --git a/esphome/components/lcd_gpio/gpio_lcd_display.cpp b/esphome/components/lcd_gpio/gpio_lcd_display.cpp index 213fc8637e7..92ae37eab89 100644 --- a/esphome/components/lcd_gpio/gpio_lcd_display.cpp +++ b/esphome/components/lcd_gpio/gpio_lcd_display.cpp @@ -3,7 +3,7 @@ namespace esphome::lcd_gpio { -static const char *const TAG = "lcd_gpio"; +ESPHOME_LOG_TAG(TAG, "lcd_gpio"); void GPIOLCDDisplay::setup() { this->rs_pin_->setup(); // OUTPUT diff --git a/esphome/components/lcd_menu/__init__.py b/esphome/components/lcd_menu/__init__.py index 88b8ac21d42..483a36491db 100644 --- a/esphome/components/lcd_menu/__init__.py +++ b/esphome/components/lcd_menu/__init__.py @@ -11,6 +11,7 @@ from esphome.core.entity_helpers import inherit_property_from from esphome.types import ConfigType CODEOWNERS = ["@numo68"] +DOMAIN = "lcd_menu" AUTO_LOAD = ["display_menu_base"] diff --git a/esphome/components/lcd_menu/lcd_menu.cpp b/esphome/components/lcd_menu/lcd_menu.cpp index f731817bdb4..7ced6e67cec 100644 --- a/esphome/components/lcd_menu/lcd_menu.cpp +++ b/esphome/components/lcd_menu/lcd_menu.cpp @@ -4,7 +4,7 @@ namespace esphome::lcd_menu { -static const char *const TAG = "lcd_menu"; +ESPHOME_LOG_TAG(TAG, "lcd_menu"); void LCDCharacterMenuComponent::setup() { if (this->display_->is_failed()) { diff --git a/esphome/components/lcd_pcf8574/__init__.py b/esphome/components/lcd_pcf8574/__init__.py index e69de29bb2d..cfeba4c863d 100644 --- a/esphome/components/lcd_pcf8574/__init__.py +++ b/esphome/components/lcd_pcf8574/__init__.py @@ -0,0 +1 @@ +DOMAIN = "lcd_pcf8574" diff --git a/esphome/components/lcd_pcf8574/pcf8574_display.cpp b/esphome/components/lcd_pcf8574/pcf8574_display.cpp index d5fc6835985..fc7f5ff2f50 100644 --- a/esphome/components/lcd_pcf8574/pcf8574_display.cpp +++ b/esphome/components/lcd_pcf8574/pcf8574_display.cpp @@ -4,7 +4,7 @@ namespace esphome::lcd_pcf8574 { -static const char *const TAG = "lcd_pcf8574"; +ESPHOME_LOG_TAG(TAG, "lcd_pcf8574"); static const uint8_t LCD_DISPLAY_BACKLIGHT_ON = 0x08; static const uint8_t LCD_DISPLAY_BACKLIGHT_OFF = 0x00; diff --git a/esphome/components/ld2410/__init__.py b/esphome/components/ld2410/__init__.py index d3179d9b302..e2728aa73c7 100644 --- a/esphome/components/ld2410/__init__.py +++ b/esphome/components/ld2410/__init__.py @@ -9,6 +9,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["ld24xx"] DEPENDENCIES = ["uart"] CODEOWNERS = ["@sebcaps", "@regevbr"] +DOMAIN = "ld2410" MULTI_CONF = True ld2410_ns = cg.esphome_ns.namespace("ld2410") diff --git a/esphome/components/ld2410/ld2410.cpp b/esphome/components/ld2410/ld2410.cpp index 914de8e1455..22bd20d1e5a 100644 --- a/esphome/components/ld2410/ld2410.cpp +++ b/esphome/components/ld2410/ld2410.cpp @@ -12,7 +12,7 @@ namespace esphome::ld2410 { -static const char *const TAG = "ld2410"; +ESPHOME_LOG_TAG(TAG, "ld2410"); enum BaudRate : uint8_t { BAUD_RATE_9600 = 1, diff --git a/esphome/components/ld2410/sensor.py b/esphome/components/ld2410/sensor.py index 5b523d09442..a8636f3df69 100644 --- a/esphome/components/ld2410/sensor.py +++ b/esphome/components/ld2410/sensor.py @@ -34,83 +34,35 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), cv.Optional(CONF_STILL_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), cv.Optional(CONF_MOVING_ENERGY): sensor.sensor_schema( - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_MOTION_SENSOR, unit_of_measurement=UNIT_PERCENT, ), cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema( - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_FLASH, unit_of_measurement=UNIT_PERCENT, ), cv.Optional(CONF_LIGHT): sensor.sensor_schema( device_class=DEVICE_CLASS_ILLUMINANCE, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_LIGHTBULB, ), cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), @@ -123,29 +75,13 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend( { cv.Optional(CONF_MOVE_ENERGY): sensor.sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_MOTION_SENSOR, unit_of_measurement=UNIT_PERCENT, ), cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_FLASH, unit_of_measurement=UNIT_PERCENT, ), diff --git a/esphome/components/ld2412/__init__.py b/esphome/components/ld2412/__init__.py index 82db3198611..fd63f78c3d4 100644 --- a/esphome/components/ld2412/__init__.py +++ b/esphome/components/ld2412/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["ld24xx"] CODEOWNERS = ["@Rihan9"] DEPENDENCIES = ["uart"] +DOMAIN = "ld2412" MULTI_CONF = True LD2412_ns = cg.esphome_ns.namespace("ld2412") diff --git a/esphome/components/ld2412/ld2412.cpp b/esphome/components/ld2412/ld2412.cpp index 7041b7539fb..bd43d06cc27 100644 --- a/esphome/components/ld2412/ld2412.cpp +++ b/esphome/components/ld2412/ld2412.cpp @@ -12,7 +12,7 @@ namespace esphome::ld2412 { -static const char *const TAG = "ld2412"; +ESPHOME_LOG_TAG(TAG, "ld2412"); enum BaudRate : uint8_t { BAUD_RATE_9600 = 1, diff --git a/esphome/components/ld2412/sensor.py b/esphome/components/ld2412/sensor.py index e236a7d5f38..f350fb70505 100644 --- a/esphome/components/ld2412/sensor.py +++ b/esphome/components/ld2412/sensor.py @@ -34,84 +34,36 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component), cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), cv.Optional(CONF_LIGHT): sensor.sensor_schema( device_class=DEVICE_CLASS_ILLUMINANCE, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_LIGHTBULB, unit_of_measurement=UNIT_EMPTY, # No standard unit for this light sensor ), cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), cv.Optional(CONF_MOVING_ENERGY): sensor.sensor_schema( - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_MOTION_SENSOR, unit_of_measurement=UNIT_PERCENT, ), cv.Optional(CONF_STILL_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_SIGNAL, unit_of_measurement=UNIT_CENTIMETER, ), cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema( - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_FLASH, unit_of_measurement=UNIT_PERCENT, ), @@ -124,29 +76,13 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend( { cv.Optional(CONF_MOVE_ENERGY): sensor.sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_MOTION_SENSOR, unit_of_measurement=UNIT_PERCENT, ), cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema( entity_category=ENTITY_CATEGORY_DIAGNOSTIC, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_FLASH, unit_of_measurement=UNIT_PERCENT, ), diff --git a/esphome/components/ld2420/__init__.py b/esphome/components/ld2420/__init__.py index 5a5aabeba03..87a54d278a7 100644 --- a/esphome/components/ld2420/__init__.py +++ b/esphome/components/ld2420/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_ID from esphome.types import ConfigType CODEOWNERS = ["@descipher"] +DOMAIN = "ld2420" DEPENDENCIES = ["uart"] diff --git a/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp b/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp index 6297cba2e79..87375ccbd97 100644 --- a/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp +++ b/esphome/components/ld2420/binary_sensor/ld2420_binary_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::ld2420 { -static const char *const TAG = "ld2420.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "ld2420.binary_sensor"); void LD2420BinarySensor::dump_config() { ESP_LOGCONFIG(TAG, "Binary Sensor:"); diff --git a/esphome/components/ld2420/button/reconfig_buttons.cpp b/esphome/components/ld2420/button/reconfig_buttons.cpp index 1e748e59b8f..85e3411f490 100644 --- a/esphome/components/ld2420/button/reconfig_buttons.cpp +++ b/esphome/components/ld2420/button/reconfig_buttons.cpp @@ -2,7 +2,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -static const char *const TAG = "ld2420.button"; +ESPHOME_LOG_TAG(TAG, "ld2420.button"); namespace esphome::ld2420 { diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp index e342ead4148..dec7e42de47 100644 --- a/esphome/components/ld2420/ld2420.cpp +++ b/esphome/components/ld2420/ld2420.cpp @@ -60,7 +60,7 @@ Gate 0 low thresh = 20 00 uint16_t 0x0020, Threshold value = 60 EA 00 00 uint32_ namespace esphome::ld2420 { -static const char *const TAG = "ld2420"; +ESPHOME_LOG_TAG(TAG, "ld2420"); // Local const's static constexpr uint16_t REFRESH_RATE_MS = 1000; diff --git a/esphome/components/ld2420/number/gate_config_number.cpp b/esphome/components/ld2420/number/gate_config_number.cpp index 998eed21881..491cd857349 100644 --- a/esphome/components/ld2420/number/gate_config_number.cpp +++ b/esphome/components/ld2420/number/gate_config_number.cpp @@ -2,7 +2,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -static const char *const TAG = "ld2420.number"; +ESPHOME_LOG_TAG(TAG, "ld2420.number"); namespace esphome::ld2420 { diff --git a/esphome/components/ld2420/select/operating_mode_select.cpp b/esphome/components/ld2420/select/operating_mode_select.cpp index 82939e18d33..6d9bb24cbcd 100644 --- a/esphome/components/ld2420/select/operating_mode_select.cpp +++ b/esphome/components/ld2420/select/operating_mode_select.cpp @@ -4,7 +4,7 @@ namespace esphome::ld2420 { -static const char *const TAG = "ld2420.select"; +ESPHOME_LOG_TAG(TAG, "ld2420.select"); void LD2420Select::control(size_t index) { this->publish_state(index); diff --git a/esphome/components/ld2420/sensor/ld2420_sensor.cpp b/esphome/components/ld2420/sensor/ld2420_sensor.cpp index 02b5826f98d..d8690a6898a 100644 --- a/esphome/components/ld2420/sensor/ld2420_sensor.cpp +++ b/esphome/components/ld2420/sensor/ld2420_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::ld2420 { -static const char *const TAG = "ld2420.sensor"; +ESPHOME_LOG_TAG(TAG, "ld2420.sensor"); void LD2420Sensor::dump_config() { ESP_LOGCONFIG(TAG, "Sensor:"); diff --git a/esphome/components/ld2420/text_sensor/ld2420_text_sensor.cpp b/esphome/components/ld2420/text_sensor/ld2420_text_sensor.cpp index f7b016c9d9e..a8360b1ccbe 100644 --- a/esphome/components/ld2420/text_sensor/ld2420_text_sensor.cpp +++ b/esphome/components/ld2420/text_sensor/ld2420_text_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::ld2420 { -static const char *const TAG = "ld2420.text_sensor"; +ESPHOME_LOG_TAG(TAG, "ld2420.text_sensor"); void LD2420TextSensor::dump_config() { ESP_LOGCONFIG(TAG, "Text Sensor:"); diff --git a/esphome/components/ld2450/__init__.py b/esphome/components/ld2450/__init__.py index 4c37f4fcd1e..a54e83f2875 100644 --- a/esphome/components/ld2450/__init__.py +++ b/esphome/components/ld2450/__init__.py @@ -8,6 +8,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["ld24xx"] DEPENDENCIES = ["uart"] CODEOWNERS = ["@hareeshmu"] +DOMAIN = "ld2450" MULTI_CONF = True ld2450_ns = cg.esphome_ns.namespace("ld2450") diff --git a/esphome/components/ld2450/ld2450.cpp b/esphome/components/ld2450/ld2450.cpp index 4b41d63a88a..b43493c26e9 100644 --- a/esphome/components/ld2450/ld2450.cpp +++ b/esphome/components/ld2450/ld2450.cpp @@ -16,7 +16,7 @@ namespace esphome::ld2450 { -static const char *const TAG = "ld2450"; +ESPHOME_LOG_TAG(TAG, "ld2450"); enum BaudRate : uint8_t { BAUD_RATE_9600 = 1, diff --git a/esphome/components/ld2450/sensor.py b/esphome/components/ld2450/sensor.py index e714d353d1f..6f2056330bd 100644 --- a/esphome/components/ld2450/sensor.py +++ b/esphome/components/ld2450/sensor.py @@ -46,41 +46,17 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema( accuracy_decimals=0, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_ACCOUNT_GROUP, ), cv.Optional(CONF_STILL_TARGET_COUNT): sensor.sensor_schema( accuracy_decimals=0, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_HUMAN_GREETING_PROXIMITY, ), cv.Optional(CONF_MOVING_TARGET_COUNT): sensor.sensor_schema( accuracy_decimals=0, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_ACCOUNT_SWITCH, ), } @@ -92,85 +68,37 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend( { cv.Optional(CONF_X): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_ALPHA_X_BOX_OUTLINE, unit_of_measurement=UNIT_MILLIMETER, ), cv.Optional(CONF_Y): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_ALPHA_Y_BOX_OUTLINE, unit_of_measurement=UNIT_MILLIMETER, ), cv.Optional(CONF_SPEED): sensor.sensor_schema( device_class=DEVICE_CLASS_SPEED, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_SPEEDOMETER_SLOW, unit_of_measurement=UNIT_MILLIMETER_PER_SECOND, ), cv.Optional(CONF_ANGLE): sensor.sensor_schema( - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_FORMAT_TEXT_ROTATION_ANGLE_UP, unit_of_measurement=UNIT_DEGREES, accuracy_decimals=1, ), cv.Optional(CONF_DISTANCE): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_MAP_MARKER_DISTANCE, unit_of_measurement=UNIT_MILLIMETER, ), cv.Optional(CONF_RESOLUTION): sensor.sensor_schema( device_class=DEVICE_CLASS_DISTANCE, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_RELATION_ZERO_OR_ONE_TO_ZERO_OR_ONE, unit_of_measurement=UNIT_MILLIMETER, ), @@ -183,41 +111,17 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend( { cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema( accuracy_decimals=0, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_MAP_MARKER_ACCOUNT, ), cv.Optional(CONF_STILL_TARGET_COUNT): sensor.sensor_schema( accuracy_decimals=0, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_MAP_MARKER_ACCOUNT, ), cv.Optional(CONF_MOVING_TARGET_COUNT): sensor.sensor_schema( accuracy_decimals=0, - filters=[ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, - ], + filters=sensor.TIMEOUT_THROTTLE_FILTERS, icon=ICON_MAP_MARKER_ACCOUNT, ), } diff --git a/esphome/components/ld24xx/__init__.py b/esphome/components/ld24xx/__init__.py index 516af848564..97ff42626c7 100644 --- a/esphome/components/ld24xx/__init__.py +++ b/esphome/components/ld24xx/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@kbx81"] +DOMAIN = "ld24xx" diff --git a/esphome/components/ld6002b/__init__.py b/esphome/components/ld6002b/__init__.py index af1e501a6ab..c7529f93149 100644 --- a/esphome/components/ld6002b/__init__.py +++ b/esphome/components/ld6002b/__init__.py @@ -9,6 +9,7 @@ from .const import CONF_AUTO_WAKE, CONF_WAKEUP_PULSE CODEOWNERS = ["@hepter"] DEPENDENCIES = ["uart"] +DOMAIN = "ld6002b" MULTI_CONF = True ld6002b_ns = cg.esphome_ns.namespace("ld6002b") diff --git a/esphome/components/ld6002b/button/__init__.py b/esphome/components/ld6002b/button/__init__.py index a664890a86a..a33e706eb6e 100644 --- a/esphome/components/ld6002b/button/__init__.py +++ b/esphome/components/ld6002b/button/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import button +from esphome.components.select import DOMAIN as SELECT_DOMAIN import esphome.config_validation as cv from esphome.const import ( CONF_AREA_ID, @@ -91,7 +92,7 @@ def final_validate(config: ConfigType) -> None: if config.get(CONF_APPLY_AREA): has_area_id_select = any( entry.get(CONF_LD6002B_ID) == hub_id and entry.get(CONF_AREA_ID) is not None - for entry in full_config.get("select", []) + for entry in full_config.get(SELECT_DOMAIN, []) ) if not has_area_id_select: raise cv.Invalid( diff --git a/esphome/components/ld6002b/ld6002b.cpp b/esphome/components/ld6002b/ld6002b.cpp index aa34ad9d392..93a2ea341f4 100644 --- a/esphome/components/ld6002b/ld6002b.cpp +++ b/esphome/components/ld6002b/ld6002b.cpp @@ -8,7 +8,7 @@ namespace esphome::ld6002b { -static const char *const TAG = "ld6002b"; +ESPHOME_LOG_TAG(TAG, "ld6002b"); static constexpr uint8_t TF_SOF = 0x01; static constexpr uint32_t SETUP_DELAY_MS = 100; @@ -620,7 +620,7 @@ void LD6002BComponent::handle_frame_(uint16_t type, const uint8_t *data, uint16_ this->process_command_queue_(); if (refresh_areas) { this->area_write_in_flight_ = false; - this->set_timeout(AREA_REFRESH_TIMEOUT, 50, [this]() { this->send_control_command_(CMD_GET_AREAS); }); + this->set_timeout(AREA_REFRESH_TIMEOUT_ID, 50, [this]() { this->send_control_command_(CMD_GET_AREAS); }); } return; } @@ -1215,7 +1215,7 @@ void LD6002BComponent::send_command_internal_(uint16_t type, const uint8_t *data std::memcpy(this->wake_scratch_.data(), data, len); } // A button pulse must not raise the pin in the middle of this one. - this->cancel_timeout(WAKE_BUTTON_TIMEOUT); + this->cancel_timeout(WAKE_BUTTON_TIMEOUT_ID); this->wake_pulse_pending_ = true; this->wakeup_pin_->digital_write(false); const uint8_t generation = this->send_generation_; @@ -1364,7 +1364,8 @@ void LD6002BComponent::wake_() { if (this->wakeup_pin_ == nullptr || this->wake_pulse_pending_) return; this->wakeup_pin_->digital_write(false); - this->set_timeout(WAKE_BUTTON_TIMEOUT, this->wakeup_pulse_ms_, [this]() { this->wakeup_pin_->digital_write(true); }); + this->set_timeout(WAKE_BUTTON_TIMEOUT_ID, this->wakeup_pulse_ms_, + [this]() { this->wakeup_pin_->digital_write(true); }); } void LD6002BComponent::set_number_value(NumberType type, float value) { diff --git a/esphome/components/ld6002b/ld6002b.h b/esphome/components/ld6002b/ld6002b.h index bea38043128..c52f770d6e9 100644 --- a/esphome/components/ld6002b/ld6002b.h +++ b/esphome/components/ld6002b/ld6002b.h @@ -413,12 +413,12 @@ class LD6002BComponent : public Component, public uart::UARTDevice { // How long the module stays awake after any frame, and so still answers the next one. static constexpr uint32_t MODULE_AWAKE_MS = 10000; static constexpr uint8_t CMD_MAX_RETRIES = 3; - // Named so a repeated press replaces its own pending timeout instead of stacking + // Keyed so a repeated press replaces its own pending timeout instead of stacking // another, and so the command path can cancel it when it takes the pin over. - static constexpr const char *WAKE_BUTTON_TIMEOUT = "wake_button"; - // Named so a burst of writes collapses to one read once they settle, rather than + static constexpr uint32_t WAKE_BUTTON_TIMEOUT_ID = 0; + // Keyed so a burst of writes collapses to one read once they settle, rather than // one read per write. - static constexpr const char *AREA_REFRESH_TIMEOUT = "area_refresh"; + static constexpr uint32_t AREA_REFRESH_TIMEOUT_ID = 1; // A reply cannot trail the frame that earned it for longer than this; the field worst case is ~726ms. static constexpr uint32_t STALE_ACK_MAX_AGE_MS = 1000; diff --git a/esphome/components/ld6002b/number/__init__.py b/esphome/components/ld6002b/number/__init__.py index 236b049f53b..de0ff57a644 100644 --- a/esphome/components/ld6002b/number/__init__.py +++ b/esphome/components/ld6002b/number/__init__.py @@ -1,9 +1,10 @@ import esphome.codegen as cg from esphome.components import number +from esphome.components.button import DOMAIN as BUTTON_DOMAIN +from esphome.components.select import DOMAIN as SELECT_DOMAIN import esphome.config_validation as cv from esphome.const import ( CONF_AREA_ID, - CONF_BUTTON, DEVICE_CLASS_DISTANCE, DEVICE_CLASS_DURATION, ENTITY_CATEGORY_CONFIG, @@ -114,7 +115,7 @@ def final_validate(config: ConfigType) -> None: has_apply_area = any( entry.get(CONF_LD6002B_ID) == hub_id and entry.get(CONF_APPLY_AREA) is not None - for entry in full_config.get(CONF_BUTTON, []) + for entry in full_config.get(BUTTON_DOMAIN, []) ) if not has_apply_area: raise cv.Invalid( @@ -124,7 +125,7 @@ def final_validate(config: ConfigType) -> None: has_area_id_select = any( entry.get(CONF_LD6002B_ID) == hub_id and entry.get(CONF_AREA_ID) is not None - for entry in full_config.get("select", []) + for entry in full_config.get(SELECT_DOMAIN, []) ) if not has_area_id_select: raise cv.Invalid( diff --git a/esphome/components/ld6002b/sensor.py b/esphome/components/ld6002b/sensor.py index 5653f8b9449..d2285187f6b 100644 --- a/esphome/components/ld6002b/sensor.py +++ b/esphome/components/ld6002b/sensor.py @@ -30,44 +30,32 @@ from .const import ( DEPENDENCIES = ["ld6002b"] -# The ld2450 defaults for a streamed value: hold the last reading for a second so a -# dropped frame does not read as absence, then rate-limit what reaches the frontend. -_VALUE_SENSOR_FILTERS = [ - { - "timeout": { - "timeout": cv.TimePeriod(milliseconds=1000), - "value": "last", - } - }, - {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, -] - TARGET_SCHEMA = cv.Schema( { cv.Optional(CONF_X): sensor.sensor_schema( unit_of_measurement=UNIT_METER, accuracy_decimals=2, device_class=DEVICE_CLASS_DISTANCE, - filters=_VALUE_SENSOR_FILTERS, + filters=sensor.TIMEOUT_THROTTLE_FILTERS, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_Y): sensor.sensor_schema( unit_of_measurement=UNIT_METER, accuracy_decimals=2, device_class=DEVICE_CLASS_DISTANCE, - filters=_VALUE_SENSOR_FILTERS, + filters=sensor.TIMEOUT_THROTTLE_FILTERS, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_Z): sensor.sensor_schema( unit_of_measurement=UNIT_METER, accuracy_decimals=2, device_class=DEVICE_CLASS_DISTANCE, - filters=_VALUE_SENSOR_FILTERS, + filters=sensor.TIMEOUT_THROTTLE_FILTERS, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_DOPPLER_INDEX): sensor.sensor_schema( accuracy_decimals=0, - filters=_VALUE_SENSOR_FILTERS, + filters=sensor.TIMEOUT_THROTTLE_FILTERS, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CLUSTER_ID): sensor.sensor_schema( diff --git a/esphome/components/ledc/__init__.py b/esphome/components/ledc/__init__.py index 71a87b6ae50..cc2c4d56aa1 100644 --- a/esphome/components/ledc/__init__.py +++ b/esphome/components/ledc/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@OttoWinter"] +DOMAIN = "ledc" diff --git a/esphome/components/ledc/ledc_output.cpp b/esphome/components/ledc/ledc_output.cpp index 62833a76493..18ce19a183b 100644 --- a/esphome/components/ledc/ledc_output.cpp +++ b/esphome/components/ledc/ledc_output.cpp @@ -22,7 +22,7 @@ static const uint8_t SETUP_ATTEMPT_COUNT_MAX = 5; namespace esphome::ledc { -static const char *const TAG = "ledc.output"; +ESPHOME_LOG_TAG(TAG, "ledc.output"); static bool ledc_peripheral_reset_done = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static const int MAX_RES_BITS = LEDC_TIMER_BIT_MAX - 1; diff --git a/esphome/components/libretiny/__init__.py b/esphome/components/libretiny/__init__.py index 8cd7f3e7b62..61eafe5f86d 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -32,6 +32,7 @@ from esphome.storage_json import StorageJSON from . import gpio # noqa: F401 from .const import ( COMPONENT_BK72XX, + COMPONENT_LN882X, CONF_GPIO_RECOVER, CONF_LOGLEVEL, CONF_SDK_SILENT, @@ -56,6 +57,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@kuba2k2"] AUTO_LOAD = ["preferences"] +DOMAIN = "libretiny" IS_TARGET_PLATFORM = True # BLE 5.x BK SDK options to disable unused features. @@ -490,6 +492,13 @@ async def component_to_code(config): # Not enabled on RTL87xx/LN882x — costs more heap than it saves there. cg.add_build_flag("-DconfigSUPPORT_STATIC_ALLOCATION=1") + # Static destructors never run, so skip registering them. See atexit_stubs.cpp. + cg.add_build_flag("-Wl,--wrap=__cxa_atexit") + + # LN882x: a zero-size allocation must not trip the SDK's assert (see ln882x_zero_malloc.c). + if config[CONF_COMPONENT_ID] == COMPONENT_LN882X: + cg.add_build_flag("-Wl,--wrap=pvPortMalloc") + # RTL8710B needs FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake # required by AsyncTCP 3.4.3+ (https://github.com/esphome/esphome/issues/10220) # RTL8720C (ambz2) requires FreeRTOS 10.x so this only applies to RTL8710B @@ -521,6 +530,10 @@ async def component_to_code(config): # RAM-executable output section and prints a post-link placement summary. if FAMILY_COMPONENT[config[CONF_FAMILY]] != COMPONENT_BK72XX: cg.add_platformio_option("extra_scripts", ["pre:patch_linker.py"]) + # Match the 1-byte enums of the LN882H SDK's prebuilt WiFi library. + if FAMILY_COMPONENT[config[CONF_FAMILY]] == COMPONENT_LN882X: + cg.add_build_unflag("-fno-short-enums") + cg.add_build_flag("-fshort-enums") # dummy version code cg.add_define("USE_ARDUINO_VERSION_CODE", cg.RawExpression("VERSION_CODE(0, 0, 0)")) # decrease web server stack size (16k words -> 4k words) diff --git a/esphome/components/libretiny/atexit_stubs.cpp b/esphome/components/libretiny/atexit_stubs.cpp new file mode 100644 index 00000000000..1d237e7f059 --- /dev/null +++ b/esphome/components/libretiny/atexit_stubs.cpp @@ -0,0 +1,20 @@ +/* + * Linker wrap stub for __cxa_atexit(). + * + * GCC registers the destructor of every global and function local static + * object with __cxa_atexit(). The firmware never returns from main or calls + * exit(), so these destructors never run, but the first registration links + * newlib's exit handler tables. + */ + +#include "esphome/core/defines.h" + +#ifdef USE_LIBRETINY + +namespace esphome::libretiny {} + +// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming) +extern "C" int __wrap___cxa_atexit(void (*)(void *), void *, void *) { return 0; } +// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming) + +#endif // USE_LIBRETINY diff --git a/esphome/components/libretiny/core.cpp b/esphome/components/libretiny/core.cpp index 8686a41e646..1f5de35d2cb 100644 --- a/esphome/components/libretiny/core.cpp +++ b/esphome/components/libretiny/core.cpp @@ -1,6 +1,26 @@ #ifdef USE_LIBRETINY -// HAL functions live in hal.cpp. core.cpp is intentionally empty for -// libretiny — there is no extra component bootstrap to keep here. +// HAL functions live in hal.cpp; this file only holds the serial port +// workaround below. + +#include "core.h" + +#ifdef USE_LN882X +#include + +// The SDK's open port handles (components/serial/serial.c); not in its header. +extern "C" Serial_t *serial_handles[SER_PORT_NUM]; + +namespace esphome::libretiny { + +void ensure_serial_handle(uint8_t port) { + if (port >= SER_PORT_NUM || serial_handles[port] != nullptr) + return; + // serial_init() zeroes and opens it; the port stays open for good. + serial_handles[port] = new Serial_t(); +} + +} // namespace esphome::libretiny +#endif // USE_LN882X #endif // USE_LIBRETINY diff --git a/esphome/components/libretiny/core.h b/esphome/components/libretiny/core.h index f909db4f0f8..8f6760c0fc1 100644 --- a/esphome/components/libretiny/core.h +++ b/esphome/components/libretiny/core.h @@ -4,6 +4,21 @@ #include -namespace esphome::libretiny {} // namespace esphome::libretiny +namespace esphome::libretiny { + +#ifdef USE_LN882X +/// Give a hardware UART its SDK handle before its Serial begin()s. +/// +/// LibreTiny's LN882H Serial passes the SDK's per port handle to +/// serial_init(), which fills it in, but only the default log port (UART1) +/// has one at boot. Any other port hands it NULL and the chip faults before +/// setup, so a logger or uart on UART0 never boots. Drop this once LibreTiny +/// gives each port storage of its own. +void ensure_serial_handle(uint8_t port); +#else +inline void ensure_serial_handle(uint8_t /*port*/) {} +#endif + +} // namespace esphome::libretiny #endif // USE_LIBRETINY diff --git a/esphome/components/libretiny/gpio_arduino.cpp b/esphome/components/libretiny/gpio_arduino.cpp index ca88835f695..9884c37b85c 100644 --- a/esphome/components/libretiny/gpio_arduino.cpp +++ b/esphome/components/libretiny/gpio_arduino.cpp @@ -1,11 +1,12 @@ #ifdef USE_LIBRETINY #include "gpio_arduino.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" namespace esphome::libretiny { -static const char *const TAG = "libretiny.gpio"; +ESPHOME_LOG_TAG(TAG, "libretiny.gpio"); static int IRAM_ATTR flags_to_mode(gpio::Flags flags) { flags = @@ -37,7 +38,7 @@ ISRInternalGPIOPin ArduinoInternalGPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } -void ArduinoInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { +void ArduinoInternalGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { PinStatus arduino_mode = (PinStatus) 255; switch (type) { case gpio::INTERRUPT_RISING_EDGE: diff --git a/esphome/components/libretiny/gpio_arduino.h b/esphome/components/libretiny/gpio_arduino.h index da477fde363..b5ccf544ba3 100644 --- a/esphome/components/libretiny/gpio_arduino.h +++ b/esphome/components/libretiny/gpio_arduino.h @@ -1,11 +1,11 @@ #pragma once #ifdef USE_LIBRETINY -#include "esphome/core/hal.h" +#include "esphome/core/gpio_pin.h" namespace esphome::libretiny { -class ArduinoInternalGPIOPin final : public InternalGPIOPin { +class ArduinoInternalGPIOPin final : public GPIOPin { public: void set_pin(uint8_t pin) { pin_ = pin; } void set_inverted(bool inverted) { inverted_ = inverted; } @@ -16,14 +16,18 @@ class ArduinoInternalGPIOPin final : public InternalGPIOPin { bool digital_read() override; void digital_write(bool value) override; size_t dump_summary(char *buffer, size_t len) const override; - void detach_interrupt() const override; - ISRInternalGPIOPin to_isr() const override; - uint8_t get_pin() const override { return pin_; } + void detach_interrupt() const; + template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { + this->attach_interrupt_(reinterpret_cast(func), arg, type); + } + ISRInternalGPIOPin to_isr() const; + uint8_t get_pin() const { return pin_; } gpio::Flags get_flags() const override { return flags_; } - bool is_inverted() const override { return inverted_; } + bool is_inverted() const { return inverted_; } + bool is_internal() override { return true; } protected: - void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const; uint8_t pin_; bool inverted_{}; diff --git a/esphome/components/libretiny/hal.cpp b/esphome/components/libretiny/hal.cpp index 01b276005dd..f97a5a19a7a 100644 --- a/esphome/components/libretiny/hal.cpp +++ b/esphome/components/libretiny/hal.cpp @@ -12,6 +12,12 @@ // libretiny component's API. namespace esphome::libretiny {} // namespace esphome::libretiny +#if defined(USE_LN882X) && !defined(CLANG_TIDY) // clang-tidy runs without the PlatformIO flags +// Fail the build if the -fshort-enums swap in __init__.py stops working. +enum Ln882xEnumSizeCheck { LN882X_ENUM_SIZE_CHECK }; +static_assert(sizeof(Ln882xEnumSizeCheck) == 1, "LN882x must build with -fshort-enums to match the SDK"); +#endif + namespace esphome { // yield(), delay(), micros(), millis(), millis_64(), delayMicroseconds(), diff --git a/esphome/components/libretiny/hal.h b/esphome/components/libretiny/hal.h index 48b94a5214a..838536b866d 100644 --- a/esphome/components/libretiny/hal.h +++ b/esphome/components/libretiny/hal.h @@ -8,6 +8,7 @@ #include #include +#include "esphome/core/main_task.h" #include "esphome/core/time_64.h" // IRAM_ATTR places a function in executable RAM so it is callable from an @@ -84,6 +85,10 @@ __attribute__((always_inline)) inline bool in_isr_context() { #endif } +/// Before setup() stores the handle every task counts as background; the wake is a no-op until then. +__attribute__((always_inline)) inline bool is_main_loop_thread() { + return xTaskGetCurrentTaskHandle() == esphome_main_task_handle; +} __attribute__((always_inline)) inline void yield() { ::yield(); } __attribute__((always_inline)) inline void delay(uint32_t ms) { ::delay(ms); } __attribute__((always_inline)) inline uint32_t micros() { return static_cast(::micros()); } diff --git a/esphome/components/libretiny/ln882x_zero_malloc.c b/esphome/components/libretiny/ln882x_zero_malloc.c new file mode 100644 index 00000000000..77e30b3a7dd --- /dev/null +++ b/esphome/components/libretiny/ln882x_zero_malloc.c @@ -0,0 +1,19 @@ +/* + * LN882x: heap_5 returns NULL for a zero-size request, and the SDK's + * malloc-failed hook ends in LN_ASSERT(0), which spins until the watchdog + * resets the chip. LibreTiny's scan handler asks for zero entries when a scan + * finds no networks. Linked with -Wl,--wrap=pvPortMalloc so every allocation + * comes through here; drop it once LibreTiny no longer allocates zero entries. + */ + +#ifdef USE_LN882X + +#include + +void *__real_pvPortMalloc(size_t size); // NOLINT(readability-identifier-naming) + +void *__wrap_pvPortMalloc(size_t size) { // NOLINT(readability-identifier-naming) + return __real_pvPortMalloc(size == 0 ? 1 : size); +} + +#endif // USE_LN882X diff --git a/esphome/components/libretiny/lt_component.cpp b/esphome/components/libretiny/lt_component.cpp index 0ab064e3e19..9cc7b5ff68d 100644 --- a/esphome/components/libretiny/lt_component.cpp +++ b/esphome/components/libretiny/lt_component.cpp @@ -2,11 +2,14 @@ #ifdef USE_LIBRETINY +#include + +#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome::libretiny { -static const char *const TAG = "libretiny"; +ESPHOME_LOG_TAG(TAG, "libretiny"); void LTComponent::dump_config() { ESP_LOGCONFIG(TAG, @@ -18,6 +21,17 @@ void LTComponent::dump_config() { ESP_LOGCONFIG(TAG, " Optimization: -Os, SDK: -O" STRINGIFY_MACRO(__OPTIMIZE_LEVEL__)); #endif +#if defined(USE_LN882X) && defined(USE_WIFI) + // The SDK falls back to this MAC when the board layout's KV store holds none. + static constexpr uint8_t SDK_DEFAULT_MAC[MAC_ADDRESS_SIZE] = {0x00, 0x50, 0xC2, 0x5E, 0x10, 0x88}; + uint8_t mac[MAC_ADDRESS_SIZE]; + get_mac_address_raw(mac); + if (memcmp(mac, SDK_DEFAULT_MAC, MAC_ADDRESS_SIZE) == 0) { + ESP_LOGE(TAG, "MAC is the LN882H SDK default 00:50:C2:5E:10:88; only one device with it can be on a network. " + "On a Tuya module, set board: to its Tuya board (e.g. wl2s, wl2h-u) and flash over serial"); + } +#endif + #ifdef USE_TEXT_SENSOR if (this->version_ != nullptr) { this->version_->publish_state(<_BANNER_STR[10]); diff --git a/esphome/components/libretiny/preferences.cpp b/esphome/components/libretiny/preferences.cpp index d0bd3bf26b3..a868e5042e2 100644 --- a/esphome/components/libretiny/preferences.cpp +++ b/esphome/components/libretiny/preferences.cpp @@ -8,7 +8,7 @@ namespace esphome::libretiny { -static const char *const TAG = "preferences"; +ESPHOME_LOG_TAG(TAG, "preferences"); struct NVSData { uint32_t key; diff --git a/esphome/components/libretiny_pwm/__init__.py b/esphome/components/libretiny_pwm/__init__.py index 4db724f8ade..1825606769b 100644 --- a/esphome/components/libretiny_pwm/__init__.py +++ b/esphome/components/libretiny_pwm/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@kuba2k2"] +DOMAIN = "libretiny_pwm" diff --git a/esphome/components/libretiny_pwm/libretiny_pwm.cpp b/esphome/components/libretiny_pwm/libretiny_pwm.cpp index eea593a39da..ee6fd5bb0e7 100644 --- a/esphome/components/libretiny_pwm/libretiny_pwm.cpp +++ b/esphome/components/libretiny_pwm/libretiny_pwm.cpp @@ -5,7 +5,7 @@ namespace esphome::libretiny_pwm { -static const char *const TAG = "libretiny.pwm"; +ESPHOME_LOG_TAG(TAG, "libretiny.pwm"); void LibreTinyPWM::write_state(float state) { if (!this->initialized_) { diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index b175620bbf4..c490300b58c 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -41,7 +41,7 @@ from esphome.core.entity_helpers import ( queue_entity_register, setup_entity, ) -from esphome.cpp_generator import MockObjClass +from esphome.cpp_generator import MockObj, MockObjClass import esphome.final_validate as fv from esphome.types import ConfigType @@ -71,6 +71,7 @@ from .types import ( # noqa: F401 ChannelColors, ColorMode, GammaTable, + LightEffect, LightOutput, LightState, LightStateRTCState, @@ -519,6 +520,17 @@ def validate_color_temperature_channels(value): return value +def _effects_table(effects: list[MockObj]) -> MockObj: + """Emit the flash table of a light's effect pointers; each must be a static address.""" + return cg.shared_progmem_array( + "light_effects", + LightEffect.operator("ptr"), + cg.ArrayInitializer(*effects), + share=False, + constexpr=False, + ) + + @setup_entity("light") async def setup_light_core_(light_var, config, output_var): # All 8 legacy restore_mode values, and the restore_state key, are just different @@ -586,7 +598,7 @@ async def setup_light_core_(light_var, config, output_var): EFFECTS_REGISTRY, config.get(CONF_EFFECTS, []) ) if effects: - cg.add(light_var.add_effects(effects)) + cg.add(light_var.add_effects(_effects_table(effects), len(effects))) for conf in config.get(CONF_ON_TURN_ON, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_var) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index d2f5913f4b5..a6440700fa9 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -3,7 +3,7 @@ namespace esphome::light { -static const char *const TAG = "light.addressable"; +ESPHOME_LOG_TAG(TAG, "light.addressable"); void AddressableLight::call_setup() { this->setup(); diff --git a/esphome/components/light/automation.cpp b/esphome/components/light/automation.cpp index ddac2f9341f..35aa035b26a 100644 --- a/esphome/components/light/automation.cpp +++ b/esphome/components/light/automation.cpp @@ -3,7 +3,7 @@ namespace esphome::light { -static const char *const TAG = "light.automation"; +ESPHOME_LOG_TAG(TAG, "light.automation"); void addressableset_warn_about_scale(const char *field) { ESP_LOGW(TAG, "Lambda for parameter %s of light.addressable_set should return values in range 0-1 instead of 0-255.", diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 7e54fccebca..0970c513a29 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -8,7 +8,7 @@ namespace esphome::light { -static const char *const TAG = "light"; +ESPHOME_LOG_TAG(TAG, "light"); // Cold-path logger; caller handles the clamp so the in-range hot path avoids // the spill/reload around the call. @@ -147,7 +147,7 @@ void LightCall::perform() { // EFFECT StringRef effect_s; if (this->effect_ == 0u) { - effect_s = StringRef::from_lit("None"); + effect_s = EFFECT_NONE_REF; } else { effect_s = this->parent_->effects_[this->effect_ - 1]->get_name(); } diff --git a/esphome/components/light/light_effect.h b/esphome/components/light/light_effect.h index a89e3fec5a9..3fabcf5d51b 100644 --- a/esphome/components/light/light_effect.h +++ b/esphome/components/light/light_effect.h @@ -5,6 +5,8 @@ namespace esphome::light { +inline constexpr StringRef EFFECT_NONE_REF = StringRef::from_lit("None"); + class LightState; class LightEffect { diff --git a/esphome/components/light/light_effect_names.h b/esphome/components/light/light_effect_names.h new file mode 100644 index 00000000000..7ddf459b248 --- /dev/null +++ b/esphome/components/light/light_effect_names.h @@ -0,0 +1,44 @@ +#pragma once + +#include "esphome/core/helpers.h" +#include "esphome/core/string_ref.h" +#include "light_effect.h" + +namespace esphome::light { + +/// Effect names as the native API lists them: "None" first, then each effect; empty without effects. +/// Reads the light's effect table directly, so listing entities allocates nothing. Holds a reference: +/// it only lives for one synchronous encode, and by value costs flash on every copy. +class LightEffectNames { + public: + explicit LightEffectNames(const ConstVector &effects) : effects_(effects) {} + + class Iterator { + public: + ESPHOME_ALWAYS_INLINE Iterator(const ConstVector &effects, size_t index) + : effects_(effects), index_(index) {} + ESPHOME_ALWAYS_INLINE StringRef operator*() const { + return this->index_ == 0 ? EFFECT_NONE_REF : this->effects_[this->index_ - 1]->get_name(); + } + ESPHOME_ALWAYS_INLINE Iterator &operator++() { + ++this->index_; + return *this; + } + ESPHOME_ALWAYS_INLINE bool operator!=(const Iterator &other) const { return this->index_ != other.index_; } + + protected: + const ConstVector &effects_; + size_t index_; + }; + + ESPHOME_ALWAYS_INLINE Iterator begin() const { return {this->effects_, 0}; } + ESPHOME_ALWAYS_INLINE Iterator end() const { + return {this->effects_, this->effects_.empty() ? 0 : this->effects_.size() + 1}; + } + ESPHOME_ALWAYS_INLINE bool empty() const { return this->effects_.empty(); } + + protected: + const ConstVector &effects_; +}; + +} // namespace esphome::light diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 1b9a7f94c1b..885440187f9 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -9,11 +9,9 @@ #include "light_output.h" #include "transformers.h" -#include - namespace esphome::light { -static const char *const TAG = "light"; +ESPHOME_LOG_TAG(TAG, "light"); // Colour modes are bitmasks of capabilities. A mode the light doesn't support may be a bare set of // required capabilities (see restore_state.py's colour mode inference): use the first supported @@ -196,8 +194,6 @@ void LightState::publish_state() { LightOutput *LightState::get_output() const { return this->output_; } -static constexpr auto EFFECT_NONE_REF = StringRef::from_lit("None"); - StringRef LightState::get_effect_name() { if (this->active_effect_index_ > 0) { return this->effects_[this->active_effect_index_ - 1]->get_name(); @@ -218,11 +214,6 @@ void LightState::add_target_state_reached_listener(LightTargetStateReachedListen this->target_state_reached_listeners_->push_back(listener); } -void LightState::add_effects(const std::initializer_list &effects) { - // Called once from Python codegen during setup with all effects from YAML config - this->effects_ = effects; -} - void LightState::current_values_as_brightness(float *brightness) { this->current_values.as_brightness(brightness); *brightness = this->gamma_correct_lut(*brightness); @@ -352,9 +343,6 @@ float LightState::gamma_uncorrect_lut(float value) const { #endif // USE_LIGHT_GAMMA_LUT void LightState::start_effect_(uint32_t effect_index) { - // An external add_effects() can exceed the codegen cap; ignore an index the uint16_t can't hold - if (effect_index > std::numeric_limits::max()) - return; this->stop_effect_(); if (effect_index == 0) return; diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index fcb2ca40c90..2a99943e1df 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -217,10 +217,13 @@ class LightState : public EntityBase, public Component { bool supports_effects() const { return !this->effects_.empty(); } /// Get all effects for this light state. - const FixedVector &get_effects() const { return this->effects_; } + const ConstVector &get_effects() const { return this->effects_; } - /// Add effects for this light state. - void add_effects(const std::initializer_list &effects); + /// Codegen only: point at the generated table of this light's effects, which must outlive it. + /// Codegen caps the count at 65535, so an effect index always fits the uint16_t active index. + void add_effects(LightEffect *const *effects, size_t count) { + this->effects_ = ConstVector(effects, count); + } /// Get the total number of effects available for this light. size_t get_effect_count() const { return this->effects_.size(); } @@ -354,7 +357,7 @@ class LightState : public EntityBase, public Component { /// The currently active transformer for this light (transition/flash). std::unique_ptr transformer_{nullptr}; /// List of effects for this light. - FixedVector effects_; + ConstVector effects_; /// Object used to store the persisted values of the light. ESPPreferenceObject rtc_; diff --git a/esphome/components/lightwaverf/__init__.py b/esphome/components/lightwaverf/__init__.py index a1138c7de82..1a4234ceaf0 100644 --- a/esphome/components/lightwaverf/__init__.py +++ b/esphome/components/lightwaverf/__init__.py @@ -15,6 +15,7 @@ from esphome.cpp_helpers import gpio_pin_expression from esphome.types import ConfigType CODEOWNERS = ["@max246"] +DOMAIN = "lightwaverf" lightwaverf_ns = cg.esphome_ns.namespace("lightwaverf") diff --git a/esphome/components/lightwaverf/lightwaverf.cpp b/esphome/components/lightwaverf/lightwaverf.cpp index b8b6a9697ea..58c4ab07849 100644 --- a/esphome/components/lightwaverf/lightwaverf.cpp +++ b/esphome/components/lightwaverf/lightwaverf.cpp @@ -7,7 +7,7 @@ namespace esphome::lightwaverf { -static const char *const TAG = "lightwaverf.sensor"; +ESPHOME_LOG_TAG(TAG, "lightwaverf.sensor"); static const uint8_t DEFAULT_REPEAT = 10; static const bool DEFAULT_INVERT = false; diff --git a/esphome/components/lilygo_t5_47/__init__.py b/esphome/components/lilygo_t5_47/__init__.py index 5499d096a91..cb3605c63e4 100644 --- a/esphome/components/lilygo_t5_47/__init__.py +++ b/esphome/components/lilygo_t5_47/__init__.py @@ -1,3 +1,5 @@ import esphome.codegen as cg +DOMAIN = "lilygo_t5_47" + lilygo_t5_47_ns = cg.esphome_ns.namespace("lilygo_t5_47") diff --git a/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp index 87319235e9b..d76f4dfb395 100644 --- a/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp +++ b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp @@ -5,7 +5,7 @@ namespace esphome::lilygo_t5_47 { -static const char *const TAG = "lilygo_t5_47.touchscreen"; +ESPHOME_LOG_TAG(TAG, "lilygo_t5_47.touchscreen"); static const uint8_t POWER_REGISTER = 0xD6; static const uint8_t TOUCH_REGISTER = 0xD0; diff --git a/esphome/components/lm75b/__init__.py b/esphome/components/lm75b/__init__.py index e69de29bb2d..228b24febe4 100644 --- a/esphome/components/lm75b/__init__.py +++ b/esphome/components/lm75b/__init__.py @@ -0,0 +1 @@ +DOMAIN = "lm75b" diff --git a/esphome/components/lm75b/lm75b.cpp b/esphome/components/lm75b/lm75b.cpp index 2e8b9d3cfe1..56e69faa62f 100644 --- a/esphome/components/lm75b/lm75b.cpp +++ b/esphome/components/lm75b/lm75b.cpp @@ -4,7 +4,7 @@ namespace esphome::lm75b { -static const char *const TAG = "lm75b"; +ESPHOME_LOG_TAG(TAG, "lm75b"); void LM75BComponent::dump_config() { ESP_LOGCONFIG(TAG, "LM75B:"); diff --git a/esphome/components/ln882h_ble/__init__.py b/esphome/components/ln882h_ble/__init__.py index aadc1f3b2f9..7e382eda85b 100644 --- a/esphome/components/ln882h_ble/__init__.py +++ b/esphome/components/ln882h_ble/__init__.py @@ -16,6 +16,7 @@ from esphome.types import ConfigType DEPENDENCIES = ["ln882x"] CODEOWNERS = ["@Bl00d-B0b"] +DOMAIN = "ln882h_ble" ln882h_ble_ns = cg.esphome_ns.namespace("ln882h_ble") LN882HBLE = ln882h_ble_ns.class_("LN882HBLE", cg.Component) diff --git a/esphome/components/ln882h_ble/ln882h_ble.cpp b/esphome/components/ln882h_ble/ln882h_ble.cpp index 0b15bf434c1..8f569086de2 100644 --- a/esphome/components/ln882h_ble/ln882h_ble.cpp +++ b/esphome/components/ln882h_ble/ln882h_ble.cpp @@ -178,7 +178,7 @@ __sprintf( // NOLINT(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,r namespace esphome::ln882h_ble { -static const char *const TAG = "ln882h_ble"; +ESPHOME_LOG_TAG(TAG, "ln882h_ble"); // The SDK event callback is a plain C function pointer with no user argument, // so it reaches the (single) component instance through a file-static pointer. diff --git a/esphome/components/ln882h_ble_tracker/__init__.py b/esphome/components/ln882h_ble_tracker/__init__.py index 69a01ae1acf..0d2b3a51804 100644 --- a/esphome/components/ln882h_ble_tracker/__init__.py +++ b/esphome/components/ln882h_ble_tracker/__init__.py @@ -28,6 +28,7 @@ CONF_LN882H_BLE_ID = "ln882h_ble_id" DEPENDENCIES = ["ln882x"] AUTO_LOAD = ["ble_device_base", "ln882h_ble"] CODEOWNERS = ["@Bl00d-B0b"] +DOMAIN = "ln882h_ble_tracker" ble_device_base.register_hub_provider("ln882h_ble_tracker") diff --git a/esphome/components/ln882h_ble_tracker/ln882h_ble_tracker.cpp b/esphome/components/ln882h_ble_tracker/ln882h_ble_tracker.cpp index e1083e5fbe7..34981560d24 100644 --- a/esphome/components/ln882h_ble_tracker/ln882h_ble_tracker.cpp +++ b/esphome/components/ln882h_ble_tracker/ln882h_ble_tracker.cpp @@ -9,7 +9,7 @@ namespace esphome::ln882h_ble_tracker { -static const char *const TAG = "ln882h_ble_tracker"; +ESPHOME_LOG_TAG(TAG, "ln882h_ble_tracker"); static constexpr float BLE_SCAN_UNIT_MS = 0.625f; diff --git a/esphome/components/ln882x/__init__.py b/esphome/components/ln882x/__init__.py index b4e179c4a95..d23702d7995 100644 --- a/esphome/components/ln882x/__init__.py +++ b/esphome/components/ln882x/__init__.py @@ -35,6 +35,7 @@ from .boards import LN882X_BOARD_PINS, LN882X_BOARDS CODEOWNERS = ["@lamauny"] AUTO_LOAD = ["libretiny"] +DOMAIN = "ln882x" IS_TARGET_PLATFORM = True COMPONENT_DATA = LibreTinyComponent( diff --git a/esphome/components/lock/__init__.py b/esphome/components/lock/__init__.py index aa5bec98d53..cf723df9b93 100644 --- a/esphome/components/lock/__init__.py +++ b/esphome/components/lock/__init__.py @@ -22,6 +22,7 @@ from esphome.cpp_generator import MockObj, MockObjClass from esphome.types import ConfigType, SafeExpType CODEOWNERS = ["@esphome/core"] +DOMAIN = "lock" IS_PLATFORM_COMPONENT = True lock_ns = cg.esphome_ns.namespace("lock") diff --git a/esphome/components/lock/lock.cpp b/esphome/components/lock/lock.cpp index 66eb692bd5c..aca3a3b0248 100644 --- a/esphome/components/lock/lock.cpp +++ b/esphome/components/lock/lock.cpp @@ -6,7 +6,7 @@ namespace esphome::lock { -static const char *const TAG = "lock"; +ESPHOME_LOG_TAG(TAG, "lock"); // Lock state strings indexed by LockState enum. // Index 0 is UNKNOWN (for LOCK_STATE_NONE), also used as fallback for out-of-range diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index d6b9bca38ba..67137214c1e 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -45,7 +45,6 @@ from esphome.const import ( CONF_HARDWARE_UART, CONF_ID, CONF_LEVEL, - CONF_LOGGER, CONF_LOGS, CONF_ON_MESSAGE, CONF_TAG, @@ -235,6 +234,15 @@ def warn_ram_log_strings(config: ConfigType) -> ConfigType: return config +def validate_task_log_buffer_alignment(value: int) -> int: + # ESP-IDF rejects a no-split ring buffer whose size is not a multiple of 4 + if CORE.is_esp32 and value % 4: + raise cv.Invalid( + f"{CONF_TASK_LOG_BUFFER_SIZE} must be a multiple of 4 on ESP32" + ) + return value + + def validate_wait_for_cdc(config: ConfigType) -> ConfigType: if config.get(CONF_WAIT_FOR_CDC) and config.get(CONF_HARDWARE_UART) != USB_CDC: raise cv.Invalid("wait_for_cdc requires hardware_uart: USB_CDC") @@ -282,6 +290,7 @@ CONFIG_SCHEMA = cv.All( max=32768, # Max: Depends on message sizes, typically ~300 messages with default size ), ), + validate_task_log_buffer_alignment, ), cv.SplitDefault( CONF_HARDWARE_UART, @@ -355,7 +364,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config: ConfigType) -> None: baud_rate: int = config[CONF_BAUD_RATE] level = config[CONF_LEVEL] - CORE.data.setdefault(CONF_LOGGER, {})[CONF_LEVEL] = level + CORE.data.setdefault(DOMAIN, {})[CONF_LEVEL] = level tx_buffer_size = config[CONF_TX_BUFFER_SIZE] cg.add_define("ESPHOME_LOGGER_TX_BUFFER_SIZE", tx_buffer_size) # Determine task log buffer size. The buffer is a direct member of Logger diff --git a/esphome/components/logger/log_buffer.h b/esphome/components/logger/log_buffer.h index 14121d3dd9d..019d13950a3 100644 --- a/esphome/components/logger/log_buffer.h +++ b/esphome/components/logger/log_buffer.h @@ -3,11 +3,14 @@ #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "esphome/core/progmem.h" namespace esphome::logger { // Maximum header size: 35 bytes fixed + 32 bytes tag + 16 bytes thread name = 83 bytes (45 byte safety margin) static constexpr uint16_t MAX_HEADER_SIZE = 128; +// Longest tag the header budget allows +static constexpr size_t MAX_TAG_LENGTH = 32; // ANSI color code last digit (30-38 range, store only last digit to save RAM on ESP8266) static const char LOG_LEVEL_COLOR_DIGIT[] PROGMEM = { @@ -189,13 +192,23 @@ struct LogBuffer { *p++ = 'm'; } // Copy string without null terminator, updates pointer in place - // Caller is responsible for ensuring buffer has sufficient space + // Caller is responsible for ensuring buffer has sufficient space. Tags may be in PROGMEM on ESP8266. void copy_string_(char *&p, const char *str) { +#ifdef USE_ESP8266 + // Only ESP8266 caps the length; tags may be in PROGMEM, so read each byte once + for (size_t i = 0; i < MAX_TAG_LENGTH; i++) { + const char c = static_cast(progmem_read_byte(reinterpret_cast(str + i))); + if (c == '\0') + break; + *p++ = c; + } +#else const size_t len = strlen(str); // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - intentionally no null terminator, building string piece by // piece memcpy(p, str, len); p += len; +#endif } }; diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 757ba6771e0..24a6586ae2f 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -8,7 +8,7 @@ namespace esphome::logger { -static const char *const TAG = "logger"; +ESPHOME_LOG_TAG(TAG, "logger"); #if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR) // Implementation for multi-threaded platforms (ESP32 with FreeRTOS, Host with pthreads, LibreTiny with FreeRTOS, @@ -144,7 +144,10 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas inline uint8_t Logger::level_for(const char *tag) { #ifdef USE_LOGGER_RUNTIME_TAG_LEVELS - auto it = this->log_levels_.find(tag); + // Skip the lookup when no per-tag levels are set + if (this->log_levels_.empty()) + return this->current_level_; + auto it = this->log_levels_.find(FlashTag{tag}); if (it != this->log_levels_.end()) return it->second; #endif diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 6bdd5910f63..b82aaa7c219 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -12,6 +12,7 @@ #include "esphome/core/defines.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "esphome/core/progmem.h" #include "log_buffer.h" #include "task_log_buffer_host.h" @@ -85,12 +86,16 @@ class LoggerLevelListener { }; #endif -#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS -// Comparison function for const char* keys in log_levels_ map +// RAM map keys compared with a tag that may be in PROGMEM, without copying it +struct FlashTag { + const char *tag; +}; struct CStrCompare { + using is_transparent = void; bool operator()(const char *a, const char *b) const { return strcmp(a, b) < 0; } + bool operator()(const char *key, FlashTag t) const { return ESPHOME_strcmp_P(key, t.tag) < 0; } + bool operator()(FlashTag t, const char *key) const { return ESPHOME_strcmp_P(key, t.tag) > 0; } }; -#endif // Stack buffer size for retrieving thread/task names from the OS // macOS allows up to 64 bytes, Linux up to 16 @@ -488,6 +493,12 @@ class LoggerMessageTrigger final : public Trigger(self); if (level <= trigger->level_) { +#ifdef USE_ESP8266 + // User lambdas may strcmp the tag, which may be in PROGMEM. The copy lives in + // the trigger so an automation that suspends (delay) still sees a valid tag. + ESPHOME_strncpy_P(trigger->ram_tag_, tag, MAX_TAG_LENGTH); + tag = trigger->ram_tag_; +#endif trigger->trigger(level, tag, message); } }); @@ -495,6 +506,9 @@ class LoggerMessageTrigger final : public Triggerbaud_rate_ > 0) { @@ -11,18 +13,21 @@ void Logger::pre_setup() { #if LT_HW_UART0 case UART_SELECTION_UART0: this->hw_serial_ = &Serial0; + libretiny::ensure_serial_handle(0); Serial0.begin(this->baud_rate_); break; #endif #if LT_HW_UART1 case UART_SELECTION_UART1: this->hw_serial_ = &Serial1; + libretiny::ensure_serial_handle(1); Serial1.begin(this->baud_rate_); break; #endif #if LT_HW_UART2 case UART_SELECTION_UART2: this->hw_serial_ = &Serial2; + libretiny::ensure_serial_handle(2); Serial2.begin(this->baud_rate_); break; #endif diff --git a/esphome/components/logger/logger_rp2.cpp b/esphome/components/logger/logger_rp2.cpp index b949d7ee162..518a1d7b1d2 100644 --- a/esphome/components/logger/logger_rp2.cpp +++ b/esphome/components/logger/logger_rp2.cpp @@ -8,7 +8,7 @@ namespace esphome::logger { -static const char *const TAG = "logger"; +ESPHOME_LOG_TAG(TAG, "logger"); void Logger::pre_setup() { if (this->baud_rate_ > 0) { diff --git a/esphome/components/logger/logger_zephyr.cpp b/esphome/components/logger/logger_zephyr.cpp index 396728ceaf0..95cc5d4a14b 100644 --- a/esphome/components/logger/logger_zephyr.cpp +++ b/esphome/components/logger/logger_zephyr.cpp @@ -37,7 +37,7 @@ __attribute__((section(".noinit"))) struct { #endif } crash_buf; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static const char *const TAG = "logger"; +ESPHOME_LOG_TAG(TAG, "logger"); #ifdef USE_LOGGER_UART_SELECTION_USB_CDC void Logger::cdc_loop_() { diff --git a/esphome/components/logger/select/__init__.py b/esphome/components/logger/select/__init__.py index 00f67422f35..4bd979398ce 100644 --- a/esphome/components/logger/select/__init__.py +++ b/esphome/components/logger/select/__init__.py @@ -1,13 +1,14 @@ import esphome.codegen as cg from esphome.components import select import esphome.config_validation as cv -from esphome.const import CONF_LEVEL, CONF_LOGGER, ENTITY_CATEGORY_CONFIG, ICON_BUG +from esphome.const import CONF_LEVEL, ENTITY_CATEGORY_CONFIG, ICON_BUG from esphome.core import CORE from esphome.cpp_helpers import register_component, register_parented from esphome.types import ConfigType from .. import ( CONF_LOGGER_ID, + DOMAIN, LOG_LEVELS, Logger, logger_ns, @@ -31,7 +32,7 @@ async def to_code(config: ConfigType) -> None: request_logger_level_listeners() parent = await cg.get_variable(config[CONF_LOGGER_ID]) levels = list(LOG_LEVELS) - index = levels.index(CORE.data[CONF_LOGGER][CONF_LEVEL]) + index = levels.index(CORE.data[DOMAIN][CONF_LEVEL]) levels = levels[: index + 1] var = await select.new_select(config, options=levels) await register_parented(var, parent) diff --git a/esphome/components/logger/task_log_buffer_esp32.cpp b/esphome/components/logger/task_log_buffer_esp32.cpp index cb97f5504fa..75b567ff892 100644 --- a/esphome/components/logger/task_log_buffer_esp32.cpp +++ b/esphome/components/logger/task_log_buffer_esp32.cpp @@ -10,15 +10,7 @@ namespace esphome::logger { TaskLogBuffer::TaskLogBuffer() { // Create a static ring buffer with RINGBUF_TYPE_NOSPLIT for message integrity // Storage is a member array (embedded in Logger), no heap allocation needed - this->ring_buffer_ = - xRingbufferCreateStatic(sizeof(this->storage_), RINGBUF_TYPE_NOSPLIT, this->storage_, &this->structure_); -} - -TaskLogBuffer::~TaskLogBuffer() { - if (this->ring_buffer_ != nullptr) { - vRingbufferDelete(this->ring_buffer_); - this->ring_buffer_ = nullptr; - } + xRingbufferCreateStatic(sizeof(this->storage_), RINGBUF_TYPE_NOSPLIT, this->storage_, &this->structure_); } bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &text_length) { @@ -27,7 +19,7 @@ bool TaskLogBuffer::borrow_message_main_loop(LogMessage *&message, uint16_t &tex } size_t item_size = 0; - void *received_item = xRingbufferReceive(ring_buffer_, &item_size, 0); + void *received_item = xRingbufferReceive(this->handle_(), &item_size, 0); if (received_item == nullptr) { return false; } @@ -44,7 +36,7 @@ void TaskLogBuffer::release_message_main_loop() { if (this->current_token_ == nullptr) { return; } - vRingbufferReturnItem(ring_buffer_, this->current_token_); + vRingbufferReturnItem(this->handle_(), this->current_token_); this->current_token_ = nullptr; // Update counter to mark all messages as processed last_processed_counter_ = message_counter_.load(std::memory_order_relaxed); @@ -71,7 +63,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin // Acquire memory directly from the ring buffer void *acquired_memory = nullptr; - BaseType_t result = xRingbufferSendAcquire(ring_buffer_, &acquired_memory, total_size, 0); + BaseType_t result = xRingbufferSendAcquire(this->handle_(), &acquired_memory, total_size, 0); if (result != pdTRUE || acquired_memory == nullptr) { return false; // Failed to acquire memory @@ -100,7 +92,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin // Handle unexpected formatting error if (ret <= 0) { - vRingbufferReturnItem(ring_buffer_, acquired_memory); + vRingbufferReturnItem(this->handle_(), acquired_memory); return false; } @@ -111,7 +103,7 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin msg->text_length = text_length; // Complete the send operation with the acquired memory - result = xRingbufferSendComplete(ring_buffer_, acquired_memory); + result = xRingbufferSendComplete(this->handle_(), acquired_memory); if (result != pdTRUE) { return false; // Failed to complete the message send diff --git a/esphome/components/logger/task_log_buffer_esp32.h b/esphome/components/logger/task_log_buffer_esp32.h index e8197667954..0ffaa044931 100644 --- a/esphome/components/logger/task_log_buffer_esp32.h +++ b/esphome/components/logger/task_log_buffer_esp32.h @@ -47,7 +47,7 @@ class TaskLogBuffer { }; TaskLogBuffer(); - ~TaskLogBuffer(); + // No destructor: Logger is never destroyed // NOT thread-safe - borrow a message from the ring buffer, only call from main loop bool borrow_message_main_loop(LogMessage *&message, uint16_t &text_length); @@ -68,7 +68,11 @@ class TaskLogBuffer { static constexpr size_t size() { return ESPHOME_TASK_LOG_BUFFER_SIZE; } private: - RingbufHandle_t ring_buffer_{nullptr}; // FreeRTOS ring buffer handle + // xRingbufferCreateStatic() returns the static structure itself as the handle; it only + // returns NULL for a no-split size that is unaligned or under two item headers + static_assert(ESPHOME_TASK_LOG_BUFFER_SIZE % 4 == 0, "task_log_buffer_size must be a multiple of 4"); + RingbufHandle_t handle_() { return &this->structure_; } + StaticRingbuffer_t structure_; // Static structure for the ring buffer uint8_t storage_[ESPHOME_TASK_LOG_BUFFER_SIZE]; // Embedded in Logger (no separate heap allocation) diff --git a/esphome/components/lps22/__init__.py b/esphome/components/lps22/__init__.py index e69de29bb2d..b9936d7ab18 100644 --- a/esphome/components/lps22/__init__.py +++ b/esphome/components/lps22/__init__.py @@ -0,0 +1 @@ +DOMAIN = "lps22" diff --git a/esphome/components/lps22/lps22.cpp b/esphome/components/lps22/lps22.cpp index 020add695c1..57f801c332b 100644 --- a/esphome/components/lps22/lps22.cpp +++ b/esphome/components/lps22/lps22.cpp @@ -2,7 +2,7 @@ namespace esphome::lps22 { -static constexpr const char *const TAG = "lps22"; +ESPHOME_LOG_TAG(TAG, "lps22"); static constexpr uint8_t WHO_AM_I = 0x0F; static constexpr uint8_t LPS22HB_ID = 0xB1; diff --git a/esphome/components/lsm6ds/__init__.py b/esphome/components/lsm6ds/__init__.py index b1044276a17..311058249e0 100644 --- a/esphome/components/lsm6ds/__init__.py +++ b/esphome/components/lsm6ds/__init__.py @@ -3,6 +3,7 @@ from esphome.components import i2c from esphome.components.motion import MotionComponent CODEOWNERS = ["@clydebarrow"] +DOMAIN = "lsm6ds" CONF_LSM6DS_ID = "lsm6ds_id" # C++ namespace / class diff --git a/esphome/components/lsm6ds/lsm6ds.cpp b/esphome/components/lsm6ds/lsm6ds.cpp index efdd241578f..b02a610c0a8 100644 --- a/esphome/components/lsm6ds/lsm6ds.cpp +++ b/esphome/components/lsm6ds/lsm6ds.cpp @@ -4,7 +4,7 @@ namespace esphome::lsm6ds { -static const char *const TAG = "lsm6ds"; +ESPHOME_LOG_TAG(TAG, "lsm6ds"); static const struct { uint8_t who_am_i; diff --git a/esphome/components/ltr390/__init__.py b/esphome/components/ltr390/__init__.py index e69de29bb2d..3bf5ae25639 100644 --- a/esphome/components/ltr390/__init__.py +++ b/esphome/components/ltr390/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ltr390" diff --git a/esphome/components/ltr390/ltr390.cpp b/esphome/components/ltr390/ltr390.cpp index dd78b20f2c2..d9699afe5a1 100644 --- a/esphome/components/ltr390/ltr390.cpp +++ b/esphome/components/ltr390/ltr390.cpp @@ -5,7 +5,7 @@ namespace esphome::ltr390 { -static const char *const TAG = "ltr390"; +ESPHOME_LOG_TAG(TAG, "ltr390"); static const uint8_t LTR390_WAKEUP_TIME = 10; static const uint8_t LTR390_SETTLE_TIME = 5; diff --git a/esphome/components/ltr501/__init__.py b/esphome/components/ltr501/__init__.py index dd06cfffea0..450e38be0fe 100644 --- a/esphome/components/ltr501/__init__.py +++ b/esphome/components/ltr501/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@latonita"] +DOMAIN = "ltr501" diff --git a/esphome/components/ltr501/ltr501.cpp b/esphome/components/ltr501/ltr501.cpp index afdc271167d..91fca7fa9ba 100644 --- a/esphome/components/ltr501/ltr501.cpp +++ b/esphome/components/ltr501/ltr501.cpp @@ -8,7 +8,7 @@ using esphome::i2c::ErrorCode; namespace esphome::ltr501 { -static const char *const TAG = "ltr501"; +ESPHOME_LOG_TAG(TAG, "ltr501"); static const uint8_t MAX_TRIES = 5; static const uint8_t MAX_SENSITIVITY_ADJUSTMENTS = 10; diff --git a/esphome/components/ltr_als_ps/__init__.py b/esphome/components/ltr_als_ps/__init__.py index dd06cfffea0..3b6fa0d2689 100644 --- a/esphome/components/ltr_als_ps/__init__.py +++ b/esphome/components/ltr_als_ps/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@latonita"] +DOMAIN = "ltr_als_ps" diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.cpp b/esphome/components/ltr_als_ps/ltr_als_ps.cpp index 0d43aac20e4..2d3b3755ad5 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.cpp +++ b/esphome/components/ltr_als_ps/ltr_als_ps.cpp @@ -8,7 +8,7 @@ using esphome::i2c::ErrorCode; namespace esphome::ltr_als_ps { -static const char *const TAG = "ltr_als_ps"; +ESPHOME_LOG_TAG(TAG, "ltr_als_ps"); static const uint8_t MAX_TRIES = 5; diff --git a/esphome/components/lvgl/encoders.py b/esphome/components/lvgl/encoders.py index e6527bbc9b5..30480eacc7e 100644 --- a/esphome/components/lvgl/encoders.py +++ b/esphome/components/lvgl/encoders.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components.binary_sensor import BinarySensor +from esphome.components.rotary_encoder import DOMAIN as ROTARY_ENCODER_DOMAIN from esphome.components.rotary_encoder.sensor import RotaryEncoderSensor import esphome.config_validation as cv from esphome.const import CONF_GROUP, CONF_ID, CONF_SENSOR @@ -27,7 +28,7 @@ ENCODERS_CONFIG = cv.ensure_list( cv.Required(CONF_SENSOR): cv.Any( cv.All( cv.use_id(RotaryEncoderSensor), - cv.requires_component("rotary_encoder"), + cv.requires_component(ROTARY_ENCODER_DOMAIN), ), cv.Schema( { diff --git a/esphome/components/lvgl/lv_validation.py b/esphome/components/lvgl/lv_validation.py index 961fa4079d7..d8a96de639a 100644 --- a/esphome/components/lvgl/lv_validation.py +++ b/esphome/components/lvgl/lv_validation.py @@ -3,19 +3,17 @@ from typing import Any import esphome.codegen as cg from esphome.components import image -from esphome.components.color import CONF_HEX, ColorStruct, from_rgbw +from esphome.components.color import ( + CONF_HEX, + DOMAIN as COLOR_DOMAIN, + ColorStruct, + from_rgbw, +) from esphome.components.const.css_colors import CSS_COLORS -from esphome.components.font import Font +from esphome.components.font import DOMAIN as FONT_DOMAIN, Font from esphome.components.image import Image_ import esphome.config_validation as cv -from esphome.const import ( - CONF_ARGS, - CONF_COLOR, - CONF_FORMAT, - CONF_ID, - CONF_TIME, - CONF_VALUE, -) +from esphome.const import CONF_ARGS, CONF_FORMAT, CONF_ID, CONF_TIME, CONF_VALUE from esphome.core import CORE, ID, Lambda from esphome.cpp_generator import MockObj, StaticCastExpression, call_lambda from esphome.cpp_types import ESPTime, int32, uint32 @@ -81,7 +79,7 @@ def get_component_colors(value): if isinstance(value, int): return value >> 16, value >> 8 & 0xFF, value & 0xFF if isinstance(value, ID): - cval = [x for x in CORE.config[CONF_COLOR] if x[CONF_ID] == value][0] + cval = [x for x in CORE.config[COLOR_DOMAIN] if x[CONF_ID] == value][0] if CONF_HEX in cval: r, g, b = cval[CONF_HEX] else: @@ -437,7 +435,7 @@ class LvFont(LValidator): add_lv_use("font") fontval = cv.use_id(Font)(value) get_esphome_fonts_used().add(fontval) - return cv.requires_component("font")(fontval) + return cv.requires_component(FONT_DOMAIN)(fontval) # Use font::Font* as return type for lambdas returning ESPHome fonts # The inline overloads in lvgl_esphome.h handle conversion to lv_font_t* diff --git a/esphome/components/lvgl/lvcode.py b/esphome/components/lvgl/lvcode.py index d6d1aaa4144..feffac28b33 100644 --- a/esphome/components/lvgl/lvcode.py +++ b/esphome/components/lvgl/lvcode.py @@ -209,9 +209,6 @@ class LvContext(LambdaContext): def __init__(self): super().__init__(parameters=LVGL_COMP_ARG) - async def __aexit__(self, exc_type, exc_val, exc_tb): - await super().__aexit__(exc_type, exc_val, exc_tb) - def add(self, expression: Expression | Statement): cg.add(expression) return expression diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 466d4000235..e35cc0c48bf 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -13,7 +13,7 @@ static void *lv_alloc_draw_buf(size_t size, bool internal); static void *draw_buf_alloc_cb(size_t size, lv_color_format_t color_format) { return lv_alloc_draw_buf(size, false); }; namespace esphome::lvgl { -static const char *const TAG = "lvgl"; +ESPHOME_LOG_TAG(TAG, "lvgl"); static const size_t MIN_BUFFER_FRAC = 8; // buffer must be at least 1/8 of the display size static const size_t MIN_BUFFER_SIZE = 2048; // Sensible minimum buffer size diff --git a/esphome/components/lvgl/number/__init__.py b/esphome/components/lvgl/number/__init__.py index 0d8d99ab218..b23e77df22f 100644 --- a/esphome/components/lvgl/number/__init__.py +++ b/esphome/components/lvgl/number/__init__.py @@ -1,16 +1,10 @@ import esphome.codegen as cg from esphome.components import number import esphome.config_validation as cv -from esphome.const import CONF_ON_RELEASE, CONF_RESTORE_VALUE +from esphome.const import CONF_RESTORE_VALUE from esphome.cpp_generator import MockObj -from ..defines import ( - CONF_ANIMATED, - CONF_TRIGGER, - CONF_UPDATE_ON_RELEASE, - CONF_WIDGET, - LOGGER, -) +from ..defines import CONF_ANIMATED, CONF_TRIGGER, CONF_UPDATE_ON_RELEASE, CONF_WIDGET from ..lv_validation import animated from ..lvcode import ( EVENT_ARG, @@ -31,20 +25,18 @@ CONFIG_SCHEMA = number.number_schema(LVGLNumber).extend( cv.Required(CONF_WIDGET): cv.use_id(LvNumber), **VALUE_TRIGGER_SCHEMA, cv.Optional(CONF_ANIMATED, default=True): animated, - cv.Optional(CONF_UPDATE_ON_RELEASE): cv.boolean, + # Removed in 2026.11.0 - kept to provide helpful error message + # Remove before 2027.5.0 + cv.Optional(CONF_UPDATE_ON_RELEASE): cv.invalid( + "The 'update_on_release' option has been removed in ESPHome 2026.11.0.\n" + "Use 'trigger: on_release' instead." + ), cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean, } ) async def to_code(config): - trigger = config[CONF_TRIGGER] - if CONF_UPDATE_ON_RELEASE in config: - LOGGER.warning( - "Option 'update_on_release' is deprecated and will be removed in 2026.11.0 - use 'trigger: on_release' instead" - ) - if config[CONF_UPDATE_ON_RELEASE]: - trigger = CONF_ON_RELEASE widget = await get_widgets(config, CONF_WIDGET) widget = widget[0] async with LambdaContext([], return_type=cg.float_) as value: @@ -70,6 +62,6 @@ async def to_code(config): lvgl_static.add_event_cb( widget.obj, await event.get_lambda(), - *TRIGGER_EVENT_MAP[trigger], + *TRIGGER_EVENT_MAP[config[CONF_TRIGGER]], ) ) diff --git a/esphome/components/lvgl/schemas.py b/esphome/components/lvgl/schemas.py index cf766b96c86..924c17fb547 100644 --- a/esphome/components/lvgl/schemas.py +++ b/esphome/components/lvgl/schemas.py @@ -4,6 +4,7 @@ from typing import Any from esphome import config_validation as cv from esphome.automation import Trigger, validate_automation +from esphome.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from esphome.components.mapping import mapping_class from esphome.components.time import RealTimeClock from esphome.config_validation import prepend_path @@ -148,7 +149,8 @@ PRESS_TIME = cv.All( ENCODER_SCHEMA = cv.Schema( { cv.GenerateID(): cv.All( - cv.declare_id(LVEncoderListener), cv.requires_component("binary_sensor") + cv.declare_id(LVEncoderListener), + cv.requires_component(BINARY_SENSOR_DOMAIN), ), cv.Optional(CONF_GROUP): cv.declare_id(lv_group_t), cv.Optional(df.CONF_INITIAL_FOCUS): cv.All( diff --git a/esphome/components/lvgl/touchscreens.py b/esphome/components/lvgl/touchscreens.py index 0bb5715439d..6b645da3aaa 100644 --- a/esphome/components/lvgl/touchscreens.py +++ b/esphome/components/lvgl/touchscreens.py @@ -1,5 +1,9 @@ import esphome.codegen as cg -from esphome.components.touchscreen import CONF_TOUCHSCREEN_ID, Touchscreen +from esphome.components.touchscreen import ( + CONF_TOUCHSCREEN_ID, + DOMAIN as TOUCHSCREEN_DOMAIN, + Touchscreen, +) import esphome.config_validation as cv from esphome.const import CONF_ID from esphome.core import CORE @@ -17,7 +21,7 @@ CONF_TOUCHSCREEN = "touchscreen" TOUCHSCREENS_CONFIG = cv.maybe_simple_value( { cv.Required(CONF_TOUCHSCREEN_ID): cv.All( - cv.use_id(Touchscreen), cv.requires_component(CONF_TOUCHSCREEN) + cv.use_id(Touchscreen), cv.requires_component(TOUCHSCREEN_DOMAIN) ), cv.Optional(CONF_LONG_PRESS_TIME, default="400ms"): PRESS_TIME, cv.Optional(CONF_LONG_PRESS_REPEAT_TIME, default="100ms"): PRESS_TIME, @@ -29,7 +33,7 @@ TOUCHSCREENS_CONFIG = cv.maybe_simple_value( def touchscreen_schema(config): value = cv.ensure_list(TOUCHSCREENS_CONFIG)(config) - if value or CONF_TOUCHSCREEN not in CORE.loaded_integrations: + if value or TOUCHSCREEN_DOMAIN not in CORE.loaded_integrations: return value return [TOUCHSCREENS_CONFIG(config)] diff --git a/esphome/components/lvgl/widgets/meter.py b/esphome/components/lvgl/widgets/meter.py index ba322881289..fedc79383dc 100644 --- a/esphome/components/lvgl/widgets/meter.py +++ b/esphome/components/lvgl/widgets/meter.py @@ -1,6 +1,6 @@ from esphome import automation import esphome.codegen as cg -from esphome.components.image import get_image_metadata +from esphome.components.image import DOMAIN as IMAGE_DOMAIN, get_image_metadata import esphome.config_validation as cv from esphome.const import ( CONF_COLOR, @@ -214,7 +214,7 @@ INDICATOR_SCHEMA = cv.Schema( cv.GenerateID(CONF_IMAGE_ID): cv.declare_id(lv_image_t), } ), - cv.requires_component("image"), + cv.requires_component(IMAGE_DOMAIN), ), cv.Exclusive(CONF_ARC, CONF_INDICATORS): INDICATOR_ARC_SCHEMA.extend( { diff --git a/esphome/components/m5stack_8angle/__init__.py b/esphome/components/m5stack_8angle/__init__.py index 6404bcf64c9..96b9782e811 100644 --- a/esphome/components/m5stack_8angle/__init__.py +++ b/esphome/components/m5stack_8angle/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType DEPENDENCIES = ["i2c"] CODEOWNERS = ["@rnauber"] +DOMAIN = "m5stack_8angle" MULTI_CONF = True CONF_M5STACK_8ANGLE_ID = "m5stack_8angle_id" diff --git a/esphome/components/m5stack_8angle/light/m5stack_8angle_light.cpp b/esphome/components/m5stack_8angle/light/m5stack_8angle_light.cpp index e132c54daad..65b5816e46e 100644 --- a/esphome/components/m5stack_8angle/light/m5stack_8angle_light.cpp +++ b/esphome/components/m5stack_8angle/light/m5stack_8angle_light.cpp @@ -4,7 +4,7 @@ namespace esphome::m5stack_8angle { -static const char *const TAG = "m5stack_8angle.light"; +ESPHOME_LOG_TAG(TAG, "m5stack_8angle.light"); void M5Stack8AngleLightOutput::setup() { RAMAllocator allocator; diff --git a/esphome/components/m5stack_8angle/m5stack_8angle.cpp b/esphome/components/m5stack_8angle/m5stack_8angle.cpp index f466fba77e7..06f9b90af98 100644 --- a/esphome/components/m5stack_8angle/m5stack_8angle.cpp +++ b/esphome/components/m5stack_8angle/m5stack_8angle.cpp @@ -4,7 +4,7 @@ namespace esphome::m5stack_8angle { -static const char *const TAG = "m5stack_8angle"; +ESPHOME_LOG_TAG(TAG, "m5stack_8angle"); void M5Stack8AngleComponent::setup() { i2c::ErrorCode err; diff --git a/esphome/components/matrix_keypad/__init__.py b/esphome/components/matrix_keypad/__init__.py index 2e43eaf7e22..8e9b21fe433 100644 --- a/esphome/components/matrix_keypad/__init__.py +++ b/esphome/components/matrix_keypad/__init__.py @@ -7,6 +7,7 @@ from esphome.const import CONF_ID, CONF_ON_KEY, CONF_PIN, CONF_TRIGGER_ID from esphome.types import ConfigType CODEOWNERS = ["@ssieb"] +DOMAIN = "matrix_keypad" AUTO_LOAD = ["key_provider"] @@ -27,6 +28,10 @@ CONF_HAS_PULLDOWNS = "has_pulldowns" def check_keys(obj: ConfigType) -> ConfigType: + for ch in obj.get(CONF_KEYS, ""): + if not ch.isascii(): + # Each key is reported as one byte, so only ASCII characters can be key codes + raise cv.Invalid(f"Key code {ch!r} is not an ASCII character") if CONF_KEYS in obj and len(obj[CONF_KEYS]) != len(obj[CONF_ROWS]) * len( obj[CONF_COLUMNS] ): @@ -74,8 +79,11 @@ async def to_code(config: ConfigType) -> None: pin = await cg.gpio_pin_expression(conf[CONF_PIN]) col_pins.append(pin) cg.add(var.set_columns(col_pins)) - if CONF_KEYS in config: - cg.add(var.set_keys(config[CONF_KEYS])) + if (keys := config.get(CONF_KEYS)) is not None: + table = cg.shared_progmem_array( + "matrix_keypad_keys", cg.uint8, list(keys.encode()) + ) + cg.add(var.set_keys(table)) cg.add(var.set_debounce_time(config[CONF_DEBOUNCE_TIME])) if CONF_HAS_DIODES in config: cg.add(var.set_has_diodes(config[CONF_HAS_DIODES])) diff --git a/esphome/components/matrix_keypad/matrix_keypad.cpp b/esphome/components/matrix_keypad/matrix_keypad.cpp index 3b71b50fd87..e0f19ca1657 100644 --- a/esphome/components/matrix_keypad/matrix_keypad.cpp +++ b/esphome/components/matrix_keypad/matrix_keypad.cpp @@ -4,7 +4,7 @@ namespace esphome::matrix_keypad { -static const char *const TAG = "matrix_keypad"; +ESPHOME_LOG_TAG(TAG, "matrix_keypad"); void MatrixKeypad::setup() { for (auto *pin : this->rows_) { @@ -58,8 +58,8 @@ void MatrixKeypad::loop() { ESP_LOGD(TAG, "key @ row %d, col %d released", row, col); for (auto &listener : this->listeners_) listener->button_released(row, col); - if (this->pressed_key_ < (int) this->keys_.size()) { - uint8_t keycode = this->keys_[this->pressed_key_]; + if (this->keys_ != nullptr) { + uint8_t keycode = progmem_read_byte(&this->keys_[this->pressed_key_]); ESP_LOGD(TAG, "key '%c' released", keycode); for (auto &listener : this->listeners_) listener->key_released(keycode); @@ -81,8 +81,8 @@ void MatrixKeypad::loop() { ESP_LOGD(TAG, "key @ row %d, col %d pressed", row, col); for (auto &listener : this->listeners_) listener->button_pressed(row, col); - if (key < (int) this->keys_.size()) { - uint8_t keycode = this->keys_[key]; + if (this->keys_ != nullptr) { + uint8_t keycode = progmem_read_byte(&this->keys_[key]); ESP_LOGD(TAG, "key '%c' pressed", keycode); for (auto &trigger : this->key_triggers_) trigger->trigger(keycode); diff --git a/esphome/components/matrix_keypad/matrix_keypad.h b/esphome/components/matrix_keypad/matrix_keypad.h index 8c9acc8e0cb..2384691e32f 100644 --- a/esphome/components/matrix_keypad/matrix_keypad.h +++ b/esphome/components/matrix_keypad/matrix_keypad.h @@ -27,7 +27,8 @@ class MatrixKeypad final : public key_provider::KeyProvider, public Component { void dump_config() override; void set_columns(std::vector pins) { columns_ = std::move(pins); }; void set_rows(std::vector pins) { rows_ = std::move(pins); }; - void set_keys(std::string keys) { keys_ = std::move(keys); }; + /// `keys` is a codegen PROGMEM table with one key code per button. + void set_keys(const uint8_t *keys) { this->keys_ = keys; } void set_debounce_time(uint32_t debounce_time) { debounce_time_ = debounce_time; }; void set_has_diodes(bool has_diodes) { has_diodes_ = has_diodes; }; void set_has_pulldowns(bool has_pulldowns) { has_pulldowns_ = has_pulldowns; }; @@ -38,7 +39,7 @@ class MatrixKeypad final : public key_provider::KeyProvider, public Component { protected: std::vector rows_; std::vector columns_; - std::string keys_; + const uint8_t *keys_{nullptr}; uint32_t debounce_time_ = 0; bool has_diodes_{false}; bool has_pulldowns_{false}; diff --git a/esphome/components/max17043/__init__.py b/esphome/components/max17043/__init__.py index 1db25ccdd67..ba609881f0a 100644 --- a/esphome/components/max17043/__init__.py +++ b/esphome/components/max17043/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@blacknell"] +DOMAIN = "max17043" diff --git a/esphome/components/max17043/max17043.cpp b/esphome/components/max17043/max17043.cpp index 8776bb5558b..a15658512b2 100644 --- a/esphome/components/max17043/max17043.cpp +++ b/esphome/components/max17043/max17043.cpp @@ -6,7 +6,7 @@ namespace esphome::max17043 { // MAX174043 is a 1-Cell Fuel Gauge with ModelGauge and Low-Battery Alert // Consult the datasheet at https://www.analog.com/en/products/max17043.html -static const char *const TAG = "max17043"; +ESPHOME_LOG_TAG(TAG, "max17043"); static const uint8_t MAX17043_VCELL = 0x02; static const uint8_t MAX17043_SOC = 0x04; diff --git a/esphome/components/max31855/__init__.py b/esphome/components/max31855/__init__.py index e69de29bb2d..b8a76cd5007 100644 --- a/esphome/components/max31855/__init__.py +++ b/esphome/components/max31855/__init__.py @@ -0,0 +1 @@ +DOMAIN = "max31855" diff --git a/esphome/components/max31855/max31855.cpp b/esphome/components/max31855/max31855.cpp index ac6b3cecb42..4bef41761c5 100644 --- a/esphome/components/max31855/max31855.cpp +++ b/esphome/components/max31855/max31855.cpp @@ -5,7 +5,9 @@ namespace esphome::max31855 { -static const char *const TAG = "max31855"; +ESPHOME_LOG_TAG(TAG, "max31855"); + +static constexpr uint32_t READ_TIMEOUT_ID = 0; void MAX31855Sensor::update() { this->enable(); @@ -14,7 +16,7 @@ void MAX31855Sensor::update() { this->disable(); // Conversion time typ: 170ms, max: 220ms - this->set_timeout("value", 220, [this]() { this->read_data_(); }); + this->set_timeout(READ_TIMEOUT_ID, 220, [this]() { this->read_data_(); }); } void MAX31855Sensor::setup() { this->spi_setup(); } diff --git a/esphome/components/max31856/__init__.py b/esphome/components/max31856/__init__.py index e69de29bb2d..3fde3398733 100644 --- a/esphome/components/max31856/__init__.py +++ b/esphome/components/max31856/__init__.py @@ -0,0 +1 @@ +DOMAIN = "max31856" diff --git a/esphome/components/max31856/max31856.cpp b/esphome/components/max31856/max31856.cpp index b5bad8ef743..bdb47216daa 100644 --- a/esphome/components/max31856/max31856.cpp +++ b/esphome/components/max31856/max31856.cpp @@ -5,7 +5,9 @@ namespace esphome::max31856 { -static const char *const TAG = "max31856"; +ESPHOME_LOG_TAG(TAG, "max31856"); + +static constexpr uint32_t READ_TIMEOUT_ID = 0; // Based on Adafruit's library: https://github.com/adafruit/Adafruit_MAX31856 @@ -42,7 +44,7 @@ void MAX31856Sensor::update() { this->one_shot_temperature_(); // Datasheet max conversion time for 1 shot is 155ms for 60Hz / 185ms for 50Hz - this->set_timeout("MAX31856Sensor::read_thermocouple_temperature_", filter_ == FILTER_60HZ ? 155 : 185, + this->set_timeout(READ_TIMEOUT_ID, filter_ == FILTER_60HZ ? 155 : 185, [this]() { this->read_thermocouple_temperature_(); }); } diff --git a/esphome/components/max31865/__init__.py b/esphome/components/max31865/__init__.py index e69de29bb2d..42068dc04fe 100644 --- a/esphome/components/max31865/__init__.py +++ b/esphome/components/max31865/__init__.py @@ -0,0 +1 @@ +DOMAIN = "max31865" diff --git a/esphome/components/max31865/max31865.cpp b/esphome/components/max31865/max31865.cpp index e5a6fca8fb6..94899b05ab5 100644 --- a/esphome/components/max31865/max31865.cpp +++ b/esphome/components/max31865/max31865.cpp @@ -6,7 +6,9 @@ namespace esphome::max31865 { -static const char *const TAG = "max31865"; +ESPHOME_LOG_TAG(TAG, "max31865"); + +static constexpr uint32_t READ_TIMEOUT_ID = 0; void MAX31865Sensor::update() { // Check new faults since last measurement @@ -59,7 +61,7 @@ void MAX31865Sensor::update() { this->write_config_(0b11100000, 0b10100000); // Datasheet max conversion time is 55ms for 60Hz / 66ms for 50Hz - this->set_timeout("value", filter_ == FILTER_60HZ ? 55 : 66, [this]() { this->read_data_(); }); + this->set_timeout(READ_TIMEOUT_ID, filter_ == FILTER_60HZ ? 55 : 66, [this]() { this->read_data_(); }); } void MAX31865Sensor::setup() { diff --git a/esphome/components/max44009/__init__.py b/esphome/components/max44009/__init__.py index e69de29bb2d..a7d755f6a68 100644 --- a/esphome/components/max44009/__init__.py +++ b/esphome/components/max44009/__init__.py @@ -0,0 +1 @@ +DOMAIN = "max44009" diff --git a/esphome/components/max44009/max44009.cpp b/esphome/components/max44009/max44009.cpp index 731f5840563..b616b9032ee 100644 --- a/esphome/components/max44009/max44009.cpp +++ b/esphome/components/max44009/max44009.cpp @@ -4,7 +4,7 @@ namespace esphome::max44009 { -static const char *const TAG = "max44009.sensor"; +ESPHOME_LOG_TAG(TAG, "max44009.sensor"); // REGISTERS static constexpr uint8_t MAX44009_REGISTER_CONFIGURATION = 0x02; diff --git a/esphome/components/max6675/__init__.py b/esphome/components/max6675/__init__.py index e69de29bb2d..d00df9770db 100644 --- a/esphome/components/max6675/__init__.py +++ b/esphome/components/max6675/__init__.py @@ -0,0 +1 @@ +DOMAIN = "max6675" diff --git a/esphome/components/max6675/max6675.cpp b/esphome/components/max6675/max6675.cpp index 8734405508f..2fc8dbb73d7 100644 --- a/esphome/components/max6675/max6675.cpp +++ b/esphome/components/max6675/max6675.cpp @@ -3,7 +3,9 @@ namespace esphome::max6675 { -static const char *const TAG = "max6675"; +ESPHOME_LOG_TAG(TAG, "max6675"); + +static constexpr uint32_t READ_TIMEOUT_ID = 0; void MAX6675Sensor::update() { this->enable(); @@ -12,7 +14,7 @@ void MAX6675Sensor::update() { this->disable(); // Conversion time typ: 170ms, max: 220ms - this->set_timeout("value", 250, [this]() { this->read_data_(); }); + this->set_timeout(READ_TIMEOUT_ID, 250, [this]() { this->read_data_(); }); } void MAX6675Sensor::setup() { this->spi_setup(); } diff --git a/esphome/components/max6956/__init__.py b/esphome/components/max6956/__init__.py index 557f497e1ab..77e82fdec71 100644 --- a/esphome/components/max6956/__init__.py +++ b/esphome/components/max6956/__init__.py @@ -15,6 +15,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@looping40"] +DOMAIN = "max6956" DEPENDENCIES = ["i2c"] MULTI_CONF = True @@ -79,7 +80,7 @@ MAX6956_PIN_SCHEMA = pins.gpio_base_schema( mode_validator=validate_mode, ).extend( { - cv.Required(CONF_MAX6956): cv.use_id(MAX6956), + cv.Required(CONF_MAX6956): pins.use_id_or_address(MAX6956), } ) diff --git a/esphome/components/max6956/max6956.cpp b/esphome/components/max6956/max6956.cpp index ccb14496aa8..7060a8c76b7 100644 --- a/esphome/components/max6956/max6956.cpp +++ b/esphome/components/max6956/max6956.cpp @@ -3,7 +3,7 @@ namespace esphome::max6956 { -static const char *const TAG = "max6956"; +ESPHOME_LOG_TAG(TAG, "max6956"); /// Masks for MAX6956 Configuration register const uint32_t MASK_TRANSITION_DETECTION = 0x80; diff --git a/esphome/components/max6956/output/max6956_led_output.cpp b/esphome/components/max6956/output/max6956_led_output.cpp index c53a429d203..e3c9c1e8272 100644 --- a/esphome/components/max6956/output/max6956_led_output.cpp +++ b/esphome/components/max6956/output/max6956_led_output.cpp @@ -4,7 +4,7 @@ namespace esphome::max6956 { -static const char *const TAG = "max6956_led_channel"; +ESPHOME_LOG_TAG(TAG, "max6956_led_channel"); void MAX6956LedChannel::write_state(float state) { this->parent_->set_pin_brightness(this->pin_, state); } diff --git a/esphome/components/max7219/__init__.py b/esphome/components/max7219/__init__.py index e69de29bb2d..2c1a7833956 100644 --- a/esphome/components/max7219/__init__.py +++ b/esphome/components/max7219/__init__.py @@ -0,0 +1 @@ +DOMAIN = "max7219" diff --git a/esphome/components/max7219/max7219.cpp b/esphome/components/max7219/max7219.cpp index bec62ea0057..d755dd1a22c 100644 --- a/esphome/components/max7219/max7219.cpp +++ b/esphome/components/max7219/max7219.cpp @@ -5,7 +5,7 @@ namespace esphome::max7219 { -static const char *const TAG = "max7219"; +ESPHOME_LOG_TAG(TAG, "max7219"); static const uint8_t MAX7219_REGISTER_NOOP = 0x00; static const uint8_t MAX7219_REGISTER_DECODE_MODE = 0x09; diff --git a/esphome/components/max7219digit/__init__.py b/esphome/components/max7219digit/__init__.py index e69de29bb2d..33d7ad3f80a 100644 --- a/esphome/components/max7219digit/__init__.py +++ b/esphome/components/max7219digit/__init__.py @@ -0,0 +1 @@ +DOMAIN = "max7219digit" diff --git a/esphome/components/max7219digit/max7219digit.cpp b/esphome/components/max7219digit/max7219digit.cpp index 26c65aa5d34..178a6fa23a0 100644 --- a/esphome/components/max7219digit/max7219digit.cpp +++ b/esphome/components/max7219digit/max7219digit.cpp @@ -10,7 +10,7 @@ namespace esphome::max7219digit { -static const char *const TAG = "max7219DIGIT"; +ESPHOME_LOG_TAG(TAG, "max7219DIGIT"); static const uint8_t MAX7219_REGISTER_NOOP = 0x00; static const uint8_t MAX7219_REGISTER_DECODE_MODE = 0x09; diff --git a/esphome/components/max9611/__init__.py b/esphome/components/max9611/__init__.py index 9b0ea14b57e..a3fc6ca7745 100644 --- a/esphome/components/max9611/__init__.py +++ b/esphome/components/max9611/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@mckaymatthew"] +DOMAIN = "max9611" diff --git a/esphome/components/max9611/max9611.cpp b/esphome/components/max9611/max9611.cpp index 68ef7c31358..420eb7da7cb 100644 --- a/esphome/components/max9611/max9611.cpp +++ b/esphome/components/max9611/max9611.cpp @@ -24,7 +24,7 @@ float gain_to_lsb(MAX9611Multiplexer gain) { } return lsb; } -static const char *const TAG = "max9611"; +ESPHOME_LOG_TAG(TAG, "max9611"); static const uint8_t SETUP_DELAY = 4; // Wait 2 integration periods. static const float VOUT_LSB = 14.0 / 1000.0; // 14mV/LSB static const float TEMP_LSB = 0.48; // 0.48C/LSB diff --git a/esphome/components/mcp23008/__init__.py b/esphome/components/mcp23008/__init__.py index 3d1480a6f7d..9f1e9c9f964 100644 --- a/esphome/components/mcp23008/__init__.py +++ b/esphome/components/mcp23008/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["mcp23x08_base"] CODEOWNERS = ["@jesserockz"] DEPENDENCIES = ["i2c"] +DOMAIN = "mcp23008" MULTI_CONF = True mcp23008_ns = cg.esphome_ns.namespace("mcp23008") diff --git a/esphome/components/mcp23008/mcp23008.cpp b/esphome/components/mcp23008/mcp23008.cpp index 6be5f4c9518..3d2eb618d67 100644 --- a/esphome/components/mcp23008/mcp23008.cpp +++ b/esphome/components/mcp23008/mcp23008.cpp @@ -3,7 +3,7 @@ namespace esphome::mcp23008 { -static const char *const TAG = "mcp23008"; +ESPHOME_LOG_TAG(TAG, "mcp23008"); static constexpr uint8_t IOCON_ODR = 0x04; // Open-drain output for INT pin diff --git a/esphome/components/mcp23016/__init__.py b/esphome/components/mcp23016/__init__.py index 5f4b7276d83..7dddf4b0422 100644 --- a/esphome/components/mcp23016/__init__.py +++ b/esphome/components/mcp23016/__init__.py @@ -16,6 +16,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["gpio_expander"] DEPENDENCIES = ["i2c"] +DOMAIN = "mcp23016" MULTI_CONF = True mcp23016_ns = cg.esphome_ns.namespace("mcp23016") @@ -60,7 +61,7 @@ MCP23016_PIN_SCHEMA = pins.gpio_base_schema( invertible=True, ).extend( { - cv.Required(CONF_MCP23016): cv.use_id(MCP23016), + cv.Required(CONF_MCP23016): pins.use_id_or_address(MCP23016), } ) diff --git a/esphome/components/mcp23016/mcp23016.cpp b/esphome/components/mcp23016/mcp23016.cpp index 126ece3e7b9..13d930fd470 100644 --- a/esphome/components/mcp23016/mcp23016.cpp +++ b/esphome/components/mcp23016/mcp23016.cpp @@ -4,7 +4,7 @@ namespace esphome::mcp23016 { -static const char *const TAG = "mcp23016"; +ESPHOME_LOG_TAG(TAG, "mcp23016"); void MCP23016::setup() { uint16_t iocon; diff --git a/esphome/components/mcp23017/__init__.py b/esphome/components/mcp23017/__init__.py index 474d75f6ff6..6733f194566 100644 --- a/esphome/components/mcp23017/__init__.py +++ b/esphome/components/mcp23017/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["mcp23x17_base"] CODEOWNERS = ["@jesserockz"] DEPENDENCIES = ["i2c"] +DOMAIN = "mcp23017" MULTI_CONF = True mcp23017_ns = cg.esphome_ns.namespace("mcp23017") diff --git a/esphome/components/mcp23017/mcp23017.cpp b/esphome/components/mcp23017/mcp23017.cpp index 173d117457c..9cad0324470 100644 --- a/esphome/components/mcp23017/mcp23017.cpp +++ b/esphome/components/mcp23017/mcp23017.cpp @@ -3,7 +3,7 @@ namespace esphome::mcp23017 { -static const char *const TAG = "mcp23017"; +ESPHOME_LOG_TAG(TAG, "mcp23017"); static constexpr uint8_t IOCON_MIRROR = 0x40; // Mirror INTA/INTB pins static constexpr uint8_t IOCON_ODR = 0x04; // Open-drain output for INT pin diff --git a/esphome/components/mcp23s08/__init__.py b/esphome/components/mcp23s08/__init__.py index ffc51b81468..95a396865be 100644 --- a/esphome/components/mcp23s08/__init__.py +++ b/esphome/components/mcp23s08/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["mcp23x08_base"] CODEOWNERS = ["@SenexCrenshaw", "@jesserockz"] DEPENDENCIES = ["spi"] +DOMAIN = "mcp23s08" MULTI_CONF = True CONF_DEVICEADDRESS = "deviceaddress" diff --git a/esphome/components/mcp23s08/mcp23s08.cpp b/esphome/components/mcp23s08/mcp23s08.cpp index e7f582f787c..341951f1199 100644 --- a/esphome/components/mcp23s08/mcp23s08.cpp +++ b/esphome/components/mcp23s08/mcp23s08.cpp @@ -3,7 +3,7 @@ namespace esphome::mcp23s08 { -static const char *const TAG = "mcp23s08"; +ESPHOME_LOG_TAG(TAG, "mcp23s08"); // IOCON register bits static constexpr uint8_t IOCON_SEQOP = 0x20; // Sequential operation mode diff --git a/esphome/components/mcp23s17/__init__.py b/esphome/components/mcp23s17/__init__.py index d693a64ce84..9745b74bc08 100644 --- a/esphome/components/mcp23s17/__init__.py +++ b/esphome/components/mcp23s17/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["mcp23x17_base"] CODEOWNERS = ["@SenexCrenshaw", "@jesserockz"] DEPENDENCIES = ["spi"] +DOMAIN = "mcp23s17" MULTI_CONF = True CONF_DEVICEADDRESS = "deviceaddress" diff --git a/esphome/components/mcp23s17/mcp23s17.cpp b/esphome/components/mcp23s17/mcp23s17.cpp index 652931e9a93..534327f401a 100644 --- a/esphome/components/mcp23s17/mcp23s17.cpp +++ b/esphome/components/mcp23s17/mcp23s17.cpp @@ -3,7 +3,7 @@ namespace esphome::mcp23s17 { -static const char *const TAG = "mcp23s17"; +ESPHOME_LOG_TAG(TAG, "mcp23s17"); // IOCON register bits static constexpr uint8_t IOCON_SEQOP = 0x20; // Sequential operation mode diff --git a/esphome/components/mcp23x08_base/__init__.py b/esphome/components/mcp23x08_base/__init__.py index a3c12165f0c..d283d57bd0d 100644 --- a/esphome/components/mcp23x08_base/__init__.py +++ b/esphome/components/mcp23x08_base/__init__.py @@ -3,6 +3,7 @@ from esphome.components import mcp23xxx_base AUTO_LOAD = ["mcp23xxx_base"] CODEOWNERS = ["@jesserockz"] +DOMAIN = "mcp23x08_base" NUM_PINS = 8 diff --git a/esphome/components/mcp23x08_base/mcp23x08_base.cpp b/esphome/components/mcp23x08_base/mcp23x08_base.cpp index 98fd2f62493..0cda5dac4a1 100644 --- a/esphome/components/mcp23x08_base/mcp23x08_base.cpp +++ b/esphome/components/mcp23x08_base/mcp23x08_base.cpp @@ -3,7 +3,7 @@ namespace esphome::mcp23x08_base { -static const char *const TAG = "mcp23x08_base"; +ESPHOME_LOG_TAG(TAG, "mcp23x08_base"); bool MCP23X08Base::digital_read_hw(uint8_t pin) { if (!this->read_reg(mcp23x08_base::MCP23X08_GPIO, &this->input_mask_)) { diff --git a/esphome/components/mcp23x17_base/__init__.py b/esphome/components/mcp23x17_base/__init__.py index 1b93d16ff30..6160987cef8 100644 --- a/esphome/components/mcp23x17_base/__init__.py +++ b/esphome/components/mcp23x17_base/__init__.py @@ -3,6 +3,7 @@ from esphome.components import mcp23xxx_base AUTO_LOAD = ["mcp23xxx_base"] CODEOWNERS = ["@jesserockz"] +DOMAIN = "mcp23x17_base" NUM_PINS = 16 diff --git a/esphome/components/mcp23x17_base/mcp23x17_base.cpp b/esphome/components/mcp23x17_base/mcp23x17_base.cpp index 870c6c165f5..7ae17b1c13d 100644 --- a/esphome/components/mcp23x17_base/mcp23x17_base.cpp +++ b/esphome/components/mcp23x17_base/mcp23x17_base.cpp @@ -4,7 +4,7 @@ namespace esphome::mcp23x17_base { -static const char *const TAG = "mcp23x17_base"; +ESPHOME_LOG_TAG(TAG, "mcp23x17_base"); bool MCP23X17Base::digital_read_hw(uint8_t pin) { uint8_t data; diff --git a/esphome/components/mcp23xxx_base/__init__.py b/esphome/components/mcp23xxx_base/__init__.py index 755d86e4ea7..f7449b0ab9f 100644 --- a/esphome/components/mcp23xxx_base/__init__.py +++ b/esphome/components/mcp23xxx_base/__init__.py @@ -20,6 +20,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["gpio_expander"] CODEOWNERS = ["@jesserockz"] +DOMAIN = "mcp23xxx_base" mcp23xxx_base_ns = cg.esphome_ns.namespace("mcp23xxx_base") MCP23XXXBase = mcp23xxx_base_ns.class_("MCP23XXXBase", cg.Component) @@ -74,7 +75,7 @@ MCP23XXX_PIN_SCHEMA = pins.gpio_base_schema( invertible=True, ).extend( { - cv.Required(CONF_MCP23XXX): cv.use_id(MCP23XXXBase), + cv.Required(CONF_MCP23XXX): pins.use_id_or_address(MCP23XXXBase), cv.Optional(CONF_INTERRUPT, default="NO_INTERRUPT"): cv.enum( MCP23XXX_INTERRUPT_MODES, upper=True ), diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp index b8032fcec8c..8ad2f7521e5 100644 --- a/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.cpp @@ -4,6 +4,8 @@ namespace esphome::mcp23xxx_base { +void IRAM_ATTR mcp23xxx_gpio_intr(Component *arg) { arg->enable_loop_soon_any_context(); } + template void MCP23XXXGPIOPin::setup() { this->pin_mode(flags_); // When interrupt_pin is configured, pin_mode() already auto-enables CHANGE diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.h b/esphome/components/mcp23xxx_base/mcp23xxx_base.h index 1c45b0f4afa..7414677175e 100644 --- a/esphome/components/mcp23xxx_base/mcp23xxx_base.h +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.h @@ -8,6 +8,10 @@ namespace esphome::mcp23xxx_base { enum MCP23XXXInterruptMode : uint8_t { MCP23XXX_NO_INTERRUPT = 0, MCP23XXX_CHANGE, MCP23XXX_RISING, MCP23XXX_FALLING }; +// Not a template member: GCC 10 (ESP8266) ignores section attributes on template instantiations, leaving the +// ISR in flash. +void IRAM_ATTR mcp23xxx_gpio_intr(Component *arg); + template class MCP23XXXBase : public Component, public gpio_expander::CachedGpioExpander { public: virtual void pin_mode(uint8_t pin, gpio::Flags flags); @@ -35,7 +39,8 @@ template class MCP23XXXBase : public Component, public gpio_expander: void setup_interrupt_pin_() { if (this->interrupt_pin_ != nullptr) { this->interrupt_pin_->setup(); - this->interrupt_pin_->attach_interrupt(&MCP23XXXBase::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE); + this->interrupt_pin_->attach_interrupt(&mcp23xxx_gpio_intr, static_cast(this), + gpio::INTERRUPT_FALLING_EDGE); this->set_invalidate_on_read_(false); } // Disable loop until an input pin is configured via pin_mode() @@ -43,7 +48,6 @@ template class MCP23XXXBase : public Component, public gpio_expander: // For polling mode, loop is re-enabled when pin_mode() registers an input pin this->disable_loop(); } - static void IRAM_ATTR gpio_intr(MCP23XXXBase *arg) { arg->enable_loop_soon_any_context(); } // read a given register virtual bool read_reg(uint8_t reg, uint8_t *value) = 0; diff --git a/esphome/components/mcp2515/__init__.py b/esphome/components/mcp2515/__init__.py index e69de29bb2d..5b0e72b7586 100644 --- a/esphome/components/mcp2515/__init__.py +++ b/esphome/components/mcp2515/__init__.py @@ -0,0 +1 @@ +DOMAIN = "mcp2515" diff --git a/esphome/components/mcp2515/mcp2515.cpp b/esphome/components/mcp2515/mcp2515.cpp index f8c5e9f0685..8a1973d022f 100644 --- a/esphome/components/mcp2515/mcp2515.cpp +++ b/esphome/components/mcp2515/mcp2515.cpp @@ -3,7 +3,7 @@ namespace esphome::mcp2515 { -static const char *const TAG = "mcp2515"; +ESPHOME_LOG_TAG(TAG, "mcp2515"); const struct MCP2515::TxBnRegs MCP2515::TXB[N_TXBUFFERS] = {{MCP_TXB0CTRL, MCP_TXB0SIDH, MCP_TXB0DATA}, {MCP_TXB1CTRL, MCP_TXB1SIDH, MCP_TXB1DATA}, diff --git a/esphome/components/mcp3008/__init__.py b/esphome/components/mcp3008/__init__.py index 6d1bd5a970b..f29050f9fe5 100644 --- a/esphome/components/mcp3008/__init__.py +++ b/esphome/components/mcp3008/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType DEPENDENCIES = ["spi"] AUTO_LOAD = ["sensor"] +DOMAIN = "mcp3008" MULTI_CONF = True CONF_MCP3008 = "mcp3008" diff --git a/esphome/components/mcp3008/mcp3008.cpp b/esphome/components/mcp3008/mcp3008.cpp index e65e249f529..2ee6ce5b9bb 100644 --- a/esphome/components/mcp3008/mcp3008.cpp +++ b/esphome/components/mcp3008/mcp3008.cpp @@ -5,7 +5,7 @@ namespace esphome::mcp3008 { -static const char *const TAG = "mcp3008"; +ESPHOME_LOG_TAG(TAG, "mcp3008"); float MCP3008::get_setup_priority() const { return setup_priority::HARDWARE; } diff --git a/esphome/components/mcp3008/sensor/mcp3008_sensor.cpp b/esphome/components/mcp3008/sensor/mcp3008_sensor.cpp index 2647ed199e1..3a119f7d2ab 100644 --- a/esphome/components/mcp3008/sensor/mcp3008_sensor.cpp +++ b/esphome/components/mcp3008/sensor/mcp3008_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::mcp3008 { -static const char *const TAG = "mcp3008.sensor"; +ESPHOME_LOG_TAG(TAG, "mcp3008.sensor"); void MCP3008Sensor::dump_config() { ESP_LOGCONFIG(TAG, diff --git a/esphome/components/mcp3204/__init__.py b/esphome/components/mcp3204/__init__.py index 5757bdbfa98..7829a07d80a 100644 --- a/esphome/components/mcp3204/__init__.py +++ b/esphome/components/mcp3204/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_ID, CONF_REFERENCE_VOLTAGE from esphome.types import ConfigType DEPENDENCIES = ["spi"] +DOMAIN = "mcp3204" MULTI_CONF = True CODEOWNERS = ["@rsumner"] diff --git a/esphome/components/mcp3204/mcp3204.cpp b/esphome/components/mcp3204/mcp3204.cpp index ef8b5684535..dddc043f0ee 100644 --- a/esphome/components/mcp3204/mcp3204.cpp +++ b/esphome/components/mcp3204/mcp3204.cpp @@ -3,7 +3,7 @@ namespace esphome::mcp3204 { -static const char *const TAG = "mcp3204"; +ESPHOME_LOG_TAG(TAG, "mcp3204"); float MCP3204::get_setup_priority() const { return setup_priority::HARDWARE; } diff --git a/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp b/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp index 153c37d160f..188d7a1dfcf 100644 --- a/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp +++ b/esphome/components/mcp3204/sensor/mcp3204_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::mcp3204 { -static const char *const TAG = "mcp3204.sensor"; +ESPHOME_LOG_TAG(TAG, "mcp3204.sensor"); void MCP3204Sensor::dump_config() { LOG_SENSOR("", "MCP3204 Sensor", this); diff --git a/esphome/components/mcp3221/__init__.py b/esphome/components/mcp3221/__init__.py index 677bb78c358..8e5a7f134e3 100644 --- a/esphome/components/mcp3221/__init__.py +++ b/esphome/components/mcp3221/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@philippderdiedas"] +DOMAIN = "mcp3221" diff --git a/esphome/components/mcp3221/mcp3221_sensor.cpp b/esphome/components/mcp3221/mcp3221_sensor.cpp index 1b794ba9663..d38883c3421 100644 --- a/esphome/components/mcp3221/mcp3221_sensor.cpp +++ b/esphome/components/mcp3221/mcp3221_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::mcp3221 { -static const char *const TAG = "mcp3221"; +ESPHOME_LOG_TAG(TAG, "mcp3221"); float MCP3221Sensor::sample() { uint8_t data[2]; diff --git a/esphome/components/mcp4461/__init__.py b/esphome/components/mcp4461/__init__.py index 60cece67d74..d5465098f29 100644 --- a/esphome/components/mcp4461/__init__.py +++ b/esphome/components/mcp4461/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@p1ngb4ck"] DEPENDENCIES = ["i2c"] +DOMAIN = "mcp4461" MULTI_CONF = True CONF_DISABLE_WIPER_0 = "disable_wiper_0" CONF_DISABLE_WIPER_1 = "disable_wiper_1" diff --git a/esphome/components/mcp4461/mcp4461.cpp b/esphome/components/mcp4461/mcp4461.cpp index cc53f9de7f7..b6e8c247021 100644 --- a/esphome/components/mcp4461/mcp4461.cpp +++ b/esphome/components/mcp4461/mcp4461.cpp @@ -5,7 +5,7 @@ namespace esphome::mcp4461 { -static const char *const TAG = "mcp4461"; +ESPHOME_LOG_TAG(TAG, "mcp4461"); constexpr uint8_t EEPROM_WRITE_TIMEOUT_MS = 10; void Mcp4461Component::setup() { diff --git a/esphome/components/mcp4461/output/mcp4461_output.cpp b/esphome/components/mcp4461/output/mcp4461_output.cpp index d38eed4d096..6c1ab99e29b 100644 --- a/esphome/components/mcp4461/output/mcp4461_output.cpp +++ b/esphome/components/mcp4461/output/mcp4461_output.cpp @@ -6,7 +6,7 @@ namespace esphome::mcp4461 { -static const char *const TAG = "mcp4461.output"; +ESPHOME_LOG_TAG(TAG, "mcp4461.output"); // public set_level function void Mcp4461Wiper::set_level(float state) { diff --git a/esphome/components/mcp4725/__init__.py b/esphome/components/mcp4725/__init__.py index e69de29bb2d..d63ffaf7c3a 100644 --- a/esphome/components/mcp4725/__init__.py +++ b/esphome/components/mcp4725/__init__.py @@ -0,0 +1 @@ +DOMAIN = "mcp4725" diff --git a/esphome/components/mcp4725/mcp4725.cpp b/esphome/components/mcp4725/mcp4725.cpp index 8e94623de36..faa377b8de9 100644 --- a/esphome/components/mcp4725/mcp4725.cpp +++ b/esphome/components/mcp4725/mcp4725.cpp @@ -3,7 +3,7 @@ namespace esphome::mcp4725 { -static const char *const TAG = "mcp4725"; +ESPHOME_LOG_TAG(TAG, "mcp4725"); void MCP4725::setup() { auto err = this->write(nullptr, 0); diff --git a/esphome/components/mcp4728/__init__.py b/esphome/components/mcp4728/__init__.py index f48bdde681f..7156b4aa7fe 100644 --- a/esphome/components/mcp4728/__init__.py +++ b/esphome/components/mcp4728/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@berfenger"] DEPENDENCIES = ["i2c"] +DOMAIN = "mcp4728" MULTI_CONF = True CONF_STORE_IN_EEPROM = "store_in_eeprom" diff --git a/esphome/components/mcp4728/mcp4728.cpp b/esphome/components/mcp4728/mcp4728.cpp index 1b15ca05100..7259a0a89ec 100644 --- a/esphome/components/mcp4728/mcp4728.cpp +++ b/esphome/components/mcp4728/mcp4728.cpp @@ -5,7 +5,7 @@ namespace esphome::mcp4728 { -static const char *const TAG = "mcp4728"; +ESPHOME_LOG_TAG(TAG, "mcp4728"); void MCP4728Component::setup() { auto err = this->write(nullptr, 0); diff --git a/esphome/components/mcp47a1/__init__.py b/esphome/components/mcp47a1/__init__.py index e69de29bb2d..274ca6fa01e 100644 --- a/esphome/components/mcp47a1/__init__.py +++ b/esphome/components/mcp47a1/__init__.py @@ -0,0 +1 @@ +DOMAIN = "mcp47a1" diff --git a/esphome/components/mcp47a1/mcp47a1.cpp b/esphome/components/mcp47a1/mcp47a1.cpp index e2b0fa5575d..8bbde590063 100644 --- a/esphome/components/mcp47a1/mcp47a1.cpp +++ b/esphome/components/mcp47a1/mcp47a1.cpp @@ -4,7 +4,7 @@ namespace esphome::mcp47a1 { -static const char *const TAG = "mcp47a1"; +ESPHOME_LOG_TAG(TAG, "mcp47a1"); void MCP47A1::dump_config() { ESP_LOGCONFIG(TAG, "MCP47A1 Output:"); diff --git a/esphome/components/mcp9600/__init__.py b/esphome/components/mcp9600/__init__.py index e69de29bb2d..3d85a4d937c 100644 --- a/esphome/components/mcp9600/__init__.py +++ b/esphome/components/mcp9600/__init__.py @@ -0,0 +1 @@ +DOMAIN = "mcp9600" diff --git a/esphome/components/mcp9600/mcp9600.cpp b/esphome/components/mcp9600/mcp9600.cpp index 0e9b472be27..70e7cb176d4 100644 --- a/esphome/components/mcp9600/mcp9600.cpp +++ b/esphome/components/mcp9600/mcp9600.cpp @@ -3,7 +3,7 @@ namespace esphome::mcp9600 { -static const char *const TAG = "mcp9600"; +ESPHOME_LOG_TAG(TAG, "mcp9600"); static const uint8_t MCP9600_REGISTER_HOT_JUNCTION = 0x00; // static const uint8_t MCP9600_REGISTER_JUNCTION_DELTA = 0x01; // Unused, but kept for future reference diff --git a/esphome/components/mcp9808/__init__.py b/esphome/components/mcp9808/__init__.py index e69de29bb2d..7a167ebf062 100644 --- a/esphome/components/mcp9808/__init__.py +++ b/esphome/components/mcp9808/__init__.py @@ -0,0 +1 @@ +DOMAIN = "mcp9808" diff --git a/esphome/components/mcp9808/mcp9808.cpp b/esphome/components/mcp9808/mcp9808.cpp index 10e26ed7097..47df37f4563 100644 --- a/esphome/components/mcp9808/mcp9808.cpp +++ b/esphome/components/mcp9808/mcp9808.cpp @@ -14,7 +14,7 @@ static const uint8_t MCP9808_AMBIENT_CLEAR_FLAGS = 0x1F; static const uint8_t MCP9808_AMBIENT_CLEAR_SIGN = 0x0F; static const uint8_t MCP9808_AMBIENT_TEMP_NEGATIVE = 0x10; -static const char *const TAG = "mcp9808"; +ESPHOME_LOG_TAG(TAG, "mcp9808"); void MCP9808Sensor::setup() { uint16_t manu = 0; diff --git a/esphome/components/md5/__init__.py b/esphome/components/md5/__init__.py index 6a928d16829..09287664293 100644 --- a/esphome/components/md5/__init__.py +++ b/esphome/components/md5/__init__.py @@ -4,6 +4,7 @@ from esphome.helpers import IS_MACOS from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] +DOMAIN = "md5" async def to_code(config: ConfigType) -> None: diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index 0fb24fdf1df..e49a3897fee 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -5,7 +5,6 @@ import esphome.config_validation as cv from esphome.const import ( CONF_DISABLED, CONF_ID, - CONF_MDNS, CONF_OPENTHREAD, CONF_PORT, CONF_PROTOCOL, @@ -22,6 +21,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] +DOMAIN = "mdns" # Components that create mDNS services at runtime # IMPORTANT: If you add a new component here, you must also update the corresponding @@ -194,7 +194,7 @@ def request_service_enable_disable() -> bool: Public API for external components. Do not remove. """ - mdns_config = CORE.config.get(CONF_MDNS) + mdns_config = CORE.config.get(DOMAIN) if ( mdns_config is None or mdns_config[CONF_DISABLED] @@ -235,7 +235,7 @@ async def to_code(config: ConfigType) -> None: ethernet.request_ethernet_ip_state_listener() if CORE.is_esp32: - add_idf_component(name="espressif/mdns", ref="1.12.0") + add_idf_component(name="espressif/mdns", ref="1.14.0") # ESPHome only advertises; the browse APIs are unused add_idf_sdkconfig_option("CONFIG_MDNS_ENABLE_BROWSE", False) # The mdns console CLI is never used by ESPHome diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 4f236153c2c..4397c101fc5 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -23,7 +23,7 @@ namespace esphome::mdns { -static const char *const TAG = "mdns"; +ESPHOME_LOG_TAG(TAG, "mdns"); #ifndef USE_WEBSERVER_PORT #define USE_WEBSERVER_PORT 80 // NOLINT diff --git a/esphome/components/mdns/mdns_esp32.cpp b/esphome/components/mdns/mdns_esp32.cpp index 48df61326e2..1df5b9217e5 100644 --- a/esphome/components/mdns/mdns_esp32.cpp +++ b/esphome/components/mdns/mdns_esp32.cpp @@ -10,7 +10,7 @@ namespace esphome::mdns { -static const char *const TAG = "mdns"; +ESPHOME_LOG_TAG(TAG, "mdns"); #ifndef USE_OPENTHREAD static esp_err_t add_service(const MDNSService &service) { diff --git a/esphome/components/media_player/__init__.py b/esphome/components/media_player/__init__.py index d99e7877bf2..d012773d8a6 100644 --- a/esphome/components/media_player/__init__.py +++ b/esphome/components/media_player/__init__.py @@ -29,6 +29,7 @@ from esphome.cpp_generator import MockObj, MockObjClass from esphome.types import ConfigType CODEOWNERS = ["@jesserockz"] +DOMAIN = "media_player" IS_PLATFORM_COMPONENT = True diff --git a/esphome/components/media_player/media_player.cpp b/esphome/components/media_player/media_player.cpp index 6c3eef912e1..423832dd2ff 100644 --- a/esphome/components/media_player/media_player.cpp +++ b/esphome/components/media_player/media_player.cpp @@ -6,7 +6,7 @@ namespace esphome::media_player { -static const char *const TAG = "media_player"; +ESPHOME_LOG_TAG(TAG, "media_player"); const char *media_player_state_to_string(MediaPlayerState state) { switch (state) { diff --git a/esphome/components/media_source/__init__.py b/esphome/components/media_source/__init__.py index c9dab7e4d23..348ea01428a 100644 --- a/esphome/components/media_source/__init__.py +++ b/esphome/components/media_source/__init__.py @@ -7,6 +7,7 @@ from esphome.cpp_generator import MockObj, MockObjClass from esphome.types import ConfigType CODEOWNERS = ["@kahrendt"] +DOMAIN = "media_source" AUTO_LOAD = ["audio"] diff --git a/esphome/components/mhz19/__init__.py b/esphome/components/mhz19/__init__.py index e69de29bb2d..1b75339b456 100644 --- a/esphome/components/mhz19/__init__.py +++ b/esphome/components/mhz19/__init__.py @@ -0,0 +1 @@ +DOMAIN = "mhz19" diff --git a/esphome/components/mhz19/mhz19.cpp b/esphome/components/mhz19/mhz19.cpp index 707d952f83b..5be6da88eff 100644 --- a/esphome/components/mhz19/mhz19.cpp +++ b/esphome/components/mhz19/mhz19.cpp @@ -5,7 +5,7 @@ namespace esphome::mhz19 { -static const char *const TAG = "mhz19"; +ESPHOME_LOG_TAG(TAG, "mhz19"); static const uint8_t MHZ19_REQUEST_LENGTH = 8; static const uint8_t MHZ19_RESPONSE_LENGTH = 9; static const uint8_t MHZ19_COMMAND_GET_PPM[] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00}; diff --git a/esphome/components/micro_wake_word/micro_wake_word.cpp b/esphome/components/micro_wake_word/micro_wake_word.cpp index cf239be6960..6d76e399868 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.cpp +++ b/esphome/components/micro_wake_word/micro_wake_word.cpp @@ -17,7 +17,7 @@ namespace esphome::micro_wake_word { -static const char *const TAG = "micro_wake_word"; +ESPHOME_LOG_TAG(TAG, "micro_wake_word"); static const ssize_t DETECTION_QUEUE_LENGTH = 5; @@ -471,14 +471,14 @@ void MicroWakeWord::loop() { // uses floating point operations. if (!FrontendPopulateState(&this->frontend_config_, &this->frontend_state_, this->microphone_source_->get_audio_stream_info().get_sample_rate())) { - this->status_momentary_error("frontend_alloc", 1000); + this->status_momentary_error(1000); return; } if (!this->inference_task_.create(MicroWakeWord::inference_task, "mww", INFERENCE_TASK_STACK_SIZE, (void *) this, INFERENCE_TASK_PRIORITY, this->task_stack_in_psram_)) { FrontendFreeStateContents(&this->frontend_state_); // Deallocate frontend state - this->status_momentary_error("task_start", 1000); + this->status_momentary_error(1000); } } break; diff --git a/esphome/components/micro_wake_word/model_data.cpp b/esphome/components/micro_wake_word/model_data.cpp index c235e290ec2..ace0d9770ab 100644 --- a/esphome/components/micro_wake_word/model_data.cpp +++ b/esphome/components/micro_wake_word/model_data.cpp @@ -10,7 +10,7 @@ namespace esphome::micro_wake_word { -static const char *const TAG = "micro_wake_word"; +ESPHOME_LOG_TAG(TAG, "micro_wake_word"); ModelData::~ModelData() { this->deallocate_(); } diff --git a/esphome/components/micro_wake_word/streaming_model.cpp b/esphome/components/micro_wake_word/streaming_model.cpp index 72984f04fb2..7cbd11711a2 100644 --- a/esphome/components/micro_wake_word/streaming_model.cpp +++ b/esphome/components/micro_wake_word/streaming_model.cpp @@ -5,7 +5,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -static const char *const TAG = "micro_wake_word"; +ESPHOME_LOG_TAG(TAG, "micro_wake_word"); namespace esphome::micro_wake_word { diff --git a/esphome/components/micronova/button/micronova_button.cpp b/esphome/components/micronova/button/micronova_button.cpp index 13c0e1f1176..159dee2d20e 100644 --- a/esphome/components/micronova/button/micronova_button.cpp +++ b/esphome/components/micronova/button/micronova_button.cpp @@ -2,7 +2,7 @@ namespace esphome::micronova { -static const char *const TAG = "micronova.button"; +ESPHOME_LOG_TAG(TAG, "micronova.button"); void MicroNovaButton::dump_config() { LOG_BUTTON("", "Micronova button", this); diff --git a/esphome/components/micronova/micronova.cpp b/esphome/components/micronova/micronova.cpp index c2e3ef86405..86b340d5e47 100644 --- a/esphome/components/micronova/micronova.cpp +++ b/esphome/components/micronova/micronova.cpp @@ -3,7 +3,7 @@ namespace esphome::micronova { -static const char *const TAG = "micronova"; +ESPHOME_LOG_TAG(TAG, "micronova"); static constexpr uint8_t STOVE_REPLY_SIZE = 2; static constexpr uint32_t STOVE_REPLY_TIMEOUT = 200; // ms static constexpr uint8_t WRITE_BIT = 1 << 7; // 0x80 diff --git a/esphome/components/micronova/number/micronova_number.cpp b/esphome/components/micronova/number/micronova_number.cpp index 02a6de1c844..6ce2d51d7fc 100644 --- a/esphome/components/micronova/number/micronova_number.cpp +++ b/esphome/components/micronova/number/micronova_number.cpp @@ -2,7 +2,7 @@ namespace esphome::micronova { -static const char *const TAG = "micronova.number"; +ESPHOME_LOG_TAG(TAG, "micronova.number"); void MicroNovaNumber::dump_config() { LOG_NUMBER("", "Micronova number", this); diff --git a/esphome/components/micronova/sensor/micronova_sensor.cpp b/esphome/components/micronova/sensor/micronova_sensor.cpp index 8d528145cd2..f53c80fbb8f 100644 --- a/esphome/components/micronova/sensor/micronova_sensor.cpp +++ b/esphome/components/micronova/sensor/micronova_sensor.cpp @@ -2,7 +2,7 @@ namespace esphome::micronova { -static const char *const TAG = "micronova.sensor"; +ESPHOME_LOG_TAG(TAG, "micronova.sensor"); void MicroNovaSensor::dump_config() { LOG_SENSOR("", "Micronova sensor", this); diff --git a/esphome/components/micronova/switch/micronova_switch.cpp b/esphome/components/micronova/switch/micronova_switch.cpp index d01cc572543..ba74e68efff 100644 --- a/esphome/components/micronova/switch/micronova_switch.cpp +++ b/esphome/components/micronova/switch/micronova_switch.cpp @@ -2,7 +2,7 @@ namespace esphome::micronova { -static const char *const TAG = "micronova.switch"; +ESPHOME_LOG_TAG(TAG, "micronova.switch"); void MicroNovaSwitch::dump_config() { LOG_SWITCH("", "Micronova switch", this); diff --git a/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp b/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp index 50a0d34b3a7..28f755389fa 100644 --- a/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp +++ b/esphome/components/micronova/text_sensor/micronova_text_sensor.cpp @@ -2,7 +2,7 @@ namespace esphome::micronova { -static const char *const TAG = "micronova.text_sensor"; +ESPHOME_LOG_TAG(TAG, "micronova.text_sensor"); void MicroNovaTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Micronova text sensor", this); diff --git a/esphome/components/microphone/__init__.py b/esphome/components/microphone/__init__.py index c604f64d775..a83827ef5d7 100644 --- a/esphome/components/microphone/__init__.py +++ b/esphome/components/microphone/__init__.py @@ -12,7 +12,6 @@ from esphome.const import ( CONF_ID, CONF_MICROPHONE, CONF_ON_DATA, - CONF_TRIGGER_ID, ) from esphome.core import CORE from esphome.coroutine import CoroPriority, coroutine_with_priority @@ -21,6 +20,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["audio"] CODEOWNERS = ["@jesserockz", "@kahrendt"] +DOMAIN = "microphone" IS_PLATFORM_COMPONENT = True @@ -29,17 +29,12 @@ microphone_ns = cg.esphome_ns.namespace("microphone") Microphone = microphone_ns.class_("Microphone") MicrophoneSource = microphone_ns.class_("MicrophoneSource") -DataTrigger = microphone_ns.class_( - "DataTrigger", - automation.Trigger.template(cg.std_vector.template(cg.uint8).operator("ref")), -) - async def setup_microphone_core_(var: MockObj, config: ConfigType) -> None: for conf in config.get(CONF_ON_DATA, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, + await automation.build_callback_automation( + var, + "add_data_callback", [(cg.std_vector.template(cg.uint8).operator("ref").operator("const"), "x")], conf, ) @@ -53,11 +48,7 @@ async def register_microphone(var: MockObj, config: ConfigType) -> None: MICROPHONE_SCHEMA = cv.Schema.extend(audio.AUDIO_COMPONENT_SCHEMA).extend( { - cv.Optional(CONF_ON_DATA): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DataTrigger), - } - ), + cv.Optional(CONF_ON_DATA): automation.validate_automation({}), } ) diff --git a/esphome/components/microphone/automation.h b/esphome/components/microphone/automation.h deleted file mode 100644 index 0fac18d8283..00000000000 --- a/esphome/components/microphone/automation.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "esphome/core/automation.h" -#include "microphone.h" - -#include - -namespace esphome::microphone { - -class DataTrigger final : public Trigger &> { - public: - explicit DataTrigger(Microphone *mic) { - mic->add_data_callback([this](const std::vector &data) { this->trigger(data); }); - } -}; - -} // namespace esphome::microphone diff --git a/esphome/components/mics_4514/__init__.py b/esphome/components/mics_4514/__init__.py index e69de29bb2d..9b985504b0f 100644 --- a/esphome/components/mics_4514/__init__.py +++ b/esphome/components/mics_4514/__init__.py @@ -0,0 +1 @@ +DOMAIN = "mics_4514" diff --git a/esphome/components/mics_4514/mics_4514.cpp b/esphome/components/mics_4514/mics_4514.cpp index 14a73bc15f7..957cdf90ef2 100644 --- a/esphome/components/mics_4514/mics_4514.cpp +++ b/esphome/components/mics_4514/mics_4514.cpp @@ -5,7 +5,9 @@ namespace esphome::mics_4514 { -static const char *const TAG = "mics_4514"; +ESPHOME_LOG_TAG(TAG, "mics_4514"); + +static constexpr uint32_t WARMUP_TIMEOUT_ID = 0; static const uint8_t SENSOR_REGISTER = 0x04; static const uint8_t POWER_MODE_REGISTER = 0x0a; @@ -18,7 +20,7 @@ void MICS4514Component::setup() { power_mode = 0x01; this->write_register(POWER_MODE_REGISTER, &power_mode, 1); delay(100); // NOLINT - this->set_timeout("warmup", 3 * 60 * 1000, [this]() { this->warmed_up_ = true; }); + this->set_timeout(WARMUP_TIMEOUT_ID, 3 * 60 * 1000, [this]() { this->warmed_up_ = true; }); this->status_set_warning(); return; } diff --git a/esphome/components/midea/__init__.py b/esphome/components/midea/__init__.py index e69de29bb2d..d9bb8492b33 100644 --- a/esphome/components/midea/__init__.py +++ b/esphome/components/midea/__init__.py @@ -0,0 +1 @@ +DOMAIN = "midea" diff --git a/esphome/components/midea_ac/__init__.py b/esphome/components/midea_ac/__init__.py index e69de29bb2d..dbff1a11860 100644 --- a/esphome/components/midea_ac/__init__.py +++ b/esphome/components/midea_ac/__init__.py @@ -0,0 +1 @@ +DOMAIN = "midea_ac" diff --git a/esphome/components/midea_ir/__init__.py b/esphome/components/midea_ir/__init__.py index e69de29bb2d..01f3c67f7c2 100644 --- a/esphome/components/midea_ir/__init__.py +++ b/esphome/components/midea_ir/__init__.py @@ -0,0 +1 @@ +DOMAIN = "midea_ir" diff --git a/esphome/components/midea_ir/midea_ir.cpp b/esphome/components/midea_ir/midea_ir.cpp index de25c4652cb..55f84e058fb 100644 --- a/esphome/components/midea_ir/midea_ir.cpp +++ b/esphome/components/midea_ir/midea_ir.cpp @@ -6,7 +6,7 @@ namespace esphome::midea_ir { -static const char *const TAG = "midea_ir.climate"; +ESPHOME_LOG_TAG(TAG, "midea_ir.climate"); void ControlData::set_temp(float temp) { uint8_t min; diff --git a/esphome/components/mipi/__init__.py b/esphome/components/mipi/__init__.py index 50b84b630d1..b2351e23a44 100644 --- a/esphome/components/mipi/__init__.py +++ b/esphome/components/mipi/__init__.py @@ -35,6 +35,8 @@ from esphome.const import ( from esphome.core import CORE, TimePeriod from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor +DOMAIN = "mipi" + LOGGER = cv.logging.getLogger(__name__) CONF_TRANSFORMS = "transforms" @@ -648,7 +650,10 @@ class DriverChip: sequence.append((INVON,)) else: sequence.append((INVOFF,)) - if brightness := config.get(CONF_BRIGHTNESS, self.get_default(CONF_BRIGHTNESS)): + brightness = config.get( + CONF_BRIGHTNESS, self.get_default(CONF_BRIGHTNESS, None) + ) + if brightness is not None: sequence.append((BRIGHTNESS, brightness)) # Add a SLPOUT command if required. if not self.skip_command("SLPOUT"): diff --git a/esphome/components/mipi_dsi/__init__.py b/esphome/components/mipi_dsi/__init__.py index 84dd66e2f42..ae17e00bdce 100644 --- a/esphome/components/mipi_dsi/__init__.py +++ b/esphome/components/mipi_dsi/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg CODEOWNERS = ["@clydebarrow"] +DOMAIN = "mipi_dsi" mipi_dsi_ns = cg.esphome_ns.namespace("mipi_dsi") diff --git a/esphome/components/mipi_dsi/display.py b/esphome/components/mipi_dsi/display.py index b23982655a3..6bf94bf364b 100644 --- a/esphome/components/mipi_dsi/display.py +++ b/esphome/components/mipi_dsi/display.py @@ -201,7 +201,8 @@ async def to_code(config: ConfigType) -> None: sequence = model.get_sequence(config) cg.add(var.set_model(config[CONF_MODEL])) - cg.add(var.set_init_sequence(sequence)) + table = cg.shared_progmem_array("mipi_dsi_init_sequence", cg.uint8, sequence) + cg.add(var.set_init_sequence(table, len(sequence))) cg.add(var.set_invert_colors(config[CONF_INVERT_COLORS])) cg.add(var.set_hsync_pulse_width(config[CONF_HSYNC_PULSE_WIDTH])) cg.add(var.set_hsync_back_porch(config[CONF_HSYNC_BACK_PORCH])) diff --git a/esphome/components/mipi_dsi/mipi_dsi.cpp b/esphome/components/mipi_dsi/mipi_dsi.cpp index b6612038b6d..a27be568f25 100644 --- a/esphome/components/mipi_dsi/mipi_dsi.cpp +++ b/esphome/components/mipi_dsi/mipi_dsi.cpp @@ -121,20 +121,21 @@ void MipiDsi::setup() { return; } size_t index = 0; - auto &vec = this->init_sequence_; - while (index != vec.size()) { - if (vec.size() - index < 2) { + const uint8_t *seq = this->init_sequence_; + const size_t len = this->init_sequence_len_; + while (index != len) { + if (len - index < 2) { this->mark_failed(LOG_STR("Malformed init sequence")); return; } - uint8_t cmd = vec[index++]; - uint8_t x = vec[index++]; + uint8_t cmd = seq[index++]; + uint8_t x = seq[index++]; if (x == DELAY_FLAG) { ESP_LOGD(TAG, "Delay %dms", cmd); delay(cmd); } else { uint8_t num_args = x & 0x7F; - if (vec.size() - index < num_args) { + if (len - index < num_args) { this->mark_failed(LOG_STR("Malformed init sequence")); return; } @@ -145,7 +146,7 @@ void MipiDsi::setup() { delay(duration); } } - const auto *ptr = vec.data() + index; + const auto *ptr = seq + index; #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE char hex_buf[format_hex_pretty_size(MIPI_DSI_MAX_CMD_LOG_BYTES)]; #endif diff --git a/esphome/components/mipi_dsi/mipi_dsi.h b/esphome/components/mipi_dsi/mipi_dsi.h index 7bf2feb73c5..4243d3116cf 100644 --- a/esphome/components/mipi_dsi/mipi_dsi.h +++ b/esphome/components/mipi_dsi/mipi_dsi.h @@ -55,7 +55,10 @@ class MipiDsi final : public display::Display { void set_vsync_pulse_width(uint16_t vsync_pulse_width) { this->vsync_pulse_width_ = vsync_pulse_width; } void set_vsync_back_porch(uint16_t vsync_back_porch) { this->vsync_back_porch_ = vsync_back_porch; } void set_vsync_front_porch(uint16_t vsync_front_porch) { this->vsync_front_porch_ = vsync_front_porch; } - void set_init_sequence(const std::vector &init_sequence) { this->init_sequence_ = init_sequence; } + void set_init_sequence(const uint8_t *init_sequence, size_t len) { + this->init_sequence_ = init_sequence; + this->init_sequence_len_ = len; + } void set_model(const char *model) { this->model_ = model; } void set_lane_bit_rate(float lane_bit_rate) { this->lane_bit_rate_ = lane_bit_rate; } void set_lanes(uint8_t lanes) { this->lanes_ = lanes; } @@ -91,7 +94,9 @@ class MipiDsi final : public display::Display { uint16_t vsync_back_porch_ = 10; uint16_t vsync_front_porch_ = 10; const char *model_{"Unknown"}; - std::vector init_sequence_{}; + // Shared PROGMEM table + const uint8_t *init_sequence_{nullptr}; + size_t init_sequence_len_{0}; float pclk_frequency_ = 16; // in MHz float lane_bit_rate_{1500}; // in Mbps uint8_t lanes_{2}; // 1, 2, 3 or 4 lanes diff --git a/esphome/components/mipi_dsi/models/m5stack.py b/esphome/components/mipi_dsi/models/m5stack.py index 5b07229ec7c..4e847ef6fd9 100644 --- a/esphome/components/mipi_dsi/models/m5stack.py +++ b/esphome/components/mipi_dsi/models/m5stack.py @@ -14,6 +14,8 @@ DsiDriverChip( pclk_frequency="60MHz", lane_bit_rate="730Mbps", color_order="RGB", + requires={"psram", "pi4ioe5v6408"}, + reset_pin={"pi4ioe5v6408": {"address": 0x43}, "number": 4}, initsequence=[ (0xFF, 0x98, 0x81, 0x01), # Select Page 1 (0xB7, 0x03), # Pad control - 2 lane @@ -67,6 +69,8 @@ TAB5_ST7123 = DsiDriverChip( pclk_frequency="80MHz", lane_bit_rate="960Mbps", color_order="RGB", + requires={"psram", "pi4ioe5v6408"}, + reset_pin={"pi4ioe5v6408": {"address": 0x43}, "number": 4}, initsequence=[ (0x01,), (0x60, 0x71, 0x23, 0xa2), @@ -118,6 +122,8 @@ DsiDriverChip( pclk_frequency="70MHz", lane_bit_rate="965Mbps", color_order="RGB", + requires={"psram", "pi4ioe5v6408"}, + reset_pin={"pi4ioe5v6408": {"address": 0x43}, "number": 4}, initsequence=[ (0x01,), (0x60, 0x71, 0x21, 0xA2), diff --git a/esphome/components/mipi_rgb/display.py b/esphome/components/mipi_rgb/display.py index 9cba56a9763..71234c2f25b 100644 --- a/esphome/components/mipi_rgb/display.py +++ b/esphome/components/mipi_rgb/display.py @@ -286,7 +286,8 @@ async def to_code(config: ConfigType) -> None: if CONF_SPI_ID in config: await spi.register_spi_device(var, config, write_only=True) sequence = model.get_sequence(config, add_reset=True) - cg.add(var.set_init_sequence(sequence)) + table = cg.shared_progmem_array("mipi_rgb_init_sequence", cg.uint8, sequence) + cg.add(var.set_init_sequence(table, len(sequence))) cg.add(var.set_color_mode(COLOR_ORDERS[config[CONF_COLOR_ORDER]])) cg.add(var.set_invert_colors(config[CONF_INVERT_COLORS])) diff --git a/esphome/components/mipi_rgb/mipi_rgb.cpp b/esphome/components/mipi_rgb/mipi_rgb.cpp index 034efb9c92a..4928b6f6672 100644 --- a/esphome/components/mipi_rgb/mipi_rgb.cpp +++ b/esphome/components/mipi_rgb/mipi_rgb.cpp @@ -78,27 +78,28 @@ void MipiRgbSpi::write_data_(uint8_t value) { void MipiRgbSpi::write_init_sequence_() { size_t index = 0; - auto &vec = this->init_sequence_; - while (index != vec.size()) { - if (vec.size() - index < 2) { + const uint8_t *seq = this->init_sequence_; + const size_t len = this->init_sequence_len_; + while (index != len) { + if (len - index < 2) { this->mark_failed(LOG_STR("Malformed init sequence")); return; } - uint8_t cmd = vec[index++]; - uint8_t x = vec[index++]; + uint8_t cmd = seq[index++]; + uint8_t x = seq[index++]; if (x == DELAY_FLAG) { ESP_LOGD(TAG, "Delay %dms", cmd); delay(cmd); } else { uint8_t num_args = x & 0x7F; - if (vec.size() - index < num_args) { + if (len - index < num_args) { this->mark_failed(LOG_STR("Malformed init sequence")); return; } if (cmd == SLEEP_OUT) { delay(120); // NOLINT } - const auto *ptr = vec.data() + index; + const auto *ptr = seq + index; char hex_buf[format_hex_pretty_size(MIPI_RGB_MAX_CMD_LOG_BYTES)]; ESP_LOGD(TAG, "Write command %02X, length %d, byte(s) %s", cmd, num_args, format_hex_pretty_to(hex_buf, ptr, num_args, '.')); @@ -111,7 +112,6 @@ void MipiRgbSpi::write_init_sequence_() { } } // this->spi_teardown(); // SPI not needed after this - this->init_sequence_.clear(); delay(10); } diff --git a/esphome/components/mipi_rgb/mipi_rgb.h b/esphome/components/mipi_rgb/mipi_rgb.h index f528943c1b5..8b29e273abd 100644 --- a/esphome/components/mipi_rgb/mipi_rgb.h +++ b/esphome/components/mipi_rgb/mipi_rgb.h @@ -109,7 +109,10 @@ class MipiRgbSpi final : public MipiRgb, public: MipiRgbSpi(int width, int height) : MipiRgb(width, height) {} - void set_init_sequence(const std::vector &init_sequence) { this->init_sequence_ = init_sequence; } + void set_init_sequence(const uint8_t *init_sequence, size_t len) { + this->init_sequence_ = init_sequence; + this->init_sequence_len_ = len; + } void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; } void setup() override; @@ -120,7 +123,9 @@ class MipiRgbSpi final : public MipiRgb, void dump_config() override; GPIOPin *dc_pin_{nullptr}; - std::vector init_sequence_; + // Shared PROGMEM table + const uint8_t *init_sequence_{nullptr}; + size_t init_sequence_len_{0}; }; #endif diff --git a/esphome/components/mipi_spi/display.py b/esphome/components/mipi_spi/display.py index e8b54da5c79..ff49979a244 100644 --- a/esphome/components/mipi_spi/display.py +++ b/esphome/components/mipi_spi/display.py @@ -225,10 +225,10 @@ def model_schema(config: ConfigType) -> cv.All | cv.Schema: ) .extend({model.option(x): cv.boolean for x in other_options}) ) - if brightness := model.get_default(CONF_BRIGHTNESS): + if model.get_default(CONF_BRIGHTNESS, None) is not None or model.name == "CUSTOM": schema = schema.extend( { - cv.Optional(CONF_BRIGHTNESS, default=brightness): cv.int_range( + model.option(CONF_BRIGHTNESS, cv.UNDEFINED): cv.int_range( 0, 0xFF, min_included=True, max_included=True ), } @@ -401,7 +401,8 @@ async def to_code(config: ConfigType) -> None: init_sequence = model.get_sequence(config, add_madctl=False, add_reset=True) var_id.type, templateargs = get_instance(config) var = cg.new_Pvariable(var_id, TemplateArguments(*templateargs)) - cg.add(var.set_init_sequence(init_sequence)) + table = cg.shared_progmem_array("mipi_spi_init_sequence", cg.uint8, init_sequence) + cg.add(var.set_init_sequence(table, len(init_sequence))) cg.add(var.set_model(config[CONF_MODEL])) if enable_pin := config.get(CONF_ENABLE_PIN): enable = [await cg.gpio_pin_expression(pin) for pin in enable_pin] diff --git a/esphome/components/mipi_spi/light/__init__.py b/esphome/components/mipi_spi/light/__init__.py new file mode 100644 index 00000000000..aec5d9b4fdf --- /dev/null +++ b/esphome/components/mipi_spi/light/__init__.py @@ -0,0 +1,68 @@ +import esphome.codegen as cg +from esphome.components import light +import esphome.config_validation as cv +from esphome.const import ( + CONF_BRIGHTNESS, + CONF_DISPLAY_ID, + CONF_GAMMA_CORRECT, + CONF_MAX_BRIGHTNESS, + CONF_MIN_BRIGHTNESS, + CONF_MODEL, + CONF_OUTPUT_ID, +) +import esphome.final_validate as fv +from esphome.types import ConfigType + +from ..display import MipiSpi, mipi_spi_ns + +MipiSpiLight = mipi_spi_ns.class_("MipiSpiLight", light.LightOutput) + + +def _validate_brightness_range(config: ConfigType) -> ConfigType: + if config[CONF_MIN_BRIGHTNESS] >= config[CONF_MAX_BRIGHTNESS]: + raise cv.Invalid( + f"'{CONF_MIN_BRIGHTNESS}' must be less than '{CONF_MAX_BRIGHTNESS}'" + ) + return config + + +CONFIG_SCHEMA = cv.All( + light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(MipiSpiLight), + cv.GenerateID(CONF_DISPLAY_ID): cv.use_id(MipiSpi), + cv.Optional(CONF_GAMMA_CORRECT, default=1.0): cv.positive_float, + cv.Optional(CONF_MIN_BRIGHTNESS, default=0): cv.int_range(0, 255), + cv.Optional(CONF_MAX_BRIGHTNESS, default=255): cv.int_range(0, 255), + } + ), + _validate_brightness_range, +) + + +def _final_validate(config: ConfigType) -> None: + full_config = fv.full_config.get() + display_path = full_config.get_path_for_id(config[CONF_DISPLAY_ID])[:-1] + display_config = full_config.get_config_for_path(display_path) + if CONF_BRIGHTNESS not in display_config: + model = display_config[CONF_MODEL] + if model == "CUSTOM": + raise cv.Invalid( + f"The '{CONF_BRIGHTNESS}' option must be set in the display config" + ) + raise cv.Invalid( + f"Display model {model} does not support setting brightness with an SPI command" + ) + + +FINAL_VALIDATE_SCHEMA = _final_validate + + +async def to_code(config: ConfigType) -> None: + var = cg.new_Pvariable( + config[CONF_OUTPUT_ID], + config[CONF_MIN_BRIGHTNESS], + config[CONF_MAX_BRIGHTNESS], + ) + await light.register_light(var, config) + await cg.register_parented(var, config[CONF_DISPLAY_ID]) diff --git a/esphome/components/mipi_spi/light/mipi_spi_light.h b/esphome/components/mipi_spi/light/mipi_spi_light.h new file mode 100644 index 00000000000..30bbdcd8ee0 --- /dev/null +++ b/esphome/components/mipi_spi/light/mipi_spi_light.h @@ -0,0 +1,36 @@ +#pragma once + +#include "esphome/components/light/light_output.h" +#include "esphome/components/light/light_state.h" +#include "esphome/components/mipi_spi/mipi_spi.h" +#include "esphome/core/helpers.h" + +namespace esphome::mipi_spi { + +// Brightness-only light that sets the display brightness with an SPI command. +// Brightness is scaled into [min_brightness, max_brightness]; off writes min_brightness. +class MipiSpiLight final : public light::LightOutput, public Parented { + public: + MipiSpiLight(uint8_t min_brightness, uint8_t max_brightness) + : min_brightness_(min_brightness), max_brightness_(max_brightness) {} + + light::LightTraits get_traits() override { + auto traits = light::LightTraits(); + traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS}); + return traits; + } + + void write_state(light::LightState *state) override { + float brightness; + state->current_values_as_brightness(&brightness); + this->parent_->set_brightness( + this->min_brightness_ + + static_cast(roundf(brightness * (this->max_brightness_ - this->min_brightness_)))); + } + + protected: + uint8_t min_brightness_; + uint8_t max_brightness_; +}; + +} // namespace esphome::mipi_spi diff --git a/esphome/components/mipi_spi/mipi_spi.h b/esphome/components/mipi_spi/mipi_spi.h index e97b0e3a310..48a2cafbdd3 100644 --- a/esphome/components/mipi_spi/mipi_spi.h +++ b/esphome/components/mipi_spi/mipi_spi.h @@ -5,6 +5,7 @@ #include "esphome/components/spi/spi.h" #include "esphome/components/display/display.h" #include "esphome/components/display/display_color_utils.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" namespace esphome::mipi_spi { @@ -71,6 +72,12 @@ void internal_dump_config(const char *model, int width, int height, int offset_w GPIOPin *cs, GPIOPin *reset, GPIOPin *dc, int spi_mode, uint32_t data_rate, int bus_width, bool has_hardware_rotation); +// Lets a light set the display brightness without knowing the display's template parameters. +class MipiSpiBrightness { + public: + virtual void set_brightness(uint8_t brightness) = 0; +}; + /** * Base class for MIPI SPI displays. * All the methods are defined here in the header file, as it is not possible to define templated methods in a cpp file. @@ -93,6 +100,7 @@ template class MipiSpi : public display::Display, + public MipiSpiBrightness, public spi::SPIDevice { public: @@ -107,9 +115,11 @@ class MipiSpi : public display::Display, this->invert_colors_ = invert_colors; this->reset_params_(); } - void set_brightness(uint8_t brightness) { + void set_brightness(uint8_t brightness) override { this->brightness_ = brightness; - this->reset_params_(); + // Before setup the stored value is applied by reset_params_() + if (this->is_ready()) + this->write_command_(BRIGHTNESS, brightness); } void set_rotation(display::DisplayRotation rotation) override { this->rotation_ = rotation; @@ -133,7 +143,10 @@ class MipiSpi : public display::Display, return HEIGHT; } - void set_init_sequence(const std::vector &sequence) { this->init_sequence_ = sequence; } + void set_init_sequence(const uint8_t *sequence, size_t len) { + this->init_sequence_ = sequence; + this->init_sequence_len_ = len; + } // reset the display, and write the init sequence void setup() override { @@ -159,15 +172,16 @@ class MipiSpi : public display::Display, // need to know when the display is ready for SLPOUT command - will be 120ms after reset auto when = millis() + 120; size_t index = 0; - auto &vec = this->init_sequence_; - while (index != vec.size()) { - if (vec.size() - index < 2) { + const uint8_t *seq = this->init_sequence_; + const size_t len = this->init_sequence_len_; + while (index != len) { + if (len - index < 2) { esph_log_e(TAG, "Malformed init sequence"); this->mark_failed(); return; } - uint8_t cmd = vec[index++]; - uint8_t x = vec[index++]; + uint8_t cmd = progmem_read_byte(seq + index++); + uint8_t x = progmem_read_byte(seq + index++); if (x == DELAY_FLAG) { if (cmd == 0) { cmd = clamp_at_least((int) (when - millis()), 0); @@ -176,19 +190,21 @@ class MipiSpi : public display::Display, delay(cmd); } else { uint8_t num_args = x & 0x7F; - if (vec.size() - index < num_args) { + if (len - index < num_args) { esph_log_e(TAG, "Malformed init sequence"); this->mark_failed(); return; } - const auto *ptr = vec.data() + index; - this->write_command_(cmd, ptr, num_args); + // The sequence is in flash, which SPI DMA (and ESP8266 byte loads) cannot read + uint8_t args[0x80]; + progmem_memcpy(args, seq + index, num_args); + this->write_command_(cmd, args, num_args); index += num_args; } } this->reset_params_(); - // init sequence no longer needed - this->init_sequence_.clear(); + // Marks init as done, so later commands log at verbose level instead of debug + this->init_sequence_len_ = 0; } // Drawing operations @@ -236,7 +252,7 @@ class MipiSpi : public display::Display, void write_command_(uint8_t cmd, const uint8_t *bytes, size_t len) { char hex_buf[format_hex_pretty_size(MIPI_SPI_MAX_CMD_LOG_BYTES)]; // Don't spam the log after setup - if (this->init_sequence_.empty()) { + if (this->init_sequence_len_ == 0) { esph_log_v(TAG, "Command %02X, length %d, bytes %s", cmd, len, format_hex_pretty_to(hex_buf, bytes, len)); } else { esph_log_d(TAG, "Command %02X, length %d, bytes %s", cmd, len, format_hex_pretty_to(hex_buf, bytes, len)); @@ -486,7 +502,9 @@ class MipiSpi : public display::Display, bool invert_colors_{}; optional brightness_{}; const char *model_{"Unknown"}; - std::vector init_sequence_{}; + // Shared PROGMEM table + const uint8_t *init_sequence_{nullptr}; + size_t init_sequence_len_{0}; }; /** diff --git a/esphome/components/mipi_spi/models/spd2010.py b/esphome/components/mipi_spi/models/spd2010.py new file mode 100644 index 00000000000..b5893f179a2 --- /dev/null +++ b/esphome/components/mipi_spi/models/spd2010.py @@ -0,0 +1,418 @@ +# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +# Adapted from esp_lcd_spd2010 2.0.0~1 for ESPHome. +# https://github.com/espressif/esp-iot-solution/tree/master/components/display/lcd/esp_lcd_spd2010 + +from esphome.components.mipi import MODE_RGB, DriverChip, delay +from esphome.components.spi import TYPE_QUAD +from esphome.const import CONF_MIRROR_X, CONF_MIRROR_Y + +# fmt: off +DriverChip( + "WAVESHARE-ESP32-S3-TOUCH-LCD-1.46", + width=412, + height=412, + bus_mode=TYPE_QUAD, + spi_mode="MODE0", + data_rate="40MHz", + cs_pin=21, + reset_pin={"pca9554": None, "number": 1}, + color_order=MODE_RGB, + draw_rounding=4, + transforms={CONF_MIRROR_X, CONF_MIRROR_Y}, + use_axis_flips=True, + reset_delay=20, + requires={"pca9554"}, + initsequence=( + delay(100), + (0xFF, 0x20, 0x10, 0x10), + (0x0C, 0x11), + (0x10, 0x02), + (0x11, 0x11), + (0x15, 0x42), + (0x16, 0x11), + (0x1A, 0x02), + (0x1B, 0x11), + (0x61, 0x80), + (0x62, 0x80), + (0x54, 0x44), + (0x58, 0x88), + (0x5C, 0xCC), + (0x20, 0x80), + (0x21, 0x81), + (0x22, 0x31), + (0x23, 0x20), + (0x24, 0x11), + (0x25, 0x11), + (0x26, 0x12), + (0x27, 0x12), + (0x30, 0x80), + (0x31, 0x81), + (0x32, 0x31), + (0x33, 0x20), + (0x34, 0x11), + (0x35, 0x11), + (0x36, 0x12), + (0x37, 0x12), + (0x41, 0x11), + (0x42, 0x22), + (0x43, 0x33), + (0x49, 0x11), + (0x4A, 0x22), + (0x4B, 0x33), + (0xFF, 0x20, 0x10, 0x15), + (0x00, 0x00), + (0x01, 0x00), + (0x02, 0x00), + (0x03, 0x00), + (0x04, 0x10), + (0x05, 0x0C), + (0x06, 0x23), + (0x07, 0x22), + (0x08, 0x21), + (0x09, 0x20), + (0x0A, 0x33), + (0x0B, 0x32), + (0x0C, 0x34), + (0x0D, 0x35), + (0x0E, 0x01), + (0x0F, 0x01), + (0x20, 0x00), + (0x21, 0x00), + (0x22, 0x00), + (0x23, 0x00), + (0x24, 0x0C), + (0x25, 0x10), + (0x26, 0x20), + (0x27, 0x21), + (0x28, 0x22), + (0x29, 0x23), + (0x2A, 0x33), + (0x2B, 0x32), + (0x2C, 0x34), + (0x2D, 0x35), + (0x2E, 0x01), + (0x2F, 0x01), + (0xFF, 0x20, 0x10, 0x16), + (0x00, 0x00), + (0x01, 0x00), + (0x02, 0x00), + (0x03, 0x00), + (0x04, 0x08), + (0x05, 0x04), + (0x06, 0x19), + (0x07, 0x18), + (0x08, 0x17), + (0x09, 0x16), + (0x0A, 0x33), + (0x0B, 0x32), + (0x0C, 0x34), + (0x0D, 0x35), + (0x0E, 0x01), + (0x0F, 0x01), + (0x20, 0x00), + (0x21, 0x00), + (0x22, 0x00), + (0x23, 0x00), + (0x24, 0x04), + (0x25, 0x08), + (0x26, 0x16), + (0x27, 0x17), + (0x28, 0x18), + (0x29, 0x19), + (0x2A, 0x33), + (0x2B, 0x32), + (0x2C, 0x34), + (0x2D, 0x35), + (0x2E, 0x01), + (0x2F, 0x01), + (0xFF, 0x20, 0x10, 0x12), + (0x00, 0x99), + (0x2A, 0x28), + (0x2B, 0x0F), + (0x2C, 0x16), + (0x2D, 0x28), + (0x2E, 0x0F), + (0xFF, 0x20, 0x10, 0xA0), + (0x08, 0xDC), + (0xFF, 0x20, 0x10, 0x45), + (0x01, 0x9C), + (0x03, 0x9C), + (0xFF, 0x20, 0x10, 0x42), + (0x05, 0x2C), + (0xFF, 0x20, 0x10, 0x11), + (0x50, 0x01), + (0xFF, 0x20, 0x10, 0x40), + (0x86, 0x00), + (0xFF, 0x20, 0x10, 0x12), + (0x0D, 0x66), + (0xFF, 0x20, 0x10, 0x17), + (0x39, 0x3C), + (0xFF, 0x20, 0x10, 0x31), + (0x38, 0x03), + (0x39, 0xF0), + (0x36, 0x03), + (0x37, 0xE8), + (0x34, 0x03), + (0x35, 0xCF), + (0x32, 0x03), + (0x33, 0xBA), + (0x30, 0x03), + (0x31, 0xA2), + (0x2E, 0x03), + (0x2F, 0x95), + (0x2C, 0x03), + (0x2D, 0x7E), + (0x2A, 0x03), + (0x2B, 0x62), + (0x28, 0x03), + (0x29, 0x44), + (0x26, 0x02), + (0x27, 0xFC), + (0x24, 0x02), + (0x25, 0xD0), + (0x22, 0x02), + (0x23, 0x98), + (0x20, 0x02), + (0x21, 0x6F), + (0x1E, 0x02), + (0x1F, 0x32), + (0x1C, 0x01), + (0x1D, 0xF6), + (0x1A, 0x01), + (0x1B, 0xB8), + (0x18, 0x01), + (0x19, 0x6E), + (0x16, 0x01), + (0x17, 0x41), + (0x14, 0x00), + (0x15, 0xFD), + (0x12, 0x00), + (0x13, 0xCF), + (0x10, 0x00), + (0x11, 0x98), + (0x0E, 0x00), + (0x0F, 0x89), + (0x0C, 0x00), + (0x0D, 0x79), + (0x0A, 0x00), + (0x0B, 0x67), + (0x08, 0x00), + (0x09, 0x55), + (0x06, 0x00), + (0x07, 0x3F), + (0x04, 0x00), + (0x05, 0x28), + (0x02, 0x00), + (0x03, 0x0E), + (0xFF, 0x20, 0x10, 0x32), + (0x38, 0x03), + (0x39, 0xF0), + (0x36, 0x03), + (0x37, 0xE8), + (0x34, 0x03), + (0x35, 0xCF), + (0x32, 0x03), + (0x33, 0xBA), + (0x30, 0x03), + (0x31, 0xA2), + (0x2E, 0x03), + (0x2F, 0x95), + (0x2C, 0x03), + (0x2D, 0x7E), + (0x2A, 0x03), + (0x2B, 0x62), + (0x28, 0x03), + (0x29, 0x44), + (0x26, 0x02), + (0x27, 0xFC), + (0x24, 0x02), + (0x25, 0xD0), + (0x22, 0x02), + (0x23, 0x98), + (0x20, 0x02), + (0x21, 0x6F), + (0x1E, 0x02), + (0x1F, 0x32), + (0x1C, 0x01), + (0x1D, 0xF6), + (0x1A, 0x01), + (0x1B, 0xB8), + (0x18, 0x01), + (0x19, 0x6E), + (0x16, 0x01), + (0x17, 0x41), + (0x14, 0x00), + (0x15, 0xFD), + (0x12, 0x00), + (0x13, 0xCF), + (0x10, 0x00), + (0x11, 0x98), + (0x0E, 0x00), + (0x0F, 0x89), + (0x0C, 0x00), + (0x0D, 0x79), + (0x0A, 0x00), + (0x0B, 0x67), + (0x08, 0x00), + (0x09, 0x55), + (0x06, 0x00), + (0x07, 0x3F), + (0x04, 0x00), + (0x05, 0x28), + (0x02, 0x00), + (0x03, 0x0E), + (0xFF, 0x20, 0x10, 0x11), + (0x60, 0x01), + (0x65, 0x03), + (0x66, 0x38), + (0x67, 0x04), + (0x68, 0x34), + (0x69, 0x03), + (0x61, 0x03), + (0x62, 0x38), + (0x63, 0x04), + (0x64, 0x34), + (0x0A, 0x11), + (0x0B, 0x20), + (0x0C, 0x20), + (0x55, 0x06), + (0xFF, 0x20, 0x10, 0x42), + (0x05, 0x3D), + (0x06, 0x03), + (0xFF, 0x20, 0x10, 0x12), + (0x1F, 0xDC), + (0xFF, 0x20, 0x10, 0x17), + (0x11, 0xAA), + (0x16, 0x12), + (0x0B, 0xC3), + (0x10, 0x0E), + (0x14, 0xAA), + (0x18, 0xA0), + (0x1A, 0x80), + (0x1F, 0x80), + (0xFF, 0x20, 0x10, 0x11), + (0x30, 0xEE), + (0xFF, 0x20, 0x10, 0x12), + (0x15, 0x0F), + (0xFF, 0x20, 0x10, 0x2D), + (0x01, 0x3E), + (0xFF, 0x20, 0x10, 0x40), + (0x83, 0xC4), + (0xFF, 0x20, 0x10, 0x12), + (0x00, 0xCC), + (0x36, 0xA0), + (0x2A, 0x2D), + (0x2B, 0x1E), + (0x2C, 0x26), + (0x2D, 0x2D), + (0x2E, 0x1E), + (0x1F, 0xE6), + (0xFF, 0x20, 0x10, 0xA0), + (0x08, 0xE6), + (0xFF, 0x20, 0x10, 0x12), + (0x10, 0x0F), + (0xFF, 0x20, 0x10, 0x18), + (0x01, 0x01), + (0x00, 0x1E), + (0xFF, 0x20, 0x10, 0x43), + (0x03, 0x04), + (0xFF, 0x20, 0x10, 0x18), + (0x3A, 0x01), + (0xFF, 0x20, 0x10, 0x50), + (0x05, 0x08), + (0xFF, 0x20, 0x10, 0x50), + (0x00, 0xA6), + (0x01, 0xA6), + (0xFF, 0x20, 0x10, 0x50), + (0x08, 0x55), + (0xFF, 0x20, 0x10, 0x10), + (0x0B, 0x43), + (0x0C, 0x12), + (0x10, 0x01), + (0x11, 0x12), + (0x15, 0x00), + (0x16, 0x00), + (0x1A, 0x00), + (0x1B, 0x00), + (0x61, 0x00), + (0x62, 0x00), + (0x51, 0x11), + (0x55, 0x55), + (0x58, 0x00), + (0x5C, 0x00), + (0xFF, 0x20, 0x10, 0x10), + (0x20, 0x81), + (0x21, 0x82), + (0x22, 0x72), + (0x30, 0x00), + (0x31, 0x00), + (0x32, 0x00), + (0xFF, 0x20, 0x10, 0x10), + (0x44, 0x44), + (0x45, 0x55), + (0x46, 0x66), + (0x47, 0x77), + (0x49, 0x00), + (0x4A, 0x00), + (0x4B, 0x00), + (0xFF, 0x20, 0x10, 0x17), + (0x37, 0x00), + (0xFF, 0x20, 0x10, 0x15), + (0x04, 0x08), + (0x05, 0x04), + (0x06, 0x1C), + (0x07, 0x1A), + (0x08, 0x18), + (0x09, 0x16), + (0x24, 0x05), + (0x25, 0x09), + (0x26, 0x17), + (0x27, 0x19), + (0x28, 0x1B), + (0x29, 0x1D), + (0xFF, 0x20, 0x10, 0x16), + (0x04, 0x09), + (0x05, 0x05), + (0x06, 0x1D), + (0x07, 0x1B), + (0x08, 0x19), + (0x09, 0x17), + (0x24, 0x04), + (0x25, 0x08), + (0x26, 0x16), + (0x27, 0x18), + (0x28, 0x1A), + (0x29, 0x1C), + (0xFF, 0x20, 0x10, 0x18), + (0x1F, 0x02), + (0xFF, 0x20, 0x10, 0x11), + (0x15, 0x99), + (0x16, 0x99), + (0x1C, 0x88), + (0x1D, 0x88), + (0x1E, 0x88), + (0x13, 0xF0), + (0x14, 0x34), + (0xFF, 0x20, 0x10, 0x12), + (0x12, 0x89), + (0x06, 0x06), + (0x18, 0x00), + (0xFF, 0x20, 0x10, 0x11), + (0x0A, 0x00), + (0x0B, 0xF0), + (0x0C, 0xF0), + (0x6A, 0x10), + (0xFF, 0x20, 0x10, 0x11), + (0x08, 0x70), + (0x09, 0x00), + (0xFF, 0x20, 0x10, 0x00), + (0x35, 0x00), + (0xFF, 0x20, 0x10, 0x12), + (0x21, 0x70), + (0xFF, 0x20, 0x10, 0x2D), + (0x02, 0x00), + (0xFF, 0x20, 0x10, 0x00), + ), +) +# fmt: on diff --git a/esphome/components/mitsubishi/__init__.py b/esphome/components/mitsubishi/__init__.py index e69de29bb2d..05ae0f0543d 100644 --- a/esphome/components/mitsubishi/__init__.py +++ b/esphome/components/mitsubishi/__init__.py @@ -0,0 +1 @@ +DOMAIN = "mitsubishi" diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index 9cb355fe9c1..dabd62b8f1d 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -4,7 +4,7 @@ namespace esphome::mitsubishi { -static const char *const TAG = "mitsubishi.climate"; +ESPHOME_LOG_TAG(TAG, "mitsubishi.climate"); // IR frame size for Mitsubishi climate static constexpr size_t MITSUBISHI_FRAME_SIZE = 18; diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105.cpp b/esphome/components/mitsubishi_cn105/mitsubishi_cn105.cpp index 3d30d1a25f0..c6c7b57c5fa 100644 --- a/esphome/components/mitsubishi_cn105/mitsubishi_cn105.cpp +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105.cpp @@ -8,7 +8,7 @@ namespace esphome::mitsubishi_cn105 { -static const char *const TAG = "mitsubishi_cn105.driver"; +ESPHOME_LOG_TAG(TAG, "mitsubishi_cn105.driver"); static constexpr uint32_t RESPONSE_TIMEOUT_MS = 2000; diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp index 53b8c21de60..70c5d479a3f 100644 --- a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp @@ -4,7 +4,7 @@ namespace esphome::mitsubishi_cn105 { -static const char *const TAG = "mitsubishi_cn105.climate"; +ESPHOME_LOG_TAG(TAG, "mitsubishi_cn105.climate"); static constexpr std::array MODE_MAP{ std::pair{MitsubishiCN105::Mode::AUTO, climate::CLIMATE_MODE_HEAT_COOL}, diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_component.cpp b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_component.cpp index e2a6ee05afd..e3222ecf530 100644 --- a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_component.cpp +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_component.cpp @@ -6,7 +6,7 @@ namespace esphome::mitsubishi_cn105 { -static const char *const TAG = "mitsubishi_cn105"; +ESPHOME_LOG_TAG(TAG, "mitsubishi_cn105"); void MitsubishiCN105Component::dump_config() { ESP_LOGCONFIG(TAG, "Mitsubishi CN105:"); diff --git a/esphome/components/mixer/__init__.py b/esphome/components/mixer/__init__.py index e69de29bb2d..5ea96da4d25 100644 --- a/esphome/components/mixer/__init__.py +++ b/esphome/components/mixer/__init__.py @@ -0,0 +1 @@ +DOMAIN = "mixer" diff --git a/esphome/components/mixer/speaker/mixer_speaker.cpp b/esphome/components/mixer/speaker/mixer_speaker.cpp index 41b7123269a..23867aef914 100644 --- a/esphome/components/mixer/speaker/mixer_speaker.cpp +++ b/esphome/components/mixer/speaker/mixer_speaker.cpp @@ -24,7 +24,7 @@ static const uint32_t MIXER_AUTO_STOP_DEBOUNCE_MS = 200; static const size_t TASK_STACK_SIZE = 4096; -static const char *const TAG = "speaker_mixer"; +ESPHOME_LOG_TAG(TAG, "speaker_mixer"); // Event bits for SourceSpeaker command processing enum SourceSpeakerEventBits : uint32_t { @@ -359,7 +359,7 @@ void MixerSpeaker::loop() { xEventGroupClearBits(this->event_group_, MIXER_TASK_COMMAND_START); } else { ESP_LOGE(TAG, "Failed to start; retrying in 1 second"); - this->status_momentary_error("failure", 1000); + this->status_momentary_error(1000); return; } } diff --git a/esphome/components/mk2pvrouter/__init__.py b/esphome/components/mk2pvrouter/__init__.py index d00b4ce8d03..7df1f7104f5 100644 --- a/esphome/components/mk2pvrouter/__init__.py +++ b/esphome/components/mk2pvrouter/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@FredM67"] DEPENDENCIES = ["uart"] +DOMAIN = "mk2pvrouter" mk2pvrouter_ns = cg.esphome_ns.namespace("mk2pvrouter") Mk2PVRouter = mk2pvrouter_ns.class_("Mk2PVRouter", cg.Component, uart.UARTDevice) diff --git a/esphome/components/mk2pvrouter/binary_sensor/mk2pvrouter_binary_sensor.cpp b/esphome/components/mk2pvrouter/binary_sensor/mk2pvrouter_binary_sensor.cpp index 9201596af28..61ecb237437 100644 --- a/esphome/components/mk2pvrouter/binary_sensor/mk2pvrouter_binary_sensor.cpp +++ b/esphome/components/mk2pvrouter/binary_sensor/mk2pvrouter_binary_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::mk2pvrouter { -static const char *const TAG = "mk2pvrouter_binary_sensor"; +ESPHOME_LOG_TAG(TAG, "mk2pvrouter_binary_sensor"); Mk2PVRouterBinarySensor::Mk2PVRouterBinarySensor(const char *tag) : Mk2PVRouterListener(tag) {} diff --git a/esphome/components/mk2pvrouter/mk2pvrouter.cpp b/esphome/components/mk2pvrouter/mk2pvrouter.cpp index f8aa1003bc4..eecaa1a1d41 100644 --- a/esphome/components/mk2pvrouter/mk2pvrouter.cpp +++ b/esphome/components/mk2pvrouter/mk2pvrouter.cpp @@ -5,7 +5,7 @@ namespace esphome::mk2pvrouter { -static const char *const TAG = "mk2pvrouter"; +ESPHOME_LOG_TAG(TAG, "mk2pvrouter"); constexpr uint8_t START_FRAME = 0x2; constexpr uint8_t END_FRAME = 0x3; diff --git a/esphome/components/mk2pvrouter/sensor/mk2pvrouter_sensor.cpp b/esphome/components/mk2pvrouter/sensor/mk2pvrouter_sensor.cpp index 5ea07307e49..92e2ea715c5 100644 --- a/esphome/components/mk2pvrouter/sensor/mk2pvrouter_sensor.cpp +++ b/esphome/components/mk2pvrouter/sensor/mk2pvrouter_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::mk2pvrouter { -static const char *const TAG = "mk2pvrouter_sensor"; +ESPHOME_LOG_TAG(TAG, "mk2pvrouter_sensor"); Mk2PVRouterSensor::Mk2PVRouterSensor(const char *tag, bool scale_centi) : Mk2PVRouterListener(tag), scale_centi_(scale_centi) {} diff --git a/esphome/components/mk2pvrouter/text_sensor/mk2pvrouter_text_sensor.cpp b/esphome/components/mk2pvrouter/text_sensor/mk2pvrouter_text_sensor.cpp index 4aea2ef7fc7..66e6ba0a3b2 100644 --- a/esphome/components/mk2pvrouter/text_sensor/mk2pvrouter_text_sensor.cpp +++ b/esphome/components/mk2pvrouter/text_sensor/mk2pvrouter_text_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::mk2pvrouter { -static const char *const TAG = "mk2pvrouter_text_sensor"; +ESPHOME_LOG_TAG(TAG, "mk2pvrouter_text_sensor"); Mk2PVRouterTextSensor::Mk2PVRouterTextSensor(const char *tag) : Mk2PVRouterListener(tag) {} diff --git a/esphome/components/mlx90393/__init__.py b/esphome/components/mlx90393/__init__.py index fc92f02120c..800d120558a 100644 --- a/esphome/components/mlx90393/__init__.py +++ b/esphome/components/mlx90393/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@functionpointer"] +DOMAIN = "mlx90393" diff --git a/esphome/components/mlx90393/sensor_mlx90393.cpp b/esphome/components/mlx90393/sensor_mlx90393.cpp index 2288e8ff845..e47b7d8c30b 100644 --- a/esphome/components/mlx90393/sensor_mlx90393.cpp +++ b/esphome/components/mlx90393/sensor_mlx90393.cpp @@ -5,7 +5,9 @@ namespace esphome::mlx90393 { -static const char *const TAG = "mlx90393"; +ESPHOME_LOG_TAG(TAG, "mlx90393"); + +static constexpr uint32_t VERIFY_SETTINGS_TIMEOUT_ID = 0; const LogString *settings_to_string(MLX90393Setting setting) { switch (setting) { @@ -114,7 +116,7 @@ void MLX90393Cls::setup() { } // start verify settings process - this->set_timeout("verify settings", 3000, [this]() { this->verify_settings_timeout_(MLX90393_GAIN_SEL); }); + this->set_timeout(VERIFY_SETTINGS_TIMEOUT_ID, 3000, [this]() { this->verify_settings_timeout_(MLX90393_GAIN_SEL); }); } void MLX90393Cls::dump_config() { @@ -268,7 +270,8 @@ void MLX90393Cls::verify_settings_timeout_(MLX90393Setting stage) { next_stage = static_cast(0); } - this->set_timeout("verify settings", 3000, [this, next_stage]() { this->verify_settings_timeout_(next_stage); }); + this->set_timeout(VERIFY_SETTINGS_TIMEOUT_ID, 3000, + [this, next_stage]() { this->verify_settings_timeout_(next_stage); }); } } // namespace esphome::mlx90393 diff --git a/esphome/components/mlx90614/__init__.py b/esphome/components/mlx90614/__init__.py index e69de29bb2d..e1db3524a40 100644 --- a/esphome/components/mlx90614/__init__.py +++ b/esphome/components/mlx90614/__init__.py @@ -0,0 +1 @@ +DOMAIN = "mlx90614" diff --git a/esphome/components/mlx90614/mlx90614.cpp b/esphome/components/mlx90614/mlx90614.cpp index 508b3743d18..a67d770cc3d 100644 --- a/esphome/components/mlx90614/mlx90614.cpp +++ b/esphome/components/mlx90614/mlx90614.cpp @@ -24,7 +24,7 @@ static const uint8_t MLX90614_ID2 = 0x3D; static const uint8_t MLX90614_ID3 = 0x3E; static const uint8_t MLX90614_ID4 = 0x3F; -static const char *const TAG = "mlx90614"; +ESPHOME_LOG_TAG(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; diff --git a/esphome/components/mmc5603/__init__.py b/esphome/components/mmc5603/__init__.py index cc88f262313..b7008ff1579 100644 --- a/esphome/components/mmc5603/__init__.py +++ b/esphome/components/mmc5603/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@benhoff"] +DOMAIN = "mmc5603" diff --git a/esphome/components/mmc5603/mmc5603.cpp b/esphome/components/mmc5603/mmc5603.cpp index 15e715e6751..b7d16b30edd 100644 --- a/esphome/components/mmc5603/mmc5603.cpp +++ b/esphome/components/mmc5603/mmc5603.cpp @@ -5,7 +5,7 @@ namespace esphome::mmc5603 { -static const char *const TAG = "mmc5603"; +ESPHOME_LOG_TAG(TAG, "mmc5603"); static const uint8_t MMC5603_ADDRESS = 0x30; static const uint8_t MMC56X3_PRODUCT_ID = 0x39; diff --git a/esphome/components/mmc5983/__init__.py b/esphome/components/mmc5983/__init__.py index c8db8c4300d..5e5287db5fb 100644 --- a/esphome/components/mmc5983/__init__.py +++ b/esphome/components/mmc5983/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@agoode"] +DOMAIN = "mmc5983" diff --git a/esphome/components/mmc5983/mmc5983.cpp b/esphome/components/mmc5983/mmc5983.cpp index a99df36a705..f7840aafbe9 100644 --- a/esphome/components/mmc5983/mmc5983.cpp +++ b/esphome/components/mmc5983/mmc5983.cpp @@ -6,7 +6,7 @@ namespace esphome::mmc5983 { -static const char *const TAG = "mmc5983"; +ESPHOME_LOG_TAG(TAG, "mmc5983"); namespace { constexpr uint8_t IC0_ADDR = 0x09; diff --git a/esphome/components/modbus/__init__.py b/esphome/components/modbus/__init__.py index db7484e306b..4e4d7fe1608 100644 --- a/esphome/components/modbus/__init__.py +++ b/esphome/components/modbus/__init__.py @@ -18,6 +18,7 @@ from esphome.types import ConfigType, TemplateArgsType _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ["uart"] +DOMAIN = "modbus" # Loading the hub makes the modbus_client.* actions available (they are registry entries only; no code is # generated unless a config uses one). AUTO_LOAD = ["modbus_client"] @@ -52,6 +53,12 @@ CONF_TURNAROUND_TIME = "turnaround_time" MODBUS_ROLES = ["client", "server"] +# The client hub takes these times as 16-bit milliseconds; a Modbus timeout is far shorter anyway. +_HUB_TIME_PERIOD = cv.All( + cv.positive_time_period_milliseconds, + cv.Range(max=cv.TimePeriod(milliseconds=65535)), +) + # The write (mutating) function codes, matching modbus::helpers::is_function_code_write(). 0x17 # (read/write multiple) is included: it mutates, so the hub treats it as a write despite its read half. @@ -273,12 +280,8 @@ CONFIG_SCHEMA = cv.typed_schema( { cv.GenerateID(): cv.declare_id(ModbusClient), cv.Optional(CONF_FLOW_CONTROL_PIN): pins.gpio_output_pin_schema, - cv.Optional( - CONF_SEND_WAIT_TIME, default="2000ms" - ): cv.positive_time_period_milliseconds, - cv.Optional( - CONF_TURNAROUND_TIME, default="600ms" - ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_SEND_WAIT_TIME, default="2000ms"): _HUB_TIME_PERIOD, + cv.Optional(CONF_TURNAROUND_TIME, default="600ms"): _HUB_TIME_PERIOD, } ) .extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 5f237be8dd7..80065546cf7 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -9,7 +9,9 @@ namespace esphome::modbus { -static const char *const TAG = "modbus"; +ESPHOME_LOG_TAG(TAG, "modbus"); + +static constexpr uint32_t DEFERRED_SEND_TIMEOUT_ID = 0; static constexpr size_t MODBUS_MAX_LOG_BYTES = 64; @@ -205,7 +207,7 @@ void ModbusServerHub::parse_modbus_frames() { while (!this->rx_buffer_.empty()) { if (this->deferred_payload_len_ != 0) { // Another frame arrived before the deferred reply went out, so the client has moved on. - this->cancel_timeout("deferred_send"); + this->cancel_timeout(DEFERRED_SEND_TIMEOUT_ID); ESP_LOGD(TAG, "Dropped deferred reply to %" PRIu8 ": a new frame arrived first", this->deferred_payload_[0]); this->deferred_payload_len_ = 0; } @@ -1216,7 +1218,7 @@ void ModbusServerHub::send_raw_(const uint8_t *payload, uint16_t len) { std::memcpy(this->deferred_payload_.data(), payload, len); this->deferred_payload_len_ = len; // set_timeout() takes milliseconds; round the microsecond delay up so we never fire early. - this->set_timeout("deferred_send", (this->tx_delay_remaining() + US_PER_MS - 1) / US_PER_MS, [this]() { + this->set_timeout(DEFERRED_SEND_TIMEOUT_ID, (this->tx_delay_remaining() + US_PER_MS - 1) / US_PER_MS, [this]() { ModbusFrame frame(this->deferred_payload_[0], this->deferred_payload_.data() + 1, this->deferred_payload_len_ - 1); this->deferred_payload_len_ = 0; diff --git a/esphome/components/modbus/modbus_helpers.cpp b/esphome/components/modbus/modbus_helpers.cpp index d80e6c86adb..19edc989381 100644 --- a/esphome/components/modbus/modbus_helpers.cpp +++ b/esphome/components/modbus/modbus_helpers.cpp @@ -5,7 +5,7 @@ namespace esphome::modbus::helpers { -static const char *const TAG = "modbus_helpers"; +ESPHOME_LOG_TAG(TAG, "modbus_helpers"); // A quantity/address pair is standard when the quantity is non-zero, within the per-table maximum, // and the range [start_address, start_address + quantity) stays inside the 16-bit address space. diff --git a/esphome/components/modbus_client/__init__.py b/esphome/components/modbus_client/__init__.py index 66ddcd7722d..5294f20f2de 100644 --- a/esphome/components/modbus_client/__init__.py +++ b/esphome/components/modbus_client/__init__.py @@ -18,6 +18,7 @@ from esphome.types import ConfigType, TemplateArgsType CODEOWNERS = ["@exciton"] DEPENDENCIES = ["modbus"] +DOMAIN = "modbus_client" MULTI_CONF = True # The modbus hub auto-loads this component to make the actions available. Without this, that auto-load # would try to create a device with no address. diff --git a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp index b0c927cf848..679b8dc7146 100644 --- a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp +++ b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.cpp @@ -3,7 +3,7 @@ namespace esphome::modbus_controller { -static const char *const TAG = "modbus_controller.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "modbus_controller.binary_sensor"); void ModbusBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Modbus Controller Binary Sensor", this); } diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index b8d06d3d5a3..6c90bfa4646 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -7,7 +7,7 @@ namespace esphome::modbus_controller { -static const char *const TAG = "modbus_controller"; +ESPHOME_LOG_TAG(TAG, "modbus_controller"); void ModbusController::setup() { this->create_polling_commands_(); } diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index 223aa12bec2..f33fc36b0a1 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -4,7 +4,7 @@ namespace esphome::modbus_controller { -static const char *const TAG = "modbus.number"; +ESPHOME_LOG_TAG(TAG, "modbus.number"); // Maximum uint16_t registers to log in verbose hex output static constexpr size_t MODBUS_NUMBER_MAX_LOG_REGISTERS = 32; diff --git a/esphome/components/modbus_controller/output/modbus_output.cpp b/esphome/components/modbus_controller/output/modbus_output.cpp index ad29015d328..9fb40f90053 100644 --- a/esphome/components/modbus_controller/output/modbus_output.cpp +++ b/esphome/components/modbus_controller/output/modbus_output.cpp @@ -6,7 +6,7 @@ namespace esphome::modbus_controller { -static const char *const TAG = "modbus_controller.output"; +ESPHOME_LOG_TAG(TAG, "modbus_controller.output"); // Maximum bytes to log in verbose hex output static constexpr size_t MODBUS_OUTPUT_MAX_LOG_BYTES = 64; diff --git a/esphome/components/modbus_controller/select/modbus_select.cpp b/esphome/components/modbus_controller/select/modbus_select.cpp index a2f15d54f66..793aa8e9ea4 100644 --- a/esphome/components/modbus_controller/select/modbus_select.cpp +++ b/esphome/components/modbus_controller/select/modbus_select.cpp @@ -3,7 +3,7 @@ namespace esphome::modbus_controller { -static const char *const TAG = "modbus_controller.select"; +ESPHOME_LOG_TAG(TAG, "modbus_controller.select"); void ModbusSelect::dump_config() { LOG_SELECT(TAG, "Modbus Controller Select", this); } diff --git a/esphome/components/modbus_controller/sensor/modbus_sensor.cpp b/esphome/components/modbus_controller/sensor/modbus_sensor.cpp index 2035f2220a3..98037c52370 100644 --- a/esphome/components/modbus_controller/sensor/modbus_sensor.cpp +++ b/esphome/components/modbus_controller/sensor/modbus_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::modbus_controller { -static const char *const TAG = "modbus_controller.sensor"; +ESPHOME_LOG_TAG(TAG, "modbus_controller.sensor"); void ModbusSensor::dump_config() { LOG_SENSOR(TAG, "Modbus Controller Sensor", this); } diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp index 855a7b28c30..fe2b2762313 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.cpp +++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp @@ -7,7 +7,7 @@ namespace esphome::modbus_controller { -static const char *const TAG = "modbus_controller.switch"; +ESPHOME_LOG_TAG(TAG, "modbus_controller.switch"); // Maximum bytes to log in verbose hex output static constexpr size_t MODBUS_SWITCH_MAX_LOG_BYTES = 64; diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp index 31b3fb3e558..74efd41e0f4 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp @@ -4,7 +4,7 @@ namespace esphome::modbus_controller { -static const char *const TAG = "modbus_controller.text_sensor"; +ESPHOME_LOG_TAG(TAG, "modbus_controller.text_sensor"); void ModbusTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Modbus Controller Text Sensor", this); } diff --git a/esphome/components/modbus_server/__init__.py b/esphome/components/modbus_server/__init__.py index 0de40a4cc1f..bff23e364ed 100644 --- a/esphome/components/modbus_server/__init__.py +++ b/esphome/components/modbus_server/__init__.py @@ -23,6 +23,7 @@ from .const import ( ) CODEOWNERS = ["@exciton"] +DOMAIN = "modbus_server" AUTO_LOAD = ["modbus"] diff --git a/esphome/components/modbus_server/modbus_server.cpp b/esphome/components/modbus_server/modbus_server.cpp index 65bf4ef2f40..763bbf872d4 100644 --- a/esphome/components/modbus_server/modbus_server.cpp +++ b/esphome/components/modbus_server/modbus_server.cpp @@ -6,7 +6,7 @@ namespace esphome::modbus_server { using modbus::ExceptionCode; using modbus::helpers::registers_to_number; -static const char *const TAG = "modbus_server"; +ESPHOME_LOG_TAG(TAG, "modbus_server"); // The widest Modbus value type (QWORD) spans four registers. static constexpr uint8_t MAX_REGISTERS_PER_VALUE = 4; diff --git a/esphome/components/monochromatic/__init__.py b/esphome/components/monochromatic/__init__.py index e69de29bb2d..4f572e8557f 100644 --- a/esphome/components/monochromatic/__init__.py +++ b/esphome/components/monochromatic/__init__.py @@ -0,0 +1 @@ +DOMAIN = "monochromatic" diff --git a/esphome/components/mopeka_ble/__init__.py b/esphome/components/mopeka_ble/__init__.py index ec7deb3067e..968447dadf3 100644 --- a/esphome/components/mopeka_ble/__init__.py +++ b/esphome/components/mopeka_ble/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@spbrogan", "@Fabian-Schmidt"] AUTO_LOAD = ["ble_device_base"] +DOMAIN = "mopeka_ble" CONF_SHOW_SENSORS_WITHOUT_SYNC = "show_sensors_without_sync" diff --git a/esphome/components/mopeka_ble/mopeka_ble.cpp b/esphome/components/mopeka_ble/mopeka_ble.cpp index 0bef1eb6d49..24e1667a4b9 100644 --- a/esphome/components/mopeka_ble/mopeka_ble.cpp +++ b/esphome/components/mopeka_ble/mopeka_ble.cpp @@ -4,7 +4,7 @@ namespace esphome::mopeka_ble { -static const char *const TAG = "mopeka_ble"; +ESPHOME_LOG_TAG(TAG, "mopeka_ble"); // Mopeka Std (CC2540) sensor details static const uint16_t SERVICE_UUID_CC2540 = 0xADA0; diff --git a/esphome/components/mopeka_pro_check/__init__.py b/esphome/components/mopeka_pro_check/__init__.py index c57f60f5219..86bf451227a 100644 --- a/esphome/components/mopeka_pro_check/__init__.py +++ b/esphome/components/mopeka_pro_check/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@spbrogan"] +DOMAIN = "mopeka_pro_check" diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp index fe3178d3aad..ba93c70d15c 100644 --- a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp @@ -3,7 +3,7 @@ namespace esphome::mopeka_pro_check { -static const char *const TAG = "mopeka_pro_check"; +ESPHOME_LOG_TAG(TAG, "mopeka_pro_check"); static const uint8_t MANUFACTURER_DATA_LENGTH = 10; static const uint16_t MANUFACTURER_ID = 0x0059; static const double MOPEKA_LPG_COEF[] = {0.573045, -0.002822, -0.00000535}; // Magic numbers provided by Mopeka diff --git a/esphome/components/mopeka_std_check/__init__.py b/esphome/components/mopeka_std_check/__init__.py index 88e344464f1..be8f3d329bc 100644 --- a/esphome/components/mopeka_std_check/__init__.py +++ b/esphome/components/mopeka_std_check/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@Fabian-Schmidt"] +DOMAIN = "mopeka_std_check" diff --git a/esphome/components/mopeka_std_check/mopeka_std_check.cpp b/esphome/components/mopeka_std_check/mopeka_std_check.cpp index d70306c97d4..f63f2bc190b 100644 --- a/esphome/components/mopeka_std_check/mopeka_std_check.cpp +++ b/esphome/components/mopeka_std_check/mopeka_std_check.cpp @@ -5,7 +5,7 @@ namespace esphome::mopeka_std_check { -static const char *const TAG = "mopeka_std_check"; +ESPHOME_LOG_TAG(TAG, "mopeka_std_check"); static const uint16_t SERVICE_UUID = 0xADA0; static const uint8_t MANUFACTURER_DATA_LENGTH = 23; static const uint16_t MANUFACTURER_ID = 0x000D; diff --git a/esphome/components/motion/__init__.py b/esphome/components/motion/__init__.py index 63a104f1a7e..741996cbb83 100644 --- a/esphome/components/motion/__init__.py +++ b/esphome/components/motion/__init__.py @@ -1,13 +1,18 @@ from collections.abc import Callable +import logging import re from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_ON_ERROR, CONF_ON_SUCCESS +from esphome.const import CONF_ID, CONF_ON_ERROR, CONF_ON_SUCCESS, CONF_UPDATE_INTERVAL +from esphome.core import ID from esphome.cpp_generator import MockObj, MockObjClass +import esphome.final_validate as fv from esphome.helpers import fnv1_hash_object_id +_LOGGER = logging.getLogger(__name__) + CODEOWNERS = ["@esphome/core"] DOMAIN = "motion" @@ -37,6 +42,48 @@ SENSOR_SCHEMA = cv.Schema( _AXIS_REGEX = re.compile(r"^[+-]?[xyz]$", re.IGNORECASE) +# Fast motion patterns (shakes, free-fall, sudden movement) need frequent samples +# to be detected reliably; a slower parent update_interval makes them likely to be +# missed between polls. +MAX_RECOMMENDED_UPDATE_INTERVAL_MS = 100 + + +def get_motion_config(motion_id: ID) -> dict: + """Look up the (already-validated) config of the motion hub referenced by motion_id.""" + full_config = fv.full_config.get() + motion_path = full_config.get_path_for_id(motion_id)[:-1] + return full_config.get_config_for_path(motion_path) + + +def check_update_interval(motion_id: ID, feature_name: str) -> None: + """Warn if the parent motion component polls too slowly for fast motion detection.""" + motion_config = get_motion_config(motion_id) + update_interval = motion_config[CONF_UPDATE_INTERVAL] + if update_interval.total_milliseconds > MAX_RECOMMENDED_UPDATE_INTERVAL_MS: + _LOGGER.warning( + "Motion component '%s' has update_interval %s, but %s detection " + "works best with an update_interval of %dms or less.", + motion_id, + update_interval, + feature_name, + MAX_RECOMMENDED_UPDATE_INTERVAL_MS, + ) + + +def check_has_accelerometer( + motion_id: ID, feature_name: str, path: list[str] | None = None +) -> None: + """Raise if the parent motion device does not measure acceleration. + + `path` locates the error within the calling entity's config. + """ + motion_config = get_motion_config(motion_id) + if not motion_config.get(KEY_ACCELEROMETER, False): + raise cv.Invalid( + f"The motion device does not measure acceleration, required for {feature_name}", + path=path, + ) + def _axis_map(config: dict) -> dict: errors = [] diff --git a/esphome/components/motion/binary_sensor/__init__.py b/esphome/components/motion/binary_sensor/__init__.py new file mode 100644 index 00000000000..0a3876c8ab2 --- /dev/null +++ b/esphome/components/motion/binary_sensor/__init__.py @@ -0,0 +1,120 @@ +from collections.abc import Callable +import math +from typing import Any + +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import CONF_DURATION, CONF_ID, CONF_THRESHOLD, CONF_TYPE +from esphome.types import ConfigType + +from .. import ( + CONF_MOTION_ID, + MotionComponent, + check_has_accelerometer, + check_update_interval, + motion_ns, +) + +DEPENDENCIES = ["motion"] + +MotionBinarySensor = motion_ns.class_( + "MotionBinarySensor", binary_sensor.BinarySensor, cg.Component +) + +MotionBinarySensorType = motion_ns.enum("MotionBinarySensorType") + +SENSOR_TYPES = { + "face_up": MotionBinarySensorType.MOTION_BINARY_SENSOR_FACE_UP, + "face_down": MotionBinarySensorType.MOTION_BINARY_SENSOR_FACE_DOWN, + "free_fall": MotionBinarySensorType.MOTION_BINARY_SENSOR_FREE_FALL, + "moving": MotionBinarySensorType.MOTION_BINARY_SENSOR_MOVING, +} + +# face_up / face_down configure their threshold as a maximum tilt angle in degrees; +# the C++ side compares against the cosine of that angle. +ANGLE_THRESHOLD_TYPES = ("face_up", "face_down") + + +def _binary_sensor_schema( + default_threshold: float, + threshold_validator: Callable[[Any], Any], + default_duration: str | None = None, +) -> cv.Schema: + schema = ( + binary_sensor.binary_sensor_schema(MotionBinarySensor) + .extend( + { + cv.GenerateID(CONF_MOTION_ID): cv.use_id(MotionComponent), + cv.Optional( + CONF_THRESHOLD, default=default_threshold + ): threshold_validator, + } + ) + .extend(cv.COMPONENT_SCHEMA) + ) + + if default_duration is not None: + schema = schema.extend( + { + cv.Optional( + CONF_DURATION, default=default_duration + ): cv.positive_time_period_milliseconds, + } + ) + return schema + + +# Tilt angle in degrees, from horizontal, within which the device counts as face up/down. +# 0 is excluded: cos(0) == 1.0 would make the C++ comparison always false, so +# face_up/face_down would never trigger. +_angle_threshold = cv.float_range(min=0.0, max=90.0, min_included=False) +# 0 is excluded: free_fall would never trigger and moving would always be on. +_positive_threshold = cv.float_range(min=0.0, min_included=False) + +CONFIG_SCHEMA = cv.typed_schema( + { + "face_up": _binary_sensor_schema(30.0, _angle_threshold), + "face_down": _binary_sensor_schema(30.0, _angle_threshold), + "free_fall": _binary_sensor_schema(0.15, _positive_threshold, "100ms"), + "moving": _binary_sensor_schema(0.05, _positive_threshold, "2s"), + } +) + +# These types detect brief motion events, so they need frequent samples; +# face_up/face_down track a steady orientation and aren't time-sensitive. +_FAST_DETECTION_TYPES = ("free_fall", "moving") + +# face_up/face_down/free_fall are entirely accelerometer-driven; "moving" is exempt +# since it detects motion from either the accelerometer or the gyroscope. +_ACCEL_ONLY_TYPES = ("face_up", "face_down", "free_fall") + + +def _final_validate(config: dict) -> None: + sensor_type = config[CONF_TYPE] + if sensor_type in _FAST_DETECTION_TYPES: + check_update_interval(config[CONF_MOTION_ID], sensor_type.replace("_", "-")) + if sensor_type in _ACCEL_ONLY_TYPES: + check_has_accelerometer( + config[CONF_MOTION_ID], sensor_type.replace("_", "-"), path=[CONF_TYPE] + ) + + +FINAL_VALIDATE_SCHEMA = _final_validate + + +async def to_code(config: ConfigType) -> None: + sensor_type = config[CONF_TYPE] + parent = await cg.get_variable(config[CONF_MOTION_ID]) + + var = cg.new_Pvariable(config[CONF_ID], parent, SENSOR_TYPES[sensor_type]) + await binary_sensor.register_binary_sensor(var, config) + await cg.register_component(var, config) + + threshold = config[CONF_THRESHOLD] + if sensor_type in ANGLE_THRESHOLD_TYPES: + # Convert the configured tilt angle (degrees) to the cosine the C++ side expects. + threshold = round(math.cos(math.radians(threshold)), 6) + cg.add(var.set_threshold(threshold)) + if (duration := config.get(CONF_DURATION)) is not None: + cg.add(var.set_duration(duration)) diff --git a/esphome/components/motion/binary_sensor/motion_binary_sensor.cpp b/esphome/components/motion/binary_sensor/motion_binary_sensor.cpp new file mode 100644 index 00000000000..1de535a0ff6 --- /dev/null +++ b/esphome/components/motion/binary_sensor/motion_binary_sensor.cpp @@ -0,0 +1,183 @@ +#include "motion_binary_sensor.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include "esphome/core/application.h" +#include "esphome/core/progmem.h" + +namespace esphome::motion { + +ESPHOME_LOG_TAG(TAG, "motion.binary_sensor"); + +// Thresholds used to decide the device is at rest for face_up / face_down detection. +// While moving (shaking, being picked up) the orientation reading is dominated by +// linear acceleration and cannot be trusted, so those sensors block (hold) instead. +static constexpr float STILL_ACCEL_TOLERANCE = 0.12f; // max deviation of |accel| from 1g, in g +static constexpr float STILL_GYRO_THRESHOLD = 15.0f; // max angular rate magnitude, in °/s +static constexpr float GYRO_THRESHOLD_SCALE = 50.0f; // arbitrary gyro threshold scale, in °/s per g of acceleration + +MotionBinarySensor::MotionBinarySensor(MotionComponent *parent, MotionBinarySensorType type) + : parent_(parent), type_(type) {} + +bool MotionBinarySensor::is_stationary(const MotionData &data) { + float ax = data.acceleration[X_AXIS]; + float ay = data.acceleration[Y_AXIS]; + float az = data.acceleration[Z_AXIS]; + if (std::isnan(ax) || std::isnan(ay) || std::isnan(az)) + return false; + + // Total acceleration must be close to 1g; a larger deviation means the device is + // being accelerated (shaken / moved) and the gravity direction cannot be trusted. + float mag = std::sqrt(ax * ax + ay * ay + az * az); + if (std::fabs(mag - 1.0f) > STILL_ACCEL_TOLERANCE) + return false; + + // If a gyroscope is present, also require the angular rate to be low. + float gx = data.angular_rate[X_AXIS]; + float gy = data.angular_rate[Y_AXIS]; + float gz = data.angular_rate[Z_AXIS]; + if (!std::isnan(gx) && !std::isnan(gy) && !std::isnan(gz)) { + float gmag = std::sqrt(gx * gx + gy * gy + gz * gz); + if (gmag > STILL_GYRO_THRESHOLD) + return false; + } + return true; +} + +void MotionBinarySensor::setup() { + this->parent_->add_listener([this](MotionData const &data) { this->process_motion_data_(data); }); + this->publish_state(false); // default to false until the first update +} + +PROGMEM_STRING_TABLE(MotionBinarySensorTypeNames, "face_up", "face_down", "free_fall", "moving", "unknown"); + +void MotionBinarySensor::dump_config() { + LOG_BINARY_SENSOR("", "Motion Binary Sensor", this); + ESP_LOGCONFIG( + TAG, + " Type: %s\n" + " Threshold: %.3f\n" + " Duration: %" PRIu32 " ms", + LOG_STR_ARG(MotionBinarySensorTypeNames::get_log_str(this->type_, MotionBinarySensorTypeNames::LAST_INDEX)), + this->threshold_, this->duration_); +} + +void MotionBinarySensor::process_motion_data_(const MotionData &data) { + uint32_t now = App.get_loop_component_start_time(); + + switch (this->type_) { + case MOTION_BINARY_SENSOR_FACE_UP: + case MOTION_BINARY_SENSOR_FACE_DOWN: { + // Block while the device is moving: hold the last stable state instead of + // reacting to transient acceleration spikes from shaking or handling. + if (!is_stationary(data)) + break; + + float ax = data.acceleration[X_AXIS]; + float ay = data.acceleration[Y_AXIS]; + float az = data.acceleration[Z_AXIS]; + float mag = std::sqrt(ax * ax + ay * ay + az * az); + // is_stationary_() guarantees mag is close to 1g, so this is just a safety net. + if (mag < 0.1f) + break; + + // threshold_ is the cosine of the maximum tilt: face_up / face_down are only + // reported when the device is within that tilt of horizontal. Beyond it, both + // sensors read false. Normalising by the magnitude makes the tilt limit + // independent of any residual acceleration. + float cos_tilt = az / mag; + if (this->type_ == MOTION_BINARY_SENSOR_FACE_UP) { + this->publish_state(cos_tilt > this->threshold_); + } else { + this->publish_state(cos_tilt < -this->threshold_); + } + break; + } + case MOTION_BINARY_SENSOR_FREE_FALL: { + float ax = data.acceleration[X_AXIS]; + float ay = data.acceleration[Y_AXIS]; + float az = data.acceleration[Z_AXIS]; + if (std::isnan(ax) || std::isnan(ay) || std::isnan(az)) { + // Don't let a gap in valid data count towards the free-fall duration. + this->free_fall_candidate_ = false; + return; + } + + float mag = std::sqrt(ax * ax + ay * ay + az * az); + + if (mag < this->threshold_) { + if (!this->free_fall_candidate_) { + this->free_fall_candidate_ = true; + this->free_fall_start_time_ = now; + } else if (now - this->free_fall_start_time_ >= this->duration_) { + this->publish_state(true); + } + } else { + this->free_fall_candidate_ = false; + this->publish_state(false); + } + break; + } + case MOTION_BINARY_SENSOR_MOVING: { + float ax = data.acceleration[X_AXIS]; + float ay = data.acceleration[Y_AXIS]; + float az = data.acceleration[Z_AXIS]; + float gx = data.angular_rate[X_AXIS]; + float gy = data.angular_rate[Y_AXIS]; + float gz = data.angular_rate[Z_AXIS]; + + bool moving = false; + + // Check acceleration delta. Require all three axes to be valid so a NaN on any + // axis can't poison last_accel_ and silently stop motion detection. + bool accel_valid = !std::isnan(ax) && !std::isnan(ay) && !std::isnan(az); + if (accel_valid) { + if (!std::isnan(this->last_accel_[0])) { + float dx = ax - this->last_accel_[0]; + float dy = ay - this->last_accel_[1]; + float dz = az - this->last_accel_[2]; + float accel_diff = std::sqrt(dx * dx + dy * dy + dz * dz); + if (accel_diff > this->threshold_) { + moving = true; + } + } + this->last_accel_[0] = ax; + this->last_accel_[1] = ay; + this->last_accel_[2] = az; + } + + // Check angular rate delta. Require all three axes to be valid for the same reason. + bool gyro_valid = !std::isnan(gx) && !std::isnan(gy) && !std::isnan(gz); + if (gyro_valid) { + if (!std::isnan(this->last_gyro_[0])) { + float dgx = gx - this->last_gyro_[0]; + float dgy = gy - this->last_gyro_[1]; + float dgz = gz - this->last_gyro_[2]; + float gyro_diff = std::sqrt(dgx * dgx + dgy * dgy + dgz * dgz); + if (gyro_diff > this->threshold_ * GYRO_THRESHOLD_SCALE) { + moving = true; + } + } + this->last_gyro_[0] = gx; + this->last_gyro_[1] = gy; + this->last_gyro_[2] = gz; + } + + // With no usable data this sample, don't assert "not moving" -- just wait for + // the next one. + if (!accel_valid && !gyro_valid) + break; + + if (moving) { + this->publish_state(true); + this->last_event_time_ = now; + } else { + if (this->state && (now - this->last_event_time_ >= this->duration_)) { + this->publish_state(false); + } + } + break; + } + } +} + +} // namespace esphome::motion diff --git a/esphome/components/motion/binary_sensor/motion_binary_sensor.h b/esphome/components/motion/binary_sensor/motion_binary_sensor.h new file mode 100644 index 00000000000..6d556598d45 --- /dev/null +++ b/esphome/components/motion/binary_sensor/motion_binary_sensor.h @@ -0,0 +1,50 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "../motion_component.h" + +namespace esphome::motion { + +enum MotionBinarySensorType : uint8_t { + MOTION_BINARY_SENSOR_FACE_UP = 0, + MOTION_BINARY_SENSOR_FACE_DOWN, + MOTION_BINARY_SENSOR_FREE_FALL, + MOTION_BINARY_SENSOR_MOVING, +}; + +class MotionBinarySensor : public Component, public binary_sensor::BinarySensor { + public: + explicit MotionBinarySensor(MotionComponent *parent, MotionBinarySensorType type); + + void setup() override; + void dump_config() override; + + void set_threshold(float threshold) { this->threshold_ = threshold; } + void set_duration(uint32_t duration) { this->duration_ = duration; } + + protected: + void process_motion_data_(const MotionData &data); + + /// True when the device is at rest: total acceleration is close to 1g and (if a + /// gyroscope is present) the angular rate is low. While not stationary the + /// face_up / face_down orientation is unreliable, so their updates are suspended. + static bool is_stationary(const MotionData &data); + + MotionComponent *parent_; + float threshold_{0.0f}; + uint32_t duration_{0}; + + // Tracking states + uint32_t last_event_time_{0}; + uint32_t free_fall_start_time_{0}; + + // For derivative/variance tracking + float last_accel_[3]{NAN, NAN, NAN}; + float last_gyro_[3]{NAN, NAN, NAN}; + + MotionBinarySensorType type_; + bool free_fall_candidate_{false}; +}; + +} // namespace esphome::motion diff --git a/esphome/components/motion/event/__init__.py b/esphome/components/motion/event/__init__.py new file mode 100644 index 00000000000..9c72535f131 --- /dev/null +++ b/esphome/components/motion/event/__init__.py @@ -0,0 +1,55 @@ +import esphome.codegen as cg +from esphome.components import event +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_THRESHOLD +from esphome.types import ConfigType + +from .. import ( + CONF_MOTION_ID, + MotionComponent, + check_has_accelerometer, + check_update_interval, + motion_ns, +) + +DEPENDENCIES = ["motion"] + +MotionEvent = motion_ns.class_("MotionEvent", event.Event, cg.Component) + +EVENT_TYPES = ["shake"] + +CONF_COOLDOWN = "cooldown" + +CONFIG_SCHEMA = ( + event.event_schema(MotionEvent) + .extend( + { + cv.GenerateID(CONF_MOTION_ID): cv.use_id(MotionComponent), + cv.Optional(CONF_THRESHOLD, default=0.5): cv.float_range( + min=0.0, min_included=False + ), + cv.Optional( + CONF_COOLDOWN, default="500ms" + ): cv.positive_time_period_milliseconds, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + + +def _final_validate(config: dict) -> None: + check_update_interval(config[CONF_MOTION_ID], "shake") + check_has_accelerometer(config[CONF_MOTION_ID], "shake") + + +FINAL_VALIDATE_SCHEMA = _final_validate + + +async def to_code(config: ConfigType) -> None: + parent = await cg.get_variable(config[CONF_MOTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], parent) + await event.register_event(var, config, event_types=EVENT_TYPES) + await cg.register_component(var, config) + + cg.add(var.set_threshold(config[CONF_THRESHOLD])) + cg.add(var.set_cooldown(config[CONF_COOLDOWN])) diff --git a/esphome/components/motion/event/motion_event.cpp b/esphome/components/motion/event/motion_event.cpp new file mode 100644 index 00000000000..9ad2668fd4b --- /dev/null +++ b/esphome/components/motion/event/motion_event.cpp @@ -0,0 +1,57 @@ +#include "motion_event.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include "esphome/core/application.h" + +namespace esphome::motion { + +ESPHOME_LOG_TAG(TAG, "motion.event"); + +MotionEvent::MotionEvent(MotionComponent *parent) : parent_(parent) {} + +void MotionEvent::setup() { + this->parent_->add_listener([this](MotionData const &data) { this->process_motion_data_(data); }); +} + +void MotionEvent::dump_config() { + LOG_EVENT("", "Motion Event", this); + ESP_LOGCONFIG(TAG, + " Threshold: %.3f\n" + " Cooldown: %" PRIu32 " ms", + this->threshold_, this->cooldown_); +} + +void MotionEvent::process_motion_data_(const MotionData &data) { + float ax = data.acceleration[X_AXIS]; + float ay = data.acceleration[Y_AXIS]; + float az = data.acceleration[Z_AXIS]; + if (std::isnan(ax) || std::isnan(ay) || std::isnan(az)) { + // Reset the baseline so the next valid sample doesn't jerk-compare across the gap. + this->last_accel_[0] = NAN; + this->last_accel_[1] = NAN; + this->last_accel_[2] = NAN; + return; + } + + uint32_t now = App.get_loop_component_start_time(); + + if (!std::isnan(this->last_accel_[0])) { + float dx = ax - this->last_accel_[0]; + float dy = ay - this->last_accel_[1]; + float dz = az - this->last_accel_[2]; + float jerk_mag = std::sqrt(dx * dx + dy * dy + dz * dz); + + if (jerk_mag > this->threshold_) { + if (now - this->last_trigger_time_ >= this->cooldown_) { + this->trigger("shake"); + this->last_trigger_time_ = now; + } + } + } + + this->last_accel_[0] = ax; + this->last_accel_[1] = ay; + this->last_accel_[2] = az; +} + +} // namespace esphome::motion diff --git a/esphome/components/motion/event/motion_event.h b/esphome/components/motion/event/motion_event.h new file mode 100644 index 00000000000..e8cd9f53520 --- /dev/null +++ b/esphome/components/motion/event/motion_event.h @@ -0,0 +1,31 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/event/event.h" +#include "../motion_component.h" + +namespace esphome::motion { + +class MotionEvent : public Component, public event::Event { + public: + explicit MotionEvent(MotionComponent *parent); + + void setup() override; + void dump_config() override; + + void set_threshold(float threshold) { this->threshold_ = threshold; } + void set_cooldown(uint32_t cooldown) { this->cooldown_ = cooldown; } + + protected: + void process_motion_data_(const MotionData &data); + + MotionComponent *parent_; + float threshold_{0.5f}; + uint32_t cooldown_{500}; + + // State tracking for shake detection + uint32_t last_trigger_time_{0}; + float last_accel_[3]{NAN, NAN, NAN}; +}; + +} // namespace esphome::motion diff --git a/esphome/components/motion/motion_component.cpp b/esphome/components/motion/motion_component.cpp index 226dc94e638..3171bef4ec9 100644 --- a/esphome/components/motion/motion_component.cpp +++ b/esphome/components/motion/motion_component.cpp @@ -3,7 +3,7 @@ namespace esphome::motion { -static const char *const TAG = "motion"; +ESPHOME_LOG_TAG(TAG, "motion"); static void log_matrix(const float m[9]) { ESP_LOGCONFIG(TAG, " Calibration matrix:"); diff --git a/esphome/components/motion/motion_component.h b/esphome/components/motion/motion_component.h index d117ca00320..2ca920dbe0e 100644 --- a/esphome/components/motion/motion_component.h +++ b/esphome/components/motion/motion_component.h @@ -23,6 +23,33 @@ static constexpr uint8_t X_AXIS = 0; static constexpr uint8_t Y_AXIS = 1; static constexpr uint8_t Z_AXIS = 2; +/// Compute the device's in-plane orientation from the gravity vector. +/// +/// Returns NAN when the device is flat (lying face up or face down), i.e. when the +/// horizontal component of gravity, normalised by the total acceleration, is below +/// `flat_threshold` (the sine of the minimum tilt angle). Otherwise returns the +/// rotation snapped to the nearest of 0, 90, 180 or 270 degrees, derived from the +/// direction of the horizontal gravity component. +inline float orientation_degrees(const MotionData &data, float flat_threshold) { + float ax = data.acceleration[X_AXIS]; + float ay = data.acceleration[Y_AXIS]; + float az = data.acceleration[Z_AXIS]; + if (std::isnan(ax) || std::isnan(ay) || std::isnan(az)) + return NAN; + float mag = std::sqrt(ax * ax + ay * ay + az * az); + if (mag < 0.1f) + return NAN; + // Horizontal component of gravity; near zero when the device lies flat. + float h = std::sqrt(ax * ax + ay * ay); + if (h / mag < flat_threshold) + return NAN; + // Direction of the horizontal component, snapped to the nearest 90°. + float angle = std::atan2(ay, ax) * (180.0f / std::numbers::pi_v); + int quadrant = static_cast(std::lround(angle / 90.0f)); + quadrant = ((quadrant % 4) + 4) % 4; // normalise to 0..3 + return quadrant * 90.0f; +} + // Persisted calibration. `base_hash` ties the stored matrix to the build-time // (axis_map / transform_matrix) base; if the base changes the saved calibration // is ignored. Stored under a stable, ID-derived key so it overwrites in place. @@ -78,7 +105,7 @@ class MotionComponent : public PollingComponent { output[2] = input[X_AXIS] * this->matrix_[6] + input[Y_AXIS] * this->matrix_[7] + input[Z_AXIS] * this->matrix_[8]; } - LazyCallbackManager motion_data_callback_{}; + LazyCallbackManager motion_data_callback_{}; uint32_t pref_key_{0}; uint32_t base_hash_{0}; // hash of base_matrix_, captured in setup() ESPPreferenceObject pref_{}; diff --git a/esphome/components/motion/sensor.py b/esphome/components/motion/sensor.py index ad3163a01a5..db8a8692364 100644 --- a/esphome/components/motion/sensor.py +++ b/esphome/components/motion/sensor.py @@ -1,4 +1,6 @@ # YAML config keys +import math + import esphome.codegen as cg from esphome.components import sensor import esphome.config_validation as cv @@ -6,6 +8,7 @@ from esphome.const import ( CONF_TYPE, ICON_ACCELERATION, ICON_ROTATE_RIGHT, + ICON_SCREEN_ROTATION, STATE_CLASS_MEASUREMENT, UNIT_DEGREE_PER_SECOND, UNIT_DEGREES, @@ -13,7 +16,7 @@ from esphome.const import ( ) from esphome.cpp_generator import MockObj from esphome.cpp_types import std_ns -import esphome.final_validate as fv +from esphome.types import ConfigType from . import ( AXES, @@ -21,6 +24,7 @@ from . import ( KEY_ACCELEROMETER, KEY_GYROSCOPE, SENSOR_SCHEMA, + get_motion_config, motion_ns, ) @@ -28,10 +32,17 @@ MotionData = motion_ns.class_("MotionData") CONF_PITCH = "pitch" CONF_ROLL = "roll" +CONF_ORIENTATION = "orientation" +CONF_FLAT_THRESHOLD = "flat_threshold" ICON_SEESAW = "mdi:seesaw" +# Minimum tilt angle (degrees) before the device is considered tilted enough to +# report an orientation. Below this the device is treated as flat (sensor reports +# NAN). Configured in degrees; converted to the sine of the angle for the C++ side. +DEFAULT_FLAT_THRESHOLD = 30.0 -def _accel_sensor_schema(): + +def _accel_sensor_schema() -> cv.Schema: return sensor.sensor_schema( unit_of_measurement=UNIT_G, icon=ICON_ACCELERATION, @@ -40,7 +51,7 @@ def _accel_sensor_schema(): ).extend(SENSOR_SCHEMA) -def _gyro_sensor_schema(): +def _gyro_sensor_schema() -> cv.Schema: return sensor.sensor_schema( unit_of_measurement=UNIT_DEGREE_PER_SECOND, icon=ICON_ROTATE_RIGHT, @@ -49,7 +60,7 @@ def _gyro_sensor_schema(): ).extend(SENSOR_SCHEMA) -def _level_sensor_schema(): +def _level_sensor_schema() -> cv.Schema: return sensor.sensor_schema( unit_of_measurement=UNIT_DEGREES, icon=ICON_SEESAW, @@ -58,6 +69,28 @@ def _level_sensor_schema(): ).extend(SENSOR_SCHEMA) +def _orientation_sensor_schema() -> cv.Schema: + # Reports a discrete rotation (0/90/180/270) or NAN when flat, so it is not a + # continuous measurement (no state_class). + return ( + sensor.sensor_schema( + unit_of_measurement=UNIT_DEGREES, + icon=ICON_SCREEN_ROTATION, + accuracy_decimals=0, + ) + .extend(SENSOR_SCHEMA) + .extend( + { + # 90 is excluded: sin(90) == 1.0 would make the C++ comparison always + # true, so orientation would report NAN (flat) on every reading. + cv.Optional( + CONF_FLAT_THRESHOLD, default=DEFAULT_FLAT_THRESHOLD + ): cv.float_range(min=0.0, max=90.0, max_included=False), + } + ) + ) + + _ACCELERATIONS = ["acceleration_" + a for a in AXES] _GYROSCOPES = ["gyroscope_" + g for g in AXES] _ANGULAR_RATES = ["angular_rate_" + r for r in AXES] @@ -68,20 +101,20 @@ CONFIG_SCHEMA = cv.typed_schema( **{x: _gyro_sensor_schema() for x in _GYROSCOPES}, **{x: _gyro_sensor_schema() for x in _ANGULAR_RATES}, **{x: _level_sensor_schema() for x in (CONF_PITCH, CONF_ROLL)}, + CONF_ORIENTATION: _orientation_sensor_schema(), } ) def _final_validate(config: dict) -> None: - full_config = fv.full_config.get() - motion_path = full_config.get_path_for_id(config[CONF_MOTION_ID])[:-1] - motion_config = full_config.get_config_for_path(motion_path) + motion_config = get_motion_config(config[CONF_MOTION_ID]) has_accel = motion_config.get(KEY_ACCELEROMETER, False) has_gyro = motion_config.get(KEY_GYROSCOPE, False) sensor_type = config[CONF_TYPE] if ( - sensor_type in _ACCELERATIONS or sensor_type in (CONF_ROLL, CONF_PITCH) + sensor_type in _ACCELERATIONS + or sensor_type in (CONF_ROLL, CONF_PITCH, CONF_ORIENTATION) ) and not has_accel: raise cv.Invalid( "The motion device does not measure acceleration", path=[CONF_TYPE] @@ -95,11 +128,15 @@ def _final_validate(config: dict) -> None: FINAL_VALIDATE_SCHEMA = _final_validate -def build_sensor_expr(sensor_type: str, data: MockObj) -> MockObj: +def build_sensor_expr(sensor_type: str, data: MockObj, config: dict) -> MockObj: """Build the C++ expression for a motion sensor type.""" # Note that is included via this component's header file. pif = std_ns.namespace("numbers").pi_v.template(cg.float_) + if sensor_type == CONF_ORIENTATION: + # The C++ helper compares against the sine of the tilt angle. + threshold = round(math.sin(math.radians(config[CONF_FLAT_THRESHOLD])), 6) + return motion_ns.orientation_degrees(data, threshold) if sensor_type == CONF_ROLL: ay = data.acceleration[1] az = data.acceleration[2] @@ -115,14 +152,14 @@ def build_sensor_expr(sensor_type: str, data: MockObj) -> MockObj: return getattr(data, str(sensor_type[:-2]))[sensor_offset] -async def to_code(config): +async def to_code(config: ConfigType) -> None: sensor_type = config[CONF_TYPE] var = await sensor.new_sensor(config) parent = await cg.get_variable(config[CONF_MOTION_ID]) data = MockObj("data") - expr = build_sensor_expr(sensor_type, data) + expr = build_sensor_expr(sensor_type, data, config) value_lambda = await cg.process_lambda( var.publish_state(expr), - [(MotionData.operator("ref"), str(data))], + [(MotionData.operator("const").operator("ref"), str(data))], ) cg.add(parent.add_listener(value_lambda)) diff --git a/esphome/components/mpl3115a2/__init__.py b/esphome/components/mpl3115a2/__init__.py index e69de29bb2d..08956d32f69 100644 --- a/esphome/components/mpl3115a2/__init__.py +++ b/esphome/components/mpl3115a2/__init__.py @@ -0,0 +1 @@ +DOMAIN = "mpl3115a2" diff --git a/esphome/components/mpl3115a2/mpl3115a2.cpp b/esphome/components/mpl3115a2/mpl3115a2.cpp index 238e37aff02..db892716258 100644 --- a/esphome/components/mpl3115a2/mpl3115a2.cpp +++ b/esphome/components/mpl3115a2/mpl3115a2.cpp @@ -5,7 +5,7 @@ namespace esphome::mpl3115a2 { -static const char *const TAG = "mpl3115a2"; +ESPHOME_LOG_TAG(TAG, "mpl3115a2"); void MPL3115A2Component::setup() { uint8_t whoami = 0xFF; diff --git a/esphome/components/mpr121/__init__.py b/esphome/components/mpr121/__init__.py index da56b4ff4bc..1764ff9c054 100644 --- a/esphome/components/mpr121/__init__.py +++ b/esphome/components/mpr121/__init__.py @@ -25,6 +25,7 @@ CONF_MPR121 = "mpr121" CONF_MPR121_ID = "mpr121_id" DEPENDENCIES = ["i2c"] +DOMAIN = "mpr121" mpr121_ns = cg.esphome_ns.namespace("mpr121") MPR121Component = mpr121_ns.class_("MPR121Component", cg.Component, i2c.I2CDevice) diff --git a/esphome/components/mpr121/mpr121.cpp b/esphome/components/mpr121/mpr121.cpp index 6b183233d1e..26cdcae6138 100644 --- a/esphome/components/mpr121/mpr121.cpp +++ b/esphome/components/mpr121/mpr121.cpp @@ -7,7 +7,7 @@ namespace esphome::mpr121 { -static const char *const TAG = "mpr121"; +ESPHOME_LOG_TAG(TAG, "mpr121"); void MPR121Component::setup() { this->disable_loop(); diff --git a/esphome/components/mpu6050/__init__.py b/esphome/components/mpu6050/__init__.py index e69de29bb2d..ee025604782 100644 --- a/esphome/components/mpu6050/__init__.py +++ b/esphome/components/mpu6050/__init__.py @@ -0,0 +1 @@ +DOMAIN = "mpu6050" diff --git a/esphome/components/mpu6050/mpu6050.cpp b/esphome/components/mpu6050/mpu6050.cpp index 8784e8caf81..cf393fd4d10 100644 --- a/esphome/components/mpu6050/mpu6050.cpp +++ b/esphome/components/mpu6050/mpu6050.cpp @@ -3,7 +3,7 @@ namespace esphome::mpu6050 { -static const char *const TAG = "mpu6050"; +ESPHOME_LOG_TAG(TAG, "mpu6050"); const uint8_t MPU6050_REGISTER_WHO_AM_I = 0x75; const uint8_t MPU6050_REGISTER_POWER_MANAGEMENT_1 = 0x6B; diff --git a/esphome/components/mpu6886/__init__.py b/esphome/components/mpu6886/__init__.py index 933f71ccd77..af9cdd2f272 100644 --- a/esphome/components/mpu6886/__init__.py +++ b/esphome/components/mpu6886/__init__.py @@ -1 +1,3 @@ """Support for MPC-6886.""" + +DOMAIN = "mpu6886" diff --git a/esphome/components/mpu6886/mpu6886.cpp b/esphome/components/mpu6886/mpu6886.cpp index b8cbd4635ac..514ff0dd0e7 100644 --- a/esphome/components/mpu6886/mpu6886.cpp +++ b/esphome/components/mpu6886/mpu6886.cpp @@ -3,7 +3,7 @@ namespace esphome::mpu6886 { -static const char *const TAG = "mpu6886"; +ESPHOME_LOG_TAG(TAG, "mpu6886"); const uint8_t MPU6886_REGISTER_WHO_AM_I = 0x75; const uint8_t MPU6886_REGISTER_POWER_MANAGEMENT_1 = 0x6B; diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 9418614346f..b21dc2e2f00 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -5,6 +5,7 @@ from esphome.components.esp32 import ( add_idf_component, idf_version, include_builtin_idf_component, + request_tls, ) from esphome.config_helpers import ( filter_source_files_from_defines, @@ -67,6 +68,7 @@ from esphome.core import CORE, CoroPriority, coroutine_with_priority from esphome.types import ConfigType DEPENDENCIES = ["network"] +DOMAIN = "mqtt" def AUTO_LOAD(): @@ -358,8 +360,8 @@ async def to_code(config): add_idf_component(name="espressif/mqtt", ref="1.0.0") else: include_builtin_idf_component("mqtt") - # mqtt_client.h drags in esp_tls types; esp-tls is excluded by default - include_builtin_idf_component("esp-tls") + # esp-mqtt links transport_ssl.c (esp_tls) even for plain MQTT + request_tls() cg.add_define("USE_MQTT") cg.add_global(mqtt_ns.using) diff --git a/esphome/components/mqtt/custom_mqtt_device.cpp b/esphome/components/mqtt/custom_mqtt_device.cpp index 64521f5cf3c..42171fcfb14 100644 --- a/esphome/components/mqtt/custom_mqtt_device.cpp +++ b/esphome/components/mqtt/custom_mqtt_device.cpp @@ -6,7 +6,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.custom"; +ESPHOME_LOG_TAG(TAG, "mqtt.custom"); bool CustomMQTTDevice::publish(const std::string &topic, const std::string &payload, uint8_t qos, bool retain) { return global_mqtt_client->publish(topic, payload, qos, retain); diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp index f059360e231..225f8396339 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp @@ -9,7 +9,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.alarm_control_panel"; +ESPHOME_LOG_TAG(TAG, "mqtt.alarm_control_panel"); using namespace esphome::alarm_control_panel; diff --git a/esphome/components/mqtt/mqtt_backend_esp32.cpp b/esphome/components/mqtt/mqtt_backend_esp32.cpp index 791e5504965..3b49ca33cdc 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.cpp +++ b/esphome/components/mqtt/mqtt_backend_esp32.cpp @@ -10,7 +10,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt"; +ESPHOME_LOG_TAG(TAG, "mqtt"); bool MQTTBackendESP32::initialize_() { mqtt_cfg_.broker.address.hostname = this->host_.c_str(); diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index ebb29db44f0..7a75cca6e88 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -8,7 +8,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "mqtt.binary_sensor"); MQTT_COMPONENT_TYPE(MQTTBinarySensorComponent, "binary_sensor") const EntityBase *MQTTBinarySensorComponent::get_entity() const { return this->binary_sensor_; } diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index 7e0ae7d06e1..a04121e2a13 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -8,7 +8,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.button"; +ESPHOME_LOG_TAG(TAG, "mqtt.button"); using namespace esphome::button; @@ -20,7 +20,7 @@ void MQTTButtonComponent::setup() { this->button_->press(); } else { ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str()); - this->status_momentary_warning("state", 5000); + this->status_momentary_warning(5000); } }); } diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 2ecab47904a..6e5fad3efc7 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -26,7 +26,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt"; +ESPHOME_LOG_TAG(TAG, "mqtt"); // Maximum number of MQTT component resends per loop iteration. // Limits work to avoid triggering the task watchdog on reconnect. @@ -439,7 +439,7 @@ bool MQTTClientComponent::subscribe_(const char *topic, uint8_t qos) { } else { delay(5); ESP_LOGV(TAG, "Subscribe failed for topic='%s'. Will retry", topic); - this->status_momentary_warning("subscribe", 1000); + this->status_momentary_warning(1000); } return ret != 0; } @@ -499,7 +499,7 @@ void MQTTClientComponent::unsubscribe(const std::string &topic) { } else { delay(5); ESP_LOGV(TAG, "Unsubscribe failed for topic='%s'.", topic.c_str()); - this->status_momentary_warning("unsubscribe", 1000); + this->status_momentary_warning(1000); } auto it = subscriptions_.begin(); @@ -553,7 +553,7 @@ bool MQTTClientComponent::publish(const char *topic, const char *payload, size_t ESP_LOGVV(TAG, "Publish payload (len=%u): '%.*s'", payload_length, static_cast(payload_length), payload); } else { ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). Will retry", topic, payload_length); - this->status_momentary_warning("publish", 1000); + this->status_momentary_warning(1000); } } return ret != 0; diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 0e6a374f9b2..83efc3da9ce 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -9,7 +9,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.climate"; +ESPHOME_LOG_TAG(TAG, "mqtt.climate"); using namespace esphome::climate; diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 59a5d02d977..adb5a958287 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -12,7 +12,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.component"; +ESPHOME_LOG_TAG(TAG, "mqtt.component"); // Entity category MQTT strings indexed by EntityCategory enum: NONE(0) is skipped, CONFIG(1), DIAGNOSTIC(2) PROGMEM_STRING_TABLE(EntityCategoryMqttStrings, "", "config", "diagnostic"); diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index b4ae6244048..141b5f12b38 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -273,6 +273,8 @@ class MQTTComponent : public Component { void subscribe_json(const std::string &topic, const mqtt_json_callback_t &callback, uint8_t qos = 0); protected: + static constexpr uint32_t SEND_DEFER_ID = 0; + /// Helper method to get the discovery topic for this component into a buffer. StringRef get_discovery_topic_to_(std::span buf, const MQTTDiscoveryInfo &discovery_info) const; diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index ddb4b2d69d2..a7da9a1b228 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -9,7 +9,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.cover"; +ESPHOME_LOG_TAG(TAG, "mqtt.cover"); using namespace esphome::cover; diff --git a/esphome/components/mqtt/mqtt_date.cpp b/esphome/components/mqtt/mqtt_date.cpp index c422bb30586..b5f1cf8fb8b 100644 --- a/esphome/components/mqtt/mqtt_date.cpp +++ b/esphome/components/mqtt/mqtt_date.cpp @@ -10,7 +10,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.datetime"; +ESPHOME_LOG_TAG(TAG, "mqtt.datetime"); using namespace esphome::datetime; diff --git a/esphome/components/mqtt/mqtt_datetime.cpp b/esphome/components/mqtt/mqtt_datetime.cpp index 1492abd011a..82755809387 100644 --- a/esphome/components/mqtt/mqtt_datetime.cpp +++ b/esphome/components/mqtt/mqtt_datetime.cpp @@ -10,7 +10,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.datetime.datetime"; +ESPHOME_LOG_TAG(TAG, "mqtt.datetime.datetime"); using namespace esphome::datetime; diff --git a/esphome/components/mqtt/mqtt_event.cpp b/esphome/components/mqtt/mqtt_event.cpp index 93ff6971b36..4266722ca82 100644 --- a/esphome/components/mqtt/mqtt_event.cpp +++ b/esphome/components/mqtt/mqtt_event.cpp @@ -8,7 +8,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.event"; +ESPHOME_LOG_TAG(TAG, "mqtt.event"); using namespace esphome::event; diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 3a8658a55fe..7aa2f8e9d5a 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -9,7 +9,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.fan"; +ESPHOME_LOG_TAG(TAG, "mqtt.fan"); using namespace esphome::fan; @@ -46,7 +46,7 @@ void MQTTFanComponent::setup() { case PARSE_NONE: default: ESP_LOGW(TAG, "Unknown state payload %s", payload.c_str()); - this->status_momentary_warning("state", 5000); + this->status_momentary_warning(5000); break; } }); @@ -71,7 +71,7 @@ void MQTTFanComponent::setup() { break; case PARSE_NONE: ESP_LOGW(TAG, "Unknown direction Payload %s", payload.c_str()); - this->status_momentary_warning("direction", 5000); + this->status_momentary_warning(5000); break; } }); @@ -95,7 +95,7 @@ void MQTTFanComponent::setup() { break; case PARSE_NONE: ESP_LOGW(TAG, "Unknown Oscillation Payload %s", payload.c_str()); - this->status_momentary_warning("oscillation", 5000); + this->status_momentary_warning(5000); break; } }); @@ -112,16 +112,16 @@ void MQTTFanComponent::setup() { this->state_->make_call().set_speed(speed_level).perform(); } else { ESP_LOGW(TAG, "Invalid speed level %d", speed_level); - this->status_momentary_warning("speed", 5000); + this->status_momentary_warning(5000); } } else { ESP_LOGW(TAG, "Invalid speed level %s (int expected)", payload.c_str()); - this->status_momentary_warning("speed", 5000); + this->status_momentary_warning(5000); } }); } - this->state_->add_on_state_callback([this]() { this->defer("send", [this]() { this->publish_state(); }); }); + this->state_->add_on_state_callback([this]() { this->defer(SEND_DEFER_ID, [this]() { this->publish_state(); }); }); } void MQTTFanComponent::dump_config() { diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index a8b52a3839f..e1affae8a24 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -10,7 +10,7 @@ #include "esphome/components/light/light_json_schema.h" namespace esphome::mqtt { -static const char *const TAG = "mqtt.light"; +ESPHOME_LOG_TAG(TAG, "mqtt.light"); using namespace esphome::light; @@ -28,7 +28,7 @@ void MQTTJSONLightComponent::setup() { } void MQTTJSONLightComponent::on_light_remote_values_update() { - this->defer("send", [this]() { this->publish_state_(); }); + this->defer(SEND_DEFER_ID, [this]() { this->publish_state_(); }); } MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : state_(state) {} diff --git a/esphome/components/mqtt/mqtt_lock.cpp b/esphome/components/mqtt/mqtt_lock.cpp index 369c1c6668b..8e9a6289852 100644 --- a/esphome/components/mqtt/mqtt_lock.cpp +++ b/esphome/components/mqtt/mqtt_lock.cpp @@ -9,7 +9,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.lock"; +ESPHOME_LOG_TAG(TAG, "mqtt.lock"); using namespace esphome::lock; @@ -25,11 +25,11 @@ void MQTTLockComponent::setup() { this->lock_->open(); } else { ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str()); - this->status_momentary_warning("state", 5000); + this->status_momentary_warning(5000); } }); this->lock_->add_on_state_callback( - [this](LockState /*state*/) { this->defer("send", [this]() { this->publish_state(); }); }); + [this](LockState /*state*/) { this->defer(SEND_DEFER_ID, [this]() { this->publish_state(); }); }); } void MQTTLockComponent::dump_config() { ESP_LOGCONFIG(TAG, "MQTT Lock '%s': ", this->lock_->get_name().c_str()); diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index b0bac8b3d71..3b2bebf291d 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -9,7 +9,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.number"; +ESPHOME_LOG_TAG(TAG, "mqtt.number"); using namespace esphome::number; diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index 25fd813496c..42c0ae002e4 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -8,7 +8,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.select"; +ESPHOME_LOG_TAG(TAG, "mqtt.select"); using namespace esphome::select; diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index a601a9bcc89..bdaaa47e8f4 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -14,7 +14,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.sensor"; +ESPHOME_LOG_TAG(TAG, "mqtt.sensor"); using namespace esphome::sensor; diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index 70cd03a4eb5..44ce2497720 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -8,7 +8,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.switch"; +ESPHOME_LOG_TAG(TAG, "mqtt.switch"); using namespace esphome::switch_; @@ -29,12 +29,12 @@ void MQTTSwitchComponent::setup() { case PARSE_NONE: default: ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str()); - this->status_momentary_warning("state", 5000); + this->status_momentary_warning(5000); break; } }); this->switch_->add_on_state_callback( - [this](bool enabled) { this->defer("send", [this, enabled]() { this->publish_state(enabled); }); }); + [this](bool enabled) { this->defer(SEND_DEFER_ID, [this, enabled]() { this->publish_state(enabled); }); }); } void MQTTSwitchComponent::dump_config() { ESP_LOGCONFIG(TAG, "MQTT Switch '%s': ", this->switch_->get_name().c_str()); diff --git a/esphome/components/mqtt/mqtt_text.cpp b/esphome/components/mqtt/mqtt_text.cpp index 200e420c9c4..dd43a8e2cb3 100644 --- a/esphome/components/mqtt/mqtt_text.cpp +++ b/esphome/components/mqtt/mqtt_text.cpp @@ -9,7 +9,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.text"; +ESPHOME_LOG_TAG(TAG, "mqtt.text"); using namespace esphome::text; diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index 3acd71b50d9..d105aaac992 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -8,7 +8,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.text_sensor"; +ESPHOME_LOG_TAG(TAG, "mqtt.text_sensor"); using namespace esphome::text_sensor; diff --git a/esphome/components/mqtt/mqtt_time.cpp b/esphome/components/mqtt/mqtt_time.cpp index be391ce88c5..889ee2bf349 100644 --- a/esphome/components/mqtt/mqtt_time.cpp +++ b/esphome/components/mqtt/mqtt_time.cpp @@ -10,7 +10,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.datetime.time"; +ESPHOME_LOG_TAG(TAG, "mqtt.datetime.time"); using namespace esphome::datetime; diff --git a/esphome/components/mqtt/mqtt_update.cpp b/esphome/components/mqtt/mqtt_update.cpp index c01fb9e52e0..69b2f64771b 100644 --- a/esphome/components/mqtt/mqtt_update.cpp +++ b/esphome/components/mqtt/mqtt_update.cpp @@ -8,7 +8,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.update"; +ESPHOME_LOG_TAG(TAG, "mqtt.update"); using namespace esphome::update; @@ -20,11 +20,11 @@ void MQTTUpdateComponent::setup() { this->update_->perform(); } else { ESP_LOGW(TAG, "'%s': Received unknown update payload: %s", this->friendly_name_().c_str(), payload.c_str()); - this->status_momentary_warning("state", 5000); + this->status_momentary_warning(5000); } }); - this->update_->add_on_state_callback([this]() { this->defer("send", [this]() { this->publish_state(); }); }); + this->update_->add_on_state_callback([this]() { this->defer(SEND_DEFER_ID, [this]() { this->publish_state(); }); }); } bool MQTTUpdateComponent::publish_state() { diff --git a/esphome/components/mqtt/mqtt_valve.cpp b/esphome/components/mqtt/mqtt_valve.cpp index b155a4c8972..b41a482de94 100644 --- a/esphome/components/mqtt/mqtt_valve.cpp +++ b/esphome/components/mqtt/mqtt_valve.cpp @@ -9,7 +9,7 @@ namespace esphome::mqtt { -static const char *const TAG = "mqtt.valve"; +ESPHOME_LOG_TAG(TAG, "mqtt.valve"); using namespace esphome::valve; diff --git a/esphome/components/mqtt_subscribe/__init__.py b/esphome/components/mqtt_subscribe/__init__.py index b17f3213727..ee58f82fb66 100644 --- a/esphome/components/mqtt_subscribe/__init__.py +++ b/esphome/components/mqtt_subscribe/__init__.py @@ -1,3 +1,5 @@ import esphome.codegen as cg +DOMAIN = "mqtt_subscribe" + mqtt_subscribe_ns = cg.esphome_ns.namespace("mqtt_subscribe") diff --git a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp index afb725feb64..a79216e04de 100644 --- a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp +++ b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp @@ -6,7 +6,7 @@ namespace esphome::mqtt_subscribe { -static const char *const TAG = "mqtt_subscribe.sensor"; +ESPHOME_LOG_TAG(TAG, "mqtt_subscribe.sensor"); void MQTTSubscribeSensor::setup() { mqtt::global_mqtt_client->subscribe( diff --git a/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.cpp b/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.cpp index 470e08d59ae..55992f42f99 100644 --- a/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.cpp +++ b/esphome/components/mqtt_subscribe/text_sensor/mqtt_subscribe_text_sensor.cpp @@ -7,7 +7,7 @@ namespace esphome::mqtt_subscribe { -static const char *const TAG = "mqtt_subscribe.text_sensor"; +ESPHOME_LOG_TAG(TAG, "mqtt_subscribe.text_sensor"); void MQTTSubscribeTextSensor::setup() { this->parent_->subscribe( diff --git a/esphome/components/ms5611/__init__.py b/esphome/components/ms5611/__init__.py index e69de29bb2d..925c57d57bf 100644 --- a/esphome/components/ms5611/__init__.py +++ b/esphome/components/ms5611/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ms5611" diff --git a/esphome/components/ms5611/ms5611.cpp b/esphome/components/ms5611/ms5611.cpp index 9b7dcbe653e..8f66d704fe6 100644 --- a/esphome/components/ms5611/ms5611.cpp +++ b/esphome/components/ms5611/ms5611.cpp @@ -4,7 +4,10 @@ namespace esphome::ms5611 { -static const char *const TAG = "ms5611"; +ESPHOME_LOG_TAG(TAG, "ms5611"); + +static constexpr uint32_t TEMPERATURE_TIMEOUT_ID = 0; +static constexpr uint32_t PRESSURE_TIMEOUT_ID = 1; static const uint8_t MS5611_ADDRESS = 0x77; static const uint8_t MS5611_CMD_ADC_READ = 0x00; @@ -44,7 +47,7 @@ void MS5611Component::update() { return; } - this->set_timeout("temperature", 10, [this]() { this->read_temperature_(); }); + this->set_timeout(TEMPERATURE_TIMEOUT_ID, 10, [this]() { this->read_temperature_(); }); } void MS5611Component::read_temperature_() { uint8_t bytes[3]; @@ -60,7 +63,7 @@ void MS5611Component::read_temperature_() { return; } - this->set_timeout("pressure", 10, [this, raw_temperature]() { this->read_pressure_(raw_temperature); }); + this->set_timeout(PRESSURE_TIMEOUT_ID, 10, [this, raw_temperature]() { this->read_pressure_(raw_temperature); }); } void MS5611Component::read_pressure_(uint32_t raw_temperature) { uint8_t bytes[3]; diff --git a/esphome/components/ms8607/__init__.py b/esphome/components/ms8607/__init__.py index e1cd49ec7b6..9fc73f80a97 100644 --- a/esphome/components/ms8607/__init__.py +++ b/esphome/components/ms8607/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@e28eta"] +DOMAIN = "ms8607" diff --git a/esphome/components/ms8607/ms8607.cpp b/esphome/components/ms8607/ms8607.cpp index b9cbdc749b4..2e20c4e0ea5 100644 --- a/esphome/components/ms8607/ms8607.cpp +++ b/esphome/components/ms8607/ms8607.cpp @@ -7,7 +7,13 @@ namespace esphome::ms8607 { /// TAG used for logging calls -static const char *const TAG = "ms8607"; +ESPHOME_LOG_TAG(TAG, "ms8607"); + +static constexpr uint32_t RESET_TIMEOUT_ID = 0; +static constexpr uint32_t PROM_READ_TIMEOUT_ID = 1; +static constexpr uint32_t TEMPERATURE_TIMEOUT_ID = 2; +static constexpr uint32_t PRESSURE_TIMEOUT_ID = 3; +static constexpr uint32_t HUMIDITY_TIMEOUT_ID = 4; /// Reset the Pressure/Temperature sensor static const uint8_t MS8607_PT_CMD_RESET = 0x1E; @@ -97,7 +103,7 @@ void MS8607Component::try_reset_() { if (--this->reset_attempts_remaining_ > 0) { uint32_t delay = this->reset_interval_; this->reset_interval_ *= 5; - this->set_timeout("reset", delay, [this]() { this->try_reset_(); }); + this->set_timeout(RESET_TIMEOUT_ID, delay, [this]() { this->try_reset_(); }); this->status_set_error(); } else { this->mark_failed(); @@ -110,7 +116,7 @@ void MS8607Component::try_reset_() { this->status_clear_error(); // 15ms delay matches datasheet, Adafruit_MS8607 & SparkFun_PHT_MS8607_Arduino_Library - this->set_timeout("prom-read", 15, [this]() { + this->set_timeout(PROM_READ_TIMEOUT_ID, 15, [this]() { if (this->read_calibration_values_from_prom_()) { this->setup_status_ = SetupStatus::SUCCESSFUL; this->status_clear_error(); @@ -251,7 +257,7 @@ void MS8607Component::request_read_temperature_() { } // datasheet says 17.2ms max conversion time at OSR 8192 - this->set_timeout("temperature", 20, [this]() { this->read_temperature_(); }); + this->set_timeout(TEMPERATURE_TIMEOUT_ID, 20, [this]() { this->read_temperature_(); }); } void MS8607Component::read_temperature_() { @@ -272,7 +278,8 @@ void MS8607Component::request_read_pressure_(uint32_t d2_raw_temperature) { } // datasheet says 17.2ms max conversion time at OSR 8192 - this->set_timeout("pressure", 20, [this, d2_raw_temperature]() { this->read_pressure_(d2_raw_temperature); }); + this->set_timeout(PRESSURE_TIMEOUT_ID, 20, + [this, d2_raw_temperature]() { this->read_pressure_(d2_raw_temperature); }); } void MS8607Component::read_pressure_(uint32_t d2_raw_temperature) { @@ -293,7 +300,7 @@ void MS8607Component::request_read_humidity_(float temperature_float) { } // datasheet says 15.89ms max conversion time at OSR 8192 - this->set_timeout("humidity", 20, [this, temperature_float]() { this->read_humidity_(temperature_float); }); + this->set_timeout(HUMIDITY_TIMEOUT_ID, 20, [this, temperature_float]() { this->read_humidity_(temperature_float); }); } void MS8607Component::read_humidity_(float temperature_float) { diff --git a/esphome/components/msa3xx/__init__.py b/esphome/components/msa3xx/__init__.py index 0beece67101..75260a1b0af 100644 --- a/esphome/components/msa3xx/__init__.py +++ b/esphome/components/msa3xx/__init__.py @@ -20,6 +20,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@latonita"] DEPENDENCIES = ["i2c"] +DOMAIN = "msa3xx" MULTI_CONF = True diff --git a/esphome/components/msa3xx/msa3xx.cpp b/esphome/components/msa3xx/msa3xx.cpp index ecde0cb1172..78f2b75c9ce 100644 --- a/esphome/components/msa3xx/msa3xx.cpp +++ b/esphome/components/msa3xx/msa3xx.cpp @@ -5,7 +5,7 @@ namespace esphome::msa3xx { -static const char *const TAG = "msa3xx"; +ESPHOME_LOG_TAG(TAG, "msa3xx"); const uint8_t MSA_3XX_PART_ID = 0x13; diff --git a/esphome/components/my9231/__init__.py b/esphome/components/my9231/__init__.py index e43454d6ed9..6fcb09a3478 100644 --- a/esphome/components/my9231/__init__.py +++ b/esphome/components/my9231/__init__.py @@ -12,6 +12,7 @@ from esphome.const import ( from esphome.types import ConfigType AUTO_LOAD = ["output"] +DOMAIN = "my9231" my9231_ns = cg.esphome_ns.namespace("my9231") MY9231OutputComponent = my9231_ns.class_("MY9231OutputComponent", cg.Component) diff --git a/esphome/components/my9231/my9231.cpp b/esphome/components/my9231/my9231.cpp index 0072f7196e7..679600eebce 100644 --- a/esphome/components/my9231/my9231.cpp +++ b/esphome/components/my9231/my9231.cpp @@ -4,7 +4,7 @@ namespace esphome::my9231 { -static const char *const TAG = "my9231.output"; +ESPHOME_LOG_TAG(TAG, "my9231.output"); // One-shot select (frame cycle repeat mode / frame cycle One-shot mode) static const uint8_t MY9231_CMD_ONE_SHOT_DISABLE = 0x0 << 6; diff --git a/esphome/components/nau7802/__init__.py b/esphome/components/nau7802/__init__.py index e69de29bb2d..d86eed44b84 100644 --- a/esphome/components/nau7802/__init__.py +++ b/esphome/components/nau7802/__init__.py @@ -0,0 +1 @@ +DOMAIN = "nau7802" diff --git a/esphome/components/nau7802/nau7802.cpp b/esphome/components/nau7802/nau7802.cpp index 20924520874..ad1a569e0fa 100644 --- a/esphome/components/nau7802/nau7802.cpp +++ b/esphome/components/nau7802/nau7802.cpp @@ -5,7 +5,7 @@ namespace esphome::nau7802 { -static const char *const TAG = "nau7802"; +ESPHOME_LOG_TAG(TAG, "nau7802"); // Only define what we need diff --git a/esphome/components/neopixelbus/__init__.py b/esphome/components/neopixelbus/__init__.py index e69de29bb2d..0294a2a7357 100644 --- a/esphome/components/neopixelbus/__init__.py +++ b/esphome/components/neopixelbus/__init__.py @@ -0,0 +1 @@ +DOMAIN = "neopixelbus" diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index decee27bcf8..1c08202d411 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -21,6 +21,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] AUTO_LOAD = ["mdns"] +DOMAIN = "network" _LOGGER = logging.getLogger(__name__) @@ -277,7 +278,7 @@ def get_priority_interfaces_from_full_config(full_config: ConfigType) -> set[str """ return { entry["interface"] - for entry in full_config.get("network", {}).get(CONF_PRIORITY, []) + for entry in full_config.get(DOMAIN, {}).get(CONF_PRIORITY, []) } diff --git a/esphome/components/network/network_component.cpp b/esphome/components/network/network_component.cpp index 7e059837335..fad7a385fdd 100644 --- a/esphome/components/network/network_component.cpp +++ b/esphome/components/network/network_component.cpp @@ -23,7 +23,7 @@ namespace esphome::network { -static const char *const TAG = "network"; +ESPHOME_LOG_TAG(TAG, "network"); void NetworkComponent::setup() { // Initialize ESP-IDF network interfaces and ensure the default event loop exists diff --git a/esphome/components/nextion/__init__.py b/esphome/components/nextion/__init__.py index efb6c88d28e..c1ab86427e3 100644 --- a/esphome/components/nextion/__init__.py +++ b/esphome/components/nextion/__init__.py @@ -11,6 +11,7 @@ CONF_NEXTION_ID = "nextion_id" CONF_PUBLISH_STATE = "publish_state" CONF_SEND_TO_NEXTION = "send_to_nextion" +DOMAIN = "nextion" FILTER_SOURCE_FILES = filter_source_files_from_platform( { "nextion_upload_esp32.cpp": { diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp index 08e7c58ef10..75e05e20185 100644 --- a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp @@ -4,7 +4,7 @@ namespace esphome::nextion { -static const char *const TAG = "nextion_binarysensor"; +ESPHOME_LOG_TAG(TAG, "nextion_binarysensor"); void NextionBinarySensor::process_bool(const std::string &variable_name, bool state) { if (!this->nextion_->is_setup()) diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index 73a56e0a42b..3a70be21caf 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -1,5 +1,3 @@ -import logging - from esphome import automation import esphome.codegen as cg from esphome.components import display, esp32, uart @@ -43,8 +41,6 @@ from .base_component import ( CONF_WAKE_UP_PAGE, ) -_LOGGER = logging.getLogger(__name__) - CODEOWNERS = ["@senexcrenshaw", "@edwardtfn"] DEPENDENCIES = ["uart"] @@ -56,15 +52,6 @@ def AUTO_LOAD() -> list[str]: return base -def _deprecated_dump_device_info(value): - _LOGGER.warning( - "'dump_device_info' is deprecated and will be removed in ESPHome 2026.11.0. " - "Device info is now always logged at connection time. " - "Please remove this option from your configuration." - ) - return value - - def _validate_tft_upload(config): has_tft_url = CONF_TFT_URL in config for conf_key in ( @@ -91,9 +78,11 @@ CONFIG_SCHEMA = cv.All( cv.positive_time_period_milliseconds, cv.Range(max=TimePeriod(milliseconds=255)), ), - # Deprecated — device info is now always logged. Remove before 2026.11.0. - cv.Optional(CONF_DUMP_DEVICE_INFO): cv.All( - cv.boolean, _deprecated_dump_device_info + # Removed in 2026.11.0 - kept to provide helpful error message + # Remove before 2027.5.0 + cv.Optional(CONF_DUMP_DEVICE_INFO): cv.invalid( + "The 'dump_device_info' option has been removed in ESPHome 2026.11.0.\n" + "Device info is now always logged, so remove this option from your configuration." ), cv.Optional(CONF_EXIT_REPARSE_ON_START, default=False): cv.boolean, cv.Optional(CONF_MAX_QUEUE_AGE, default="8000ms"): cv.All( @@ -271,10 +260,7 @@ async def to_code(config): ) if CORE.is_esp32: - # Re-enable ESP-IDF's HTTP client (excluded by default to save compile time) - # and esp-tls, whose sdkconfig options below need the component present - esp32.include_builtin_idf_component("esp_http_client") - esp32.include_builtin_idf_component("esp-tls") + esp32.request_http_client() esp32.add_idf_sdkconfig_option("CONFIG_ESP_TLS_INSECURE", True) esp32.add_idf_sdkconfig_option( "CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", True diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 625c915e732..b9194b3f31c 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -11,7 +11,7 @@ namespace esphome::nextion { -static const char *const TAG = "nextion"; +ESPHOME_LOG_TAG(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) { diff --git a/esphome/components/nextion/nextion_commands.cpp b/esphome/components/nextion/nextion_commands.cpp index dd3a97065cb..cac1d605946 100644 --- a/esphome/components/nextion/nextion_commands.cpp +++ b/esphome/components/nextion/nextion_commands.cpp @@ -5,7 +5,7 @@ namespace esphome::nextion { -static const char *const TAG = "nextion"; +ESPHOME_LOG_TAG(TAG, "nextion"); // Sleep safe commands void Nextion::soft_reset() { this->send_command_("rest"); } diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index a49e7f18d6c..f106cdf18f9 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -6,7 +6,7 @@ namespace esphome::nextion { -static const char *const TAG = "nextion.upload"; +ESPHOME_LOG_TAG(TAG, "nextion.upload"); bool Nextion::upload_end_(bool successful) { if (successful) { diff --git a/esphome/components/nextion/nextion_upload_arduino.cpp b/esphome/components/nextion/nextion_upload_arduino.cpp index 944fa1db477..c755a0e9c50 100644 --- a/esphome/components/nextion/nextion_upload_arduino.cpp +++ b/esphome/components/nextion/nextion_upload_arduino.cpp @@ -13,7 +13,7 @@ namespace esphome::nextion { -static const char *const TAG = "nextion.upload"; +ESPHOME_LOG_TAG(TAG, "nextion.upload"); static constexpr size_t NEXTION_MAX_RESPONSE_LOG_BYTES = 16; // Timeout for display acknowledgment during TFT upload (ms). diff --git a/esphome/components/nextion/nextion_upload_esp32.cpp b/esphome/components/nextion/nextion_upload_esp32.cpp index c4dc74b5d3c..cabd0761c25 100644 --- a/esphome/components/nextion/nextion_upload_esp32.cpp +++ b/esphome/components/nextion/nextion_upload_esp32.cpp @@ -16,7 +16,7 @@ namespace esphome::nextion { -static const char *const TAG = "nextion.upload"; +ESPHOME_LOG_TAG(TAG, "nextion.upload"); static constexpr size_t NEXTION_MAX_RESPONSE_LOG_BYTES = 16; // Timeout for display acknowledgment during TFT upload (ms). diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp index ca657522f96..bb5206e8d8e 100644 --- a/esphome/components/nextion/sensor/nextion_sensor.cpp +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::nextion { -static const char *const TAG = "nextion_sensor"; +ESPHOME_LOG_TAG(TAG, "nextion_sensor"); void NextionSensor::process_sensor(const std::string &variable_name, int state) { if (!this->nextion_->is_setup()) diff --git a/esphome/components/nextion/switch/nextion_switch.cpp b/esphome/components/nextion/switch/nextion_switch.cpp index 0018cff0052..f363666dbfe 100644 --- a/esphome/components/nextion/switch/nextion_switch.cpp +++ b/esphome/components/nextion/switch/nextion_switch.cpp @@ -4,7 +4,7 @@ namespace esphome::nextion { -static const char *const TAG = "nextion_switch"; +ESPHOME_LOG_TAG(TAG, "nextion_switch"); void NextionSwitch::process_bool(const std::string &variable_name, bool on) { if (!this->nextion_->is_setup()) diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp index 45e46914236..b2ae4366847 100644 --- a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp @@ -4,7 +4,7 @@ namespace esphome::nextion { -static const char *const TAG = "nextion_textsensor"; +ESPHOME_LOG_TAG(TAG, "nextion_textsensor"); void NextionTextSensor::process_text(const std::string &variable_name, const std::string &text_value) { if (!this->nextion_->is_setup()) diff --git a/esphome/components/nfc/__init__.py b/esphome/components/nfc/__init__.py index 6afc2eca092..a837edead54 100644 --- a/esphome/components/nfc/__init__.py +++ b/esphome/components/nfc/__init__.py @@ -2,6 +2,7 @@ from esphome import automation import esphome.codegen as cg CODEOWNERS = ["@jesserockz", "@kbx81"] +DOMAIN = "nfc" nfc_ns = cg.esphome_ns.namespace("nfc") diff --git a/esphome/components/nfc/binary_sensor/nfc_binary_sensor.cpp b/esphome/components/nfc/binary_sensor/nfc_binary_sensor.cpp index 995255d1e45..3595f370165 100644 --- a/esphome/components/nfc/binary_sensor/nfc_binary_sensor.cpp +++ b/esphome/components/nfc/binary_sensor/nfc_binary_sensor.cpp @@ -5,7 +5,7 @@ namespace esphome::nfc { -static const char *const TAG = "nfc.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "nfc.binary_sensor"); void NfcTagBinarySensor::setup() { this->publish_initial_state(false); } diff --git a/esphome/components/nfc/ndef_message.cpp b/esphome/components/nfc/ndef_message.cpp index e597b9192d4..552dfe99d30 100644 --- a/esphome/components/nfc/ndef_message.cpp +++ b/esphome/components/nfc/ndef_message.cpp @@ -3,7 +3,7 @@ namespace esphome::nfc { -static const char *const TAG = "nfc.ndef_message"; +ESPHOME_LOG_TAG(TAG, "nfc.ndef_message"); NdefMessage::NdefMessage(const std::span data) { ESP_LOGV(TAG, "Building NdefMessage with %zu bytes", data.size()); diff --git a/esphome/components/nfc/ndef_record.cpp b/esphome/components/nfc/ndef_record.cpp index 8b9e7023ed6..0e8e7ff6efd 100644 --- a/esphome/components/nfc/ndef_record.cpp +++ b/esphome/components/nfc/ndef_record.cpp @@ -2,7 +2,7 @@ namespace esphome::nfc { -static const char *const TAG = "nfc.ndef_record"; +ESPHOME_LOG_TAG(TAG, "nfc.ndef_record"); NdefRecord::NdefRecord(std::vector payload_data) { this->payload_ = std::string(payload_data.begin(), payload_data.end()); diff --git a/esphome/components/nfc/ndef_record_text.cpp b/esphome/components/nfc/ndef_record_text.cpp index 8ad687daf8c..f1e81dcd96c 100644 --- a/esphome/components/nfc/ndef_record_text.cpp +++ b/esphome/components/nfc/ndef_record_text.cpp @@ -3,7 +3,7 @@ namespace esphome::nfc { -static const char *const TAG = "nfc.ndef_record_text"; +ESPHOME_LOG_TAG(TAG, "nfc.ndef_record_text"); NdefRecordText::NdefRecordText(const std::vector &payload) { if (payload.empty()) { diff --git a/esphome/components/nfc/ndef_record_uri.cpp b/esphome/components/nfc/ndef_record_uri.cpp index e2c6d21a34e..5d7ebbba48f 100644 --- a/esphome/components/nfc/ndef_record_uri.cpp +++ b/esphome/components/nfc/ndef_record_uri.cpp @@ -2,7 +2,7 @@ namespace esphome::nfc { -static const char *const TAG = "nfc.ndef_record_uri"; +ESPHOME_LOG_TAG(TAG, "nfc.ndef_record_uri"); NdefRecordUri::NdefRecordUri(const std::vector &payload) { if (payload.empty()) { diff --git a/esphome/components/nfc/nfc.cpp b/esphome/components/nfc/nfc.cpp index a9fc1c2b8c4..03703bb4216 100644 --- a/esphome/components/nfc/nfc.cpp +++ b/esphome/components/nfc/nfc.cpp @@ -5,7 +5,7 @@ namespace esphome::nfc { -static const char *const TAG = "nfc"; +ESPHOME_LOG_TAG(TAG, "nfc"); char *format_uid_to(char *buffer, std::span uid) { return format_hex_pretty_to(buffer, FORMAT_UID_BUFFER_SIZE, uid.data(), uid.size(), '-'); diff --git a/esphome/components/nfc/nfc_helpers.cpp b/esphome/components/nfc/nfc_helpers.cpp index 82f4591bdf4..412cc385c6b 100644 --- a/esphome/components/nfc/nfc_helpers.cpp +++ b/esphome/components/nfc/nfc_helpers.cpp @@ -2,7 +2,7 @@ namespace esphome::nfc { -static const char *const TAG = "nfc.helpers"; +ESPHOME_LOG_TAG(TAG, "nfc.helpers"); bool has_ha_tag_ndef(const NfcTag &tag) { return !get_ha_tag_ndef(tag).empty(); } diff --git a/esphome/components/nfc/nfc_tag.cpp b/esphome/components/nfc/nfc_tag.cpp index c43210517dc..0d2d2a20765 100644 --- a/esphome/components/nfc/nfc_tag.cpp +++ b/esphome/components/nfc/nfc_tag.cpp @@ -2,6 +2,6 @@ namespace esphome::nfc { -static const char *const TAG = "nfc.tag"; +ESPHOME_LOG_TAG(TAG, "nfc.tag"); } // namespace esphome::nfc diff --git a/esphome/components/noblex/__init__.py b/esphome/components/noblex/__init__.py index ceceff9df9f..a7c86b064bb 100644 --- a/esphome/components/noblex/__init__.py +++ b/esphome/components/noblex/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@AGalfra"] +DOMAIN = "noblex" diff --git a/esphome/components/noblex/noblex.cpp b/esphome/components/noblex/noblex.cpp index ac8d2641fea..a9fb3935f1f 100644 --- a/esphome/components/noblex/noblex.cpp +++ b/esphome/components/noblex/noblex.cpp @@ -4,7 +4,7 @@ namespace esphome::noblex { -static const char *const TAG = "noblex.climate"; +ESPHOME_LOG_TAG(TAG, "noblex.climate"); const uint16_t NOBLEX_HEADER_MARK = 9000; const uint16_t NOBLEX_HEADER_SPACE = 4500; diff --git a/esphome/components/noise/__init__.py b/esphome/components/noise/__init__.py index d9880fe6f9c..1769c110184 100644 --- a/esphome/components/noise/__init__.py +++ b/esphome/components/noise/__init__.py @@ -10,11 +10,12 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] +DOMAIN = "noise" # Keep in sync with platformio.ini and esphome/idf_component.yml. # LIBSODIUM_VERSION must match the version noise-c pins in its manifests. -NOISE_C_VERSION = "0.1.30" -LIBSODIUM_VERSION = "1.10021.11" +NOISE_C_VERSION = "0.1.31" +LIBSODIUM_VERSION = "1.10021.12" noise_ns = cg.esphome_ns.namespace("noise") diff --git a/esphome/components/noise/noise.cpp b/esphome/components/noise/noise.cpp index 118bfb234fd..5e70300f215 100644 --- a/esphome/components/noise/noise.cpp +++ b/esphome/components/noise/noise.cpp @@ -13,7 +13,7 @@ namespace esphome::noise { -static const char *const TAG = "noise"; +ESPHOME_LOG_TAG(TAG, "noise"); void NoiseContext::load_psk(psk_t &out) const { if (this->psk_ == nullptr) { diff --git a/esphome/components/noise/noise_handshake.cpp b/esphome/components/noise/noise_handshake.cpp index bd1afa198d3..e0b7ca7c372 100644 --- a/esphome/components/noise/noise_handshake.cpp +++ b/esphome/components/noise/noise_handshake.cpp @@ -6,7 +6,7 @@ namespace esphome::noise { -static const char *const TAG = "noise"; +ESPHOME_LOG_TAG(TAG, "noise"); // Log the failing noise-c call at the same verbosity the api helper used // before this class existed; callers only see one collapsed error code. diff --git a/esphome/components/npi19/__init__.py b/esphome/components/npi19/__init__.py index e69de29bb2d..190848e54d0 100644 --- a/esphome/components/npi19/__init__.py +++ b/esphome/components/npi19/__init__.py @@ -0,0 +1 @@ +DOMAIN = "npi19" diff --git a/esphome/components/npi19/npi19.cpp b/esphome/components/npi19/npi19.cpp index 207acc9d991..2f81bfe3bac 100644 --- a/esphome/components/npi19/npi19.cpp +++ b/esphome/components/npi19/npi19.cpp @@ -5,7 +5,7 @@ namespace esphome::npi19 { -static const char *const TAG = "npi19"; +ESPHOME_LOG_TAG(TAG, "npi19"); static const uint8_t READ_COMMAND = 0xAC; diff --git a/esphome/components/nrf52/__init__.py b/esphome/components/nrf52/__init__.py index 1e841fffcfc..7cd0c6cdc1b 100644 --- a/esphome/components/nrf52/__init__.py +++ b/esphome/components/nrf52/__init__.py @@ -12,6 +12,7 @@ from esphome.build_helpers import pch from esphome.build_helpers.ccache import resolve_ccache_path import esphome.codegen as cg from esphome.components.zephyr import ( + Section, add_extra_script, copy_files as zephyr_copy_files, zephyr_add_overlay, @@ -27,6 +28,7 @@ from esphome.components.zephyr.const import ( CONF_CDC_ACM, KEY_BOARD, KEY_BOOTLOADER, + KEY_SYSBUILD, KEY_ZEPHYR, CdcAcm, ) @@ -77,6 +79,7 @@ from .framework import ( get_build_paths, setup_platformio_python_env, toolchain_tool, + wanted_west_projects, ) # force import gpio to register pin schema @@ -84,6 +87,7 @@ from .gpio import nrf52_pin_to_code # noqa: F401 CODEOWNERS = ["@tomaszduda23"] AUTO_LOAD = ["zephyr", "preferences"] +DOMAIN = "nrf52" IS_TARGET_PLATFORM = True _LOGGER = logging.getLogger(__name__) @@ -123,11 +127,37 @@ def set_core_data(config: ConfigType) -> ConfigType: CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = KEY_ZEPHYR if config[KEY_BOOTLOADER] in BOOTLOADER_CONFIG: - zephyr_add_pm_static(BOOTLOADER_CONFIG[config[KEY_BOOTLOADER]]) + sections = BOOTLOADER_CONFIG[config[KEY_BOOTLOADER]] + if CORE.testing_mode: + sections = _testing_mode_sections(sections) + zephyr_add_pm_static(sections) return config +# In testing mode, fake a larger flash to allow linking grouped component +# tests. The nRF52840 has 1 MB and a mcumgr OTA config halves the app slot, +# which an openthread config alone fills; CI images are never flashed. +NRF52840_FLASH_SIZE = 0x100000 +TESTING_FLASH_SIZE = 0x400000 + + +def _testing_mode_sections(sections: list[Section]) -> list[Section]: + """Move a bootloader pinned to the end of the real flash to the end of the + faked one, so the partition manager still sees a single gap for the app.""" + return [ + Section( + section.name, + section.address + TESTING_FLASH_SIZE - NRF52840_FLASH_SIZE, + section.size, + section.region, + ) + if section.end_address == NRF52840_FLASH_SIZE + else section + for section in sections + ] + + _TOOLCHAINS = (Toolchain.PLATFORMIO, Toolchain.SDK_NRF) _resolve_toolchain = cv.resolve_toolchain("nRF52", _TOOLCHAINS, Toolchain.SDK_NRF) @@ -378,6 +408,15 @@ async def to_code(config: ConfigType) -> None: zephyr_setup_preferences() zephyr_to_code(config) + if CORE.testing_mode: + zephyr_add_overlay( + f""" + &flash0 {{ + reg = <0x0 {TESTING_FLASH_SIZE:#x}>; + }}; + """ + ) + if dfu_config := config.get(CONF_DFU): CORE.add_job(_dfu_to_code, dfu_config) framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] @@ -822,19 +861,22 @@ _PCH_SUM_PATH = "CMakeFiles/app.dir/cmake_pch.hxx.gch.sum" def _write_pch_checksum(build_dir: Path, source_dir: Path) -> None: - """Write the checksum ccache reads in place of the .gch. The app binary - dir only exists after the first configure; sysbuild nests it.""" - app_dir = build_dir / "zephyr" - if not (app_dir / "CMakeCache.txt").is_file(): - app_dir = build_dir - if not (app_dir / "CMakeCache.txt").is_file(): - return + """Write the checksum ccache reads in place of the .gch; before the + first build too, or its compiles hash the path laden .gch instead. + The app image dir follows the SDK version, like get_elf_path; + 2.9.2+ always wraps the build in sysbuild.""" + app_dir = build_dir + if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(2, 9, 2): + app_dir = build_dir / "zephyr" checksum = pch.pch_checksum( CORE.relative_src_path(), pch.PCH_DEFAULT_HEADERS, ( str(CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]), zephyr_data()[KEY_BOARD], + # Kconfig inputs that reach autoconf.h without a .conf line + ",".join(sorted(wanted_west_projects())), + str(zephyr_data().get(KEY_SYSBUILD)), # What the Zephyr configuration is generated from *( path.read_text(encoding="utf-8") diff --git a/esphome/components/nrf52/dfu.cpp b/esphome/components/nrf52/dfu.cpp index 3d9219e96f8..a1d574a0340 100644 --- a/esphome/components/nrf52/dfu.cpp +++ b/esphome/components/nrf52/dfu.cpp @@ -10,7 +10,7 @@ namespace esphome::nrf52 { -static const char *const TAG = "dfu"; +ESPHOME_LOG_TAG(TAG, "dfu"); static const uint32_t DFU_DBL_RESET_MAGIC = 0x5A1AD5; // SALADS static const uint8_t DFU_MAGIC_UF2_RESET = 0x57; // Adafruit nRF52 bootloader UF2 magic diff --git a/esphome/components/nrf52/framework.py b/esphome/components/nrf52/framework.py index e6ccde6a2d0..00796562f42 100644 --- a/esphome/components/nrf52/framework.py +++ b/esphome/components/nrf52/framework.py @@ -225,6 +225,19 @@ def get_build_env(ccache: str | None) -> dict: env.setdefault("CCACHE_DISABLE", "1") else: env.update(ccache_env(ccache, SDK_NRF_TOOLS_CACHE)) + # Drop only the per build map entry (posix, CMake's spelling); + # its from side covers no compiled sources. A spaced path cannot + # survive ccache's space split list, so it stays hashed. + source_dir = CORE.relative_build_path("zephyr").as_posix() + if any(ch.isspace() for ch in source_dir): + _LOGGER.debug( + "Whitespace in %s; the per build map stays hashed", source_dir + ) + else: + device_map = f"-fmacro-prefix-map={source_dir}=CMAKE_SOURCE_DIR" + env["CCACHE_IGNOREOPTIONS"] = ( + f"{env.get('CCACHE_IGNOREOPTIONS', '')} {device_map}".strip() + ) return env @@ -301,21 +314,62 @@ def setup_platformio_python_env() -> None: _prepend_env_path("PATH", str(env_python_path.parent)) +def _patch_framework_file(path: Path, old: str, new: str) -> bool: + """Replace ``old`` with ``new`` in a framework script, atomically and + keeping the file mode (helpers.write_file would flatten it to 0o644). + Returns False when nothing matched.""" + import tempfile + + content = path.read_text(encoding="utf-8") + patched = content.replace(old, new) + if patched == content: + return False + # Unique sibling tmp: the install lock is best effort, so two builds + # may patch at once and a shared tmp name could rename a half + # written file into place. + fd, tmp_name = tempfile.mkstemp(dir=path.parent, suffix=".tmp") + tmp = Path(tmp_name) + try: + with os.fdopen(fd, "w", encoding="utf-8") as f: + f.write(patched) + shutil.copymode(path, tmp) + tmp.replace(path) + except BaseException: + tmp.unlink(missing_ok=True) + raise + return True + + def _patch_uf2conv_escape_sequences(framework_path: Path) -> None: # SDK v2.6.1 ships uf2conv.py with '\s+' — an unrecognised escape that # Python 3.12+ flags with SyntaxWarning (a future version will reject it). uf2conv = framework_path / "zephyr" / "scripts" / "build" / "uf2conv.py" - if not uf2conv.exists(): + if uf2conv.exists(): + _patch_framework_file( + uf2conv, "re.split('\\s+', line)", "re.split('\\\\s+', line)" + ) + + +def _patch_gen_defines_dts_path(framework_path: Path) -> None: + # The absolute zephyr.dts.pre path in the header's top comment is + # its only per device byte and blocks ccache sharing; emit the + # basename. Upstream candidate. + gen_defines = framework_path / "zephyr" / "scripts" / "dts" / "gen_defines.py" + if not gen_defines.exists(): return - content = uf2conv.read_text(encoding="utf-8") - patched = content.replace("re.split('\\s+', line)", "re.split('\\\\s+', line)") - if patched == content: + if _patch_framework_file( + gen_defines, " {edt.dts_path}", " {os.path.basename(edt.dts_path)}" + ): return - # Write atomically so a concurrent build never sees a truncated file - tmp = uf2conv.with_suffix(".py.tmp") - tmp.write_text(patched, encoding="utf-8") - shutil.copymode(uf2conv, tmp) - tmp.replace(uf2conv) + if "{os.path.basename(edt.dts_path)}" not in gen_defines.read_text( + encoding="utf-8" + ): + # Upstream reformatted the comment; sharing silently degrading + # would be invisible, so say it out loud. + _LOGGER.warning( + "gen_defines.py no longer matches; the devicetree header " + "stays per device and ccache sharing between devices degrades" + ) # West projects every build needs; components add others with include_west_project() @@ -349,7 +403,7 @@ def bluetooth_west_projects() -> tuple[str, ...]: return ("tinycrypt",) -def _wanted_west_projects() -> set[str]: +def wanted_west_projects() -> set[str]: projects = set(_get_data().west_projects) # Zephyr 4.1 moved the Cortex-M core headers to cmsis_6 if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(3, 1, 0): @@ -587,7 +641,7 @@ def _check_and_install(version: str) -> None: framework_path = _get_framework_path(version) sentinel = framework_path / ".ready" zephyr_reqs = framework_path / "zephyr" / "scripts" / "requirements.txt" - projects = _wanted_west_projects() + projects = wanted_west_projects() if not sentinel.exists() or not zephyr_reqs.exists(): _install_framework(env_python_path, framework_path, version, projects) framework_ver = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] @@ -596,6 +650,8 @@ def _check_and_install(version: str) -> None: sentinel.touch() else: _fetch_missing_west_projects(env_python_path, framework_path, version, projects) + # Every run: existing installs need it too, and it is a no-op once applied + _patch_gen_defines_dts_path(framework_path) zephyr_sentinel = python_env_path / ".zephyr_reqs_ready" if ( diff --git a/esphome/components/ntc/__init__.py b/esphome/components/ntc/__init__.py index e69de29bb2d..6b126fe8d01 100644 --- a/esphome/components/ntc/__init__.py +++ b/esphome/components/ntc/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ntc" diff --git a/esphome/components/ntc/ntc.cpp b/esphome/components/ntc/ntc.cpp index 49a42e858e5..d2b095a3d75 100644 --- a/esphome/components/ntc/ntc.cpp +++ b/esphome/components/ntc/ntc.cpp @@ -3,7 +3,7 @@ namespace esphome::ntc { -static const char *const TAG = "ntc"; +ESPHOME_LOG_TAG(TAG, "ntc"); void NTC::setup() { this->sensor_->add_on_state_callback([this](float value) { this->process_(value); }); diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index 8edbdecee5f..5822c95c7ef 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -154,6 +154,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_WIND_DIRECTION, DEVICE_CLASS_WIND_SPEED, ] +DOMAIN = "number" IS_PLATFORM_COMPONENT = True number_ns = cg.esphome_ns.namespace("number") diff --git a/esphome/components/number/automation.cpp b/esphome/components/number/automation.cpp index a3d49a6ff61..1bca604659c 100644 --- a/esphome/components/number/automation.cpp +++ b/esphome/components/number/automation.cpp @@ -3,7 +3,7 @@ namespace esphome::number { -static const char *const TAG = "number.automation"; +ESPHOME_LOG_TAG(TAG, "number.automation"); union convert { float from; diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index ca5aab64693..1d5533c423b 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -5,7 +5,7 @@ namespace esphome::number { -static const char *const TAG = "number"; +ESPHOME_LOG_TAG(TAG, "number"); // Function implementation of LOG_NUMBER macro to reduce code size void log_number(const char *tag, const char *prefix, const char *type, Number *obj) { diff --git a/esphome/components/number/number_call.cpp b/esphome/components/number/number_call.cpp index e300ca72de8..a928eaf3fa4 100644 --- a/esphome/components/number/number_call.cpp +++ b/esphome/components/number/number_call.cpp @@ -4,7 +4,7 @@ namespace esphome::number { -static const char *const TAG = "number"; +ESPHOME_LOG_TAG(TAG, "number"); // Helper functions to reduce code size for logging void NumberCall::log_perform_warning_(const LogString *message) { diff --git a/esphome/components/number/number_traits.cpp b/esphome/components/number/number_traits.cpp index 1e4239ceca5..e9559e91778 100644 --- a/esphome/components/number/number_traits.cpp +++ b/esphome/components/number/number_traits.cpp @@ -3,6 +3,6 @@ namespace esphome::number { -static const char *const TAG = "number"; +ESPHOME_LOG_TAG(TAG, "number"); } // namespace esphome::number diff --git a/esphome/components/number/sensor/number_sensor.cpp b/esphome/components/number/sensor/number_sensor.cpp index 227202622ae..e90c349f4fa 100644 --- a/esphome/components/number/sensor/number_sensor.cpp +++ b/esphome/components/number/sensor/number_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::number { -static const char *const TAG = "number.sensor"; +ESPHOME_LOG_TAG(TAG, "number.sensor"); void NumberSensor::setup() { this->source_->add_on_state_callback([this](float value) { this->publish_state(value); }); diff --git a/esphome/components/one_wire/__init__.py b/esphome/components/one_wire/__init__.py index 8d35aaa34ea..69368a990ad 100644 --- a/esphome/components/one_wire/__init__.py +++ b/esphome/components/one_wire/__init__.py @@ -5,6 +5,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@ssieb"] +DOMAIN = "one_wire" IS_PLATFORM_COMPONENT = True diff --git a/esphome/components/one_wire/one_wire.cpp b/esphome/components/one_wire/one_wire.cpp index aeab821f6ad..e9504720729 100644 --- a/esphome/components/one_wire/one_wire.cpp +++ b/esphome/components/one_wire/one_wire.cpp @@ -2,20 +2,7 @@ namespace esphome::one_wire { -static const char *const TAG = "one_wire"; - -const std::string &OneWireDevice::get_address_name() { - if (this->address_name_.empty()) { - char hex_buf[19]; // "0x" + 16 hex chars + null - this->address_name_ = format_hex_prefixed_to(hex_buf, this->address_); - } - return this->address_name_; -} - -void OneWireDevice::set_address(uint64_t address) { - this->address_ = address; - this->address_name_.clear(); -} +ESPHOME_LOG_TAG(TAG, "one_wire"); bool OneWireDevice::send_command_(uint8_t cmd) { if (!this->bus_->select(this->address_)) diff --git a/esphome/components/one_wire/one_wire.h b/esphome/components/one_wire/one_wire.h index 4dbbe117927..e3abb95bb70 100644 --- a/esphome/components/one_wire/one_wire.h +++ b/esphome/components/one_wire/one_wire.h @@ -7,14 +7,17 @@ namespace esphome::one_wire { #define LOG_ONE_WIRE_DEVICE(this) \ - ESP_LOGCONFIG(TAG, " Address: %s (%s)", this->get_address_name().c_str(), \ - LOG_STR_ARG(this->bus_->get_model_str(this->address_ & 0xff))); + do { \ + char address_buf[format_hex_prefixed_size(sizeof(uint64_t))]; \ + ESP_LOGCONFIG(TAG, " Address: %s (%s)", format_hex_prefixed_to(address_buf, (this)->address_), \ + LOG_STR_ARG((this)->bus_->get_model_str((this)->address_ & 0xff))); \ + } while (0) class OneWireDevice { public: /// @brief store the address of the device /// @param address of the device - void set_address(uint64_t address); + void set_address(uint64_t address) { this->address_ = address; } void set_index(uint8_t index) { this->index_ = index; } @@ -22,16 +25,12 @@ class OneWireDevice { /// @param bus pointer to the OneWireBus object void set_one_wire_bus(OneWireBus *bus) { this->bus_ = bus; } - /// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29". - const std::string &get_address_name(); - protected: static constexpr uint8_t INDEX_NOT_SET = 255; uint64_t address_{0}; uint8_t index_{INDEX_NOT_SET}; OneWireBus *bus_{nullptr}; ///< pointer to OneWireBus instance - std::string address_name_; /// @brief find an address if necessary /// should be called from setup diff --git a/esphome/components/one_wire/one_wire_bus.cpp b/esphome/components/one_wire/one_wire_bus.cpp index b62e4f47d4c..0a4880cf733 100644 --- a/esphome/components/one_wire/one_wire_bus.cpp +++ b/esphome/components/one_wire/one_wire_bus.cpp @@ -3,7 +3,7 @@ namespace esphome::one_wire { -static const char *const TAG = "one_wire"; +ESPHOME_LOG_TAG(TAG, "one_wire"); static const uint8_t DALLAS_MODEL_DS18S20 = 0x10; static const uint8_t DALLAS_MODEL_DS1822 = 0x22; diff --git a/esphome/components/online_image/download_buffer.cpp b/esphome/components/online_image/download_buffer.cpp index 2ec6365aa17..c4ea9e55332 100644 --- a/esphome/components/online_image/download_buffer.cpp +++ b/esphome/components/online_image/download_buffer.cpp @@ -4,7 +4,7 @@ namespace esphome::online_image { -static const char *const TAG = "online_image.download_buffer"; +ESPHOME_LOG_TAG(TAG, "online_image.download_buffer"); DownloadBuffer::DownloadBuffer(size_t size) : size_(size) { RAMAllocator allocator; diff --git a/esphome/components/online_image/online_image.cpp b/esphome/components/online_image/online_image.cpp index a21849d618c..4497d5cad12 100644 --- a/esphome/components/online_image/online_image.cpp +++ b/esphome/components/online_image/online_image.cpp @@ -5,7 +5,7 @@ #include #include -static const char *const TAG = "online_image"; +ESPHOME_LOG_TAG(TAG, "online_image"); static const char *const CONTENT_TYPE_HEADER_NAME = "content-type"; static const char *const ETAG_HEADER_NAME = "etag"; static const char *const IF_NONE_MATCH_HEADER_NAME = "if-none-match"; diff --git a/esphome/components/opentherm/__init__.py b/esphome/components/opentherm/__init__.py index 85632d0bf87..cfcdfe77833 100644 --- a/esphome/components/opentherm/__init__.py +++ b/esphome/components/opentherm/__init__.py @@ -5,12 +5,13 @@ import esphome.codegen as cg from esphome.components import sensor from esphome.components.esp32 import include_builtin_idf_component import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_TRIGGER_ID, PLATFORM_ESP32, PLATFORM_ESP8266 +from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266 from esphome.core import CORE from . import const, generate, schema, validate CODEOWNERS = ["@olegtarasov"] +DOMAIN = "opentherm" MULTI_CONF = True CONF_IN_PIN = "in_pin" @@ -26,14 +27,16 @@ CONF_SYNC_MODE = "sync_mode" CONF_BEFORE_SEND = "before_send" CONF_BEFORE_PROCESS_RESPONSE = "before_process_response" -# Triggers -BeforeSendTrigger = generate.opentherm_ns.class_( - "BeforeSendTrigger", - automation.Trigger.template(generate.OpenthermData.operator("ref")), -) -BeforeProcessResponseTrigger = generate.opentherm_ns.class_( - "BeforeProcessResponseTrigger", - automation.Trigger.template(generate.OpenthermData.operator("ref")), +_DATA_ARGS = [(generate.OpenthermData.operator("ref"), "x")] +_CALLBACK_AUTOMATIONS = ( + automation.CallbackAutomation( + CONF_BEFORE_SEND, "add_on_before_send_callback", _DATA_ARGS + ), + automation.CallbackAutomation( + CONF_BEFORE_PROCESS_RESPONSE, + "add_on_before_process_response_callback", + _DATA_ARGS, + ), ) CONFIG_SCHEMA = cv.All( @@ -50,17 +53,9 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_SUMMER_MODE_ACTIVE, False): cv.boolean, cv.Optional(CONF_DHW_BLOCK, False): cv.boolean, cv.Optional(CONF_SYNC_MODE, False): cv.boolean, - cv.Optional(CONF_BEFORE_SEND): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BeforeSendTrigger), - } - ), + cv.Optional(CONF_BEFORE_SEND): automation.validate_automation({}), cv.Optional(CONF_BEFORE_PROCESS_RESPONSE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - BeforeProcessResponseTrigger - ), - } + {} ), } ) @@ -131,14 +126,4 @@ async def to_code(config: dict[str, Any]) -> None: generate.define_setting_readers(const.SETTING, settings) generate.add_messages(var, settings, schema.SETTINGS) - for conf in config.get(CONF_BEFORE_SEND, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(generate.OpenthermData.operator("ref"), "x")], conf - ) - - for conf in config.get(CONF_BEFORE_PROCESS_RESPONSE, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation( - trigger, [(generate.OpenthermData.operator("ref"), "x")], conf - ) + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) diff --git a/esphome/components/opentherm/automation.h b/esphome/components/opentherm/automation.h deleted file mode 100644 index 365992b2805..00000000000 --- a/esphome/components/opentherm/automation.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include "esphome/core/automation.h" -#include "hub.h" -#include "opentherm.h" - -namespace esphome::opentherm { - -class BeforeSendTrigger final : public Trigger { - public: - BeforeSendTrigger(OpenthermHub *hub) { - hub->add_on_before_send_callback([this](OpenthermData &x) { this->trigger(x); }); - } -}; - -class BeforeProcessResponseTrigger final : public Trigger { - public: - BeforeProcessResponseTrigger(OpenthermHub *hub) { - hub->add_on_before_process_response_callback([this](OpenthermData &x) { this->trigger(x); }); - } -}; - -} // namespace esphome::opentherm diff --git a/esphome/components/opentherm/hub.cpp b/esphome/components/opentherm/hub.cpp index f8b515fa05b..c253d18671d 100644 --- a/esphome/components/opentherm/hub.cpp +++ b/esphome/components/opentherm/hub.cpp @@ -5,7 +5,7 @@ namespace esphome::opentherm { -static const char *const TAG = "opentherm"; +ESPHOME_LOG_TAG(TAG, "opentherm"); namespace message_data { bool parse_flag8_lb_0(OpenthermData &data) { return read_bit(data.valueLB, 0); } bool parse_flag8_lb_1(OpenthermData &data) { return read_bit(data.valueLB, 1); } diff --git a/esphome/components/opentherm/number/opentherm_number.cpp b/esphome/components/opentherm/number/opentherm_number.cpp index d78c6eb38a1..f2d8d76f6f6 100644 --- a/esphome/components/opentherm/number/opentherm_number.cpp +++ b/esphome/components/opentherm/number/opentherm_number.cpp @@ -2,7 +2,7 @@ namespace esphome::opentherm { -static const char *const TAG = "opentherm.number"; +ESPHOME_LOG_TAG(TAG, "opentherm.number"); void OpenthermNumber::control(float value) { this->publish_state(value); diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp index 3343871cd03..b8b639167d2 100644 --- a/esphome/components/opentherm/opentherm.cpp +++ b/esphome/components/opentherm/opentherm.cpp @@ -20,7 +20,7 @@ namespace esphome::opentherm { using std::string; -static const char *const TAG = "opentherm"; +ESPHOME_LOG_TAG(TAG, "opentherm"); #ifdef ESP8266 OpenTherm *OpenTherm::instance = nullptr; diff --git a/esphome/components/opentherm/output/opentherm_output.cpp b/esphome/components/opentherm/output/opentherm_output.cpp index 9b87cd8d12d..adbb1c40c16 100644 --- a/esphome/components/opentherm/output/opentherm_output.cpp +++ b/esphome/components/opentherm/output/opentherm_output.cpp @@ -3,7 +3,7 @@ namespace esphome::opentherm { -static const char *const TAG = "opentherm.output"; +ESPHOME_LOG_TAG(TAG, "opentherm.output"); void opentherm::OpenthermOutput::write_state(float state) { ESP_LOGD(TAG, "Received state: %.2f. Min value: %.2f, max value: %.2f", state, min_value_, max_value_); diff --git a/esphome/components/opentherm/switch/opentherm_switch.cpp b/esphome/components/opentherm/switch/opentherm_switch.cpp index 2fc99e793e8..6bf06f825e9 100644 --- a/esphome/components/opentherm/switch/opentherm_switch.cpp +++ b/esphome/components/opentherm/switch/opentherm_switch.cpp @@ -2,7 +2,7 @@ namespace esphome::opentherm { -static const char *const TAG = "opentherm.switch"; +ESPHOME_LOG_TAG(TAG, "opentherm.switch"); void OpenthermSwitch::write_state(bool state) { this->publish_state(state); } diff --git a/esphome/components/openthread/__init__.py b/esphome/components/openthread/__init__.py index ac14efc8e68..25c7a78bebe 100644 --- a/esphome/components/openthread/__init__.py +++ b/esphome/components/openthread/__init__.py @@ -13,12 +13,13 @@ from esphome.components.esp32 import ( get_esp32_variant, include_builtin_idf_component, only_on_variant, + require_mbedtls_ecp, require_mbedtls_tls_extras, require_mbedtls_tls_server, require_vfs_select, ) from esphome.components.mdns import MDNSComponent, enable_mdns_storage -from esphome.components.network import add_use_address +from esphome.components.network import DOMAIN as NETWORK_DOMAIN, add_use_address from esphome.components.nrf52.framework import include_west_project from esphome.components.zephyr import zephyr_add_prj_conf from esphome.config_helpers import filter_source_files_from_platform @@ -61,6 +62,7 @@ from .const import ( ) CODEOWNERS = ["@mrene"] +DOMAIN = "openthread" AUTO_LOAD = ["network"] @@ -112,9 +114,9 @@ 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. + # Commissioner/joiner Kconfigs default off, so no mbedtls_ssl_* is linked; + # setting one under sdkconfig_options keeps TLS in the build automatically. + # The crypto platform uses AES-CCM and deterministic ECDSA directly. require_mbedtls_tls_server() require_mbedtls_tls_extras( ("CONFIG_MBEDTLS_CCM_C", "CONFIG_MBEDTLS_ECDSA_DETERMINISTIC") @@ -259,7 +261,7 @@ CONFIG_SCHEMA = cv.All( def _final_validate(_: ConfigType) -> None: full_config = fv.full_config.get() - network_config = full_config.get("network", {}) + network_config = full_config.get(NETWORK_DOMAIN, {}) if not network_config.get(CONF_ENABLE_IPV6, False): raise cv.Invalid( "OpenThread requires IPv6 to be enabled in the network component. " @@ -293,6 +295,8 @@ async def to_code(config: ConfigType) -> None: # Re-enable openthread IDF component (excluded by default) if CORE.is_esp32: include_builtin_idf_component("openthread") + # OPENTHREAD_CONFIG_ECDSA_ENABLE: the SRP client host key uses mbedtls_ecdsa_* + require_mbedtls_ecp() cg.add_define("USE_OPENTHREAD") if config.get(CONF_FORCE_DATASET): diff --git a/esphome/components/openthread/openthread.cpp b/esphome/components/openthread/openthread.cpp index 7d98522cd23..4986850cd4f 100644 --- a/esphome/components/openthread/openthread.cpp +++ b/esphome/components/openthread/openthread.cpp @@ -16,7 +16,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -static const char *const TAG = "openthread"; +ESPHOME_LOG_TAG(TAG, "openthread"); namespace esphome::openthread { diff --git a/esphome/components/openthread/openthread_esp.cpp b/esphome/components/openthread/openthread_esp.cpp index 526fc961f2f..f3860b2cc81 100644 --- a/esphome/components/openthread/openthread_esp.cpp +++ b/esphome/components/openthread/openthread_esp.cpp @@ -17,7 +17,7 @@ #include "esp_vfs_eventfd.h" #include "nvs_flash.h" -static const char *const TAG = "openthread"; +ESPHOME_LOG_TAG(TAG, "openthread"); namespace esphome::openthread { diff --git a/esphome/components/openthread/openthread_zephyr.cpp b/esphome/components/openthread/openthread_zephyr.cpp index 0f474fc040c..c252034c33c 100644 --- a/esphome/components/openthread/openthread_zephyr.cpp +++ b/esphome/components/openthread/openthread_zephyr.cpp @@ -7,7 +7,7 @@ #include "esphome/core/helpers.h" #include -static const char *const TAG = "openthread"; +ESPHOME_LOG_TAG(TAG, "openthread"); namespace esphome::openthread { diff --git a/esphome/components/openthread_info/__init__.py b/esphome/components/openthread_info/__init__.py index e69de29bb2d..245d84a1044 100644 --- a/esphome/components/openthread_info/__init__.py +++ b/esphome/components/openthread_info/__init__.py @@ -0,0 +1 @@ +DOMAIN = "openthread_info" diff --git a/esphome/components/openthread_info/openthread_info_sensor.cpp b/esphome/components/openthread_info/openthread_info_sensor.cpp index 9beb52d64b0..d17dc9a9b6b 100644 --- a/esphome/components/openthread_info/openthread_info_sensor.cpp +++ b/esphome/components/openthread_info/openthread_info_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::openthread_info { -static const char *const TAG = "openthread_info"; +ESPHOME_LOG_TAG(TAG, "openthread_info"); void ParentAverageRssiOpenThreadInfo::dump_config() { LOG_SENSOR("", "Parent Average RSSI", this); } void ParentLastRssiOpenThreadInfo::dump_config() { LOG_SENSOR("", "Parent Last RSSI", this); } diff --git a/esphome/components/openthread_info/openthread_info_text_sensor.cpp b/esphome/components/openthread_info/openthread_info_text_sensor.cpp index fc61ad81b20..44eb5468502 100644 --- a/esphome/components/openthread_info/openthread_info_text_sensor.cpp +++ b/esphome/components/openthread_info/openthread_info_text_sensor.cpp @@ -5,7 +5,7 @@ namespace esphome::openthread_info { -static const char *const TAG = "openthread_info"; +ESPHOME_LOG_TAG(TAG, "openthread_info"); void IPAddressOpenThreadInfo::dump_config() { LOG_TEXT_SENSOR("", "IPAddress", this); } void RoleOpenThreadInfo::dump_config() { LOG_TEXT_SENSOR("", "Role", this); } diff --git a/esphome/components/opt3001/__init__.py b/esphome/components/opt3001/__init__.py index e69de29bb2d..efd544b1e25 100644 --- a/esphome/components/opt3001/__init__.py +++ b/esphome/components/opt3001/__init__.py @@ -0,0 +1 @@ +DOMAIN = "opt3001" diff --git a/esphome/components/opt3001/opt3001.cpp b/esphome/components/opt3001/opt3001.cpp index ce5fbdfd826..0101368cebd 100644 --- a/esphome/components/opt3001/opt3001.cpp +++ b/esphome/components/opt3001/opt3001.cpp @@ -3,7 +3,10 @@ namespace esphome::opt3001 { -static const char *const TAG = "opt3001.sensor"; +ESPHOME_LOG_TAG(TAG, "opt3001.sensor"); + +static constexpr uint32_t RESULT_RETRY_TIMEOUT_ID = 0; +static constexpr uint32_t READ_TIMEOUT_ID = 1; static const uint8_t OPT3001_REG_RESULT = 0x00; static const uint8_t OPT3001_REG_CONFIGURATION = 0x01; @@ -42,7 +45,7 @@ void OPT3001Sensor::read_result_(const std::function &f) { if ((raw_value & OPT3001_CONFIGURATION_CONVERSION_MODE_MASK) != OPT3001_CONFIGURATION_CONVERSION_MODE_SHUTDOWN) { // not ready; wait 10ms and try again ESP_LOGW(TAG, "Data not ready; waiting 10ms"); - this->set_timeout("opt3001_wait", 10, [this, f]() { read_result_(f); }); + this->set_timeout(RESULT_RETRY_TIMEOUT_ID, 10, [this, f]() { read_result_(f); }); return; } @@ -70,7 +73,7 @@ void OPT3001Sensor::read_lx_(const std::function &f) { return; } - this->set_timeout("read", OPT3001_CONVERSION_TIME_800, [this, f]() { + this->set_timeout(READ_TIMEOUT_ID, OPT3001_CONVERSION_TIME_800, [this, f]() { if (this->write(&OPT3001_REG_CONFIGURATION, 1) != i2c::ERROR_OK) { ESP_LOGW(TAG, "Starting configuration register read failed"); f(NAN); diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 6031a684894..f7e03a16990 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -22,6 +22,7 @@ from esphome.types import ConfigType OTA_STATE_LISTENER_KEY = "ota_state_listener" CODEOWNERS = ["@esphome/core"] +DOMAIN = "ota" def AUTO_LOAD() -> list[str]: diff --git a/esphome/components/ota/ota_backend_arduino_libretiny.cpp b/esphome/components/ota/ota_backend_arduino_libretiny.cpp index 231c4d2dd2e..02a5c5750f8 100644 --- a/esphome/components/ota/ota_backend_arduino_libretiny.cpp +++ b/esphome/components/ota/ota_backend_arduino_libretiny.cpp @@ -9,7 +9,7 @@ namespace esphome::ota { -static const char *const TAG = "ota"; +ESPHOME_LOG_TAG(TAG, "ota"); std::unique_ptr make_ota_backend() { return make_unique(); } diff --git a/esphome/components/ota/ota_backend_arduino_rp2.cpp b/esphome/components/ota/ota_backend_arduino_rp2.cpp index 48725b12657..74f44c0b3bc 100644 --- a/esphome/components/ota/ota_backend_arduino_rp2.cpp +++ b/esphome/components/ota/ota_backend_arduino_rp2.cpp @@ -11,7 +11,7 @@ namespace esphome::ota { -static const char *const TAG = "ota"; +ESPHOME_LOG_TAG(TAG, "ota"); std::unique_ptr make_ota_backend() { return make_unique(); } diff --git a/esphome/components/ota/ota_backend_esp8266.cpp b/esphome/components/ota/ota_backend_esp8266.cpp index 2a6a9e08b1f..5be55a52000 100644 --- a/esphome/components/ota/ota_backend_esp8266.cpp +++ b/esphome/components/ota/ota_backend_esp8266.cpp @@ -46,7 +46,7 @@ static constexpr size_t MIN_BUFFER_SIZE = 256; namespace esphome::ota { -static const char *const TAG = "ota"; +ESPHOME_LOG_TAG(TAG, "ota"); std::unique_ptr make_ota_backend() { return make_unique(); } diff --git a/esphome/components/ota/ota_backend_esp_idf.cpp b/esphome/components/ota/ota_backend_esp_idf.cpp index f33f37bbeb0..2316aad5cf6 100644 --- a/esphome/components/ota/ota_backend_esp_idf.cpp +++ b/esphome/components/ota/ota_backend_esp_idf.cpp @@ -15,7 +15,7 @@ namespace esphome::ota { -static const char *const TAG = "ota"; +ESPHOME_LOG_TAG(TAG, "ota"); std::unique_ptr make_ota_backend() { return make_unique(); } diff --git a/esphome/components/ota/ota_backend_host.cpp b/esphome/components/ota/ota_backend_host.cpp index 89e3f99e1e9..456fbb58638 100644 --- a/esphome/components/ota/ota_backend_host.cpp +++ b/esphome/components/ota/ota_backend_host.cpp @@ -27,7 +27,7 @@ namespace esphome::ota { namespace { -const char *const TAG = "ota"; +ESPHOME_LOG_TAG(TAG, "ota"); constexpr size_t MAX_OTA_SIZE = 256u * 1024u * 1024u; // 256 MiB constexpr size_t HEADER_PEEK_SIZE = 64; diff --git a/esphome/components/ota/ota_bootloader_esp_idf.cpp b/esphome/components/ota/ota_bootloader_esp_idf.cpp index 5a83d92689f..a59d010e4c6 100644 --- a/esphome/components/ota/ota_bootloader_esp_idf.cpp +++ b/esphome/components/ota/ota_bootloader_esp_idf.cpp @@ -12,7 +12,7 @@ namespace esphome::ota { -static const char *const TAG = "ota"; +ESPHOME_LOG_TAG(TAG, "ota"); OTAResponseTypes IDFOTABackend::register_and_validate_bootloader_part_() { // Register the bootloader partition diff --git a/esphome/components/ota/ota_partitions_esp_idf.cpp b/esphome/components/ota/ota_partitions_esp_idf.cpp index d2b1196de65..f9ce1b49e11 100644 --- a/esphome/components/ota/ota_partitions_esp_idf.cpp +++ b/esphome/components/ota/ota_partitions_esp_idf.cpp @@ -16,7 +16,7 @@ namespace esphome::ota { -static const char *const TAG = "ota"; +ESPHOME_LOG_TAG(TAG, "ota"); static inline bool check_overlap(uint32_t a_offset, size_t a_size, uint32_t b_offset, size_t b_size) { return (a_offset + a_size > b_offset && b_offset + b_size > a_offset); diff --git a/esphome/components/ota/ota_signature_esp_idf.cpp b/esphome/components/ota/ota_signature_esp_idf.cpp index 2192a794410..59ffa9edb0d 100644 --- a/esphome/components/ota/ota_signature_esp_idf.cpp +++ b/esphome/components/ota/ota_signature_esp_idf.cpp @@ -30,7 +30,7 @@ namespace esphome::ota { -static const char *const TAG = "ota"; +ESPHOME_LOG_TAG(TAG, "ota"); // Route the "Signature check: " prefix (and its per-block form) through one // shared format string each, so the prefix is pooled once by the linker instead diff --git a/esphome/components/output/button/output_button.cpp b/esphome/components/output/button/output_button.cpp index 5833743586f..17ffc39d7f0 100644 --- a/esphome/components/output/button/output_button.cpp +++ b/esphome/components/output/button/output_button.cpp @@ -3,7 +3,9 @@ namespace esphome::output { -static const char *const TAG = "output.button"; +ESPHOME_LOG_TAG(TAG, "output.button"); + +static constexpr uint32_t RESET_TIMEOUT_ID = 0; void OutputButton::dump_config() { LOG_BUTTON("", "Output Button", this); @@ -12,8 +14,8 @@ void OutputButton::dump_config() { void OutputButton::press_action() { this->output_->turn_on(); - // Use a named timeout so that it's automatically cancelled if button is pressed again before it's reset - this->set_timeout("reset", this->duration_, [this]() { this->output_->turn_off(); }); + // One timeout id, so a second press restarts the reset instead of stacking another + this->set_timeout(RESET_TIMEOUT_ID, this->duration_, [this]() { this->output_->turn_off(); }); } } // namespace esphome::output diff --git a/esphome/components/output/float_output.cpp b/esphome/components/output/float_output.cpp index e2b029f3687..8057d0f9f18 100644 --- a/esphome/components/output/float_output.cpp +++ b/esphome/components/output/float_output.cpp @@ -4,7 +4,7 @@ namespace esphome::output { -static const char *const TAG = "output.float"; +ESPHOME_LOG_TAG(TAG, "output.float"); #ifdef USE_OUTPUT_FLOAT_POWER_SCALING void FloatOutput::set_max_power(float max_power) { diff --git a/esphome/components/output/lock/output_lock.cpp b/esphome/components/output/lock/output_lock.cpp index ee9d9185425..2f90f602f0f 100644 --- a/esphome/components/output/lock/output_lock.cpp +++ b/esphome/components/output/lock/output_lock.cpp @@ -3,7 +3,7 @@ namespace esphome::output { -static const char *const TAG = "output.lock"; +ESPHOME_LOG_TAG(TAG, "output.lock"); void OutputLock::dump_config() { LOG_LOCK("", "Output Lock", this); } diff --git a/esphome/components/output/switch/output_switch.cpp b/esphome/components/output/switch/output_switch.cpp index a21cbc0f6b9..a7bbbab5e68 100644 --- a/esphome/components/output/switch/output_switch.cpp +++ b/esphome/components/output/switch/output_switch.cpp @@ -3,7 +3,7 @@ namespace esphome::output { -static const char *const TAG = "output.switch"; +ESPHOME_LOG_TAG(TAG, "output.switch"); void OutputSwitch::dump_config() { LOG_SWITCH("", "Output Switch", this); } void OutputSwitch::setup() { this->control(this->get_initial_state_with_restore_mode().value_or(false)); } diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index 703af8281d2..9498d4b4da7 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -7,6 +7,7 @@ from typing import Any from esphome import git, yaml_util from esphome.components.substitutions import ( + DOMAIN as SUBSTITUTIONS_DOMAIN, ContextVars, ErrList, push_context, @@ -38,7 +39,7 @@ from esphome.core import EsphomeError _LOGGER = logging.getLogger(__name__) -DOMAIN = CONF_PACKAGES +DOMAIN = "packages" # Guard against infinite include chains (e.g. A includes B includes A). MAX_INCLUDE_DEPTH = 20 @@ -353,7 +354,7 @@ def _walk_packages( packages = config[CONF_PACKAGES] packages_path = (path or []) + [CONF_PACKAGES] - with cv.prepend_path(CONF_PACKAGES): + with cv.prepend_path(DOMAIN): if isinstance(packages, yaml_util.IncludeFile): # If the packages key is an IncludeFile, resolve it first before processing. packages = resolve_include( @@ -599,7 +600,7 @@ def do_packages_pass( if CONF_PACKAGES not in config: return config - with cv.prepend_path(CONF_SUBSTITUTIONS): + with cv.prepend_path(SUBSTITUTIONS_DOMAIN): substitutions = UserDict( resolve_substitutions_block( config.pop(CONF_SUBSTITUTIONS, {}), command_line_substitutions diff --git a/esphome/components/packet_transport/packet_transport.cpp b/esphome/components/packet_transport/packet_transport.cpp index 998e1be5fc6..f2dc43654ae 100644 --- a/esphome/components/packet_transport/packet_transport.cpp +++ b/esphome/components/packet_transport/packet_transport.cpp @@ -50,7 +50,7 @@ static constexpr size_t PACKET_MAX_LOG_BYTES = 168; * Ping key (4 bytes) * */ -static const char *const TAG = "packet_transport"; +ESPHOME_LOG_TAG(TAG, "packet_transport"); static size_t round4(size_t value) { return (value + 3) & ~3; } diff --git a/esphome/components/partition/__init__.py b/esphome/components/partition/__init__.py index e69de29bb2d..7d911eb405b 100644 --- a/esphome/components/partition/__init__.py +++ b/esphome/components/partition/__init__.py @@ -0,0 +1 @@ +DOMAIN = "partition" diff --git a/esphome/components/partition/light_partition.cpp b/esphome/components/partition/light_partition.cpp index 2755f82294a..6f2645b3658 100644 --- a/esphome/components/partition/light_partition.cpp +++ b/esphome/components/partition/light_partition.cpp @@ -3,6 +3,6 @@ namespace esphome::partition { -static const char *const TAG = "partition.light"; +ESPHOME_LOG_TAG(TAG, "partition.light"); } // namespace esphome::partition diff --git a/esphome/components/pca6416a/__init__.py b/esphome/components/pca6416a/__init__.py index 47b9118d9ae..69d8935977c 100644 --- a/esphome/components/pca6416a/__init__.py +++ b/esphome/components/pca6416a/__init__.py @@ -18,6 +18,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@Mat931"] DEPENDENCIES = ["i2c"] AUTO_LOAD = ["gpio_expander"] +DOMAIN = "pca6416a" MULTI_CONF = True pca6416a_ns = cg.esphome_ns.namespace("pca6416a") @@ -60,7 +61,7 @@ def validate_mode(value: ConfigType) -> ConfigType: PCA6416A_PIN_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(PCA6416AGPIOPin), - cv.Required(CONF_PCA6416A): cv.use_id(PCA6416AComponent), + cv.Required(CONF_PCA6416A): pins.use_id_or_address(PCA6416AComponent), cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), cv.Optional(CONF_MODE, default={}): cv.All( { diff --git a/esphome/components/pca6416a/pca6416a.cpp b/esphome/components/pca6416a/pca6416a.cpp index 4c19feab6eb..f805a8da545 100644 --- a/esphome/components/pca6416a/pca6416a.cpp +++ b/esphome/components/pca6416a/pca6416a.cpp @@ -20,7 +20,7 @@ enum PCA6416AGPIORegisters { PCAL6416A_PULL_DIR1 = 0x49, }; -static const char *const TAG = "pca6416a"; +ESPHOME_LOG_TAG(TAG, "pca6416a"); void PCA6416AComponent::setup() { // Test to see if device exists diff --git a/esphome/components/pca9554/__init__.py b/esphome/components/pca9554/__init__.py index 5272df2b555..cba5656e88d 100644 --- a/esphome/components/pca9554/__init__.py +++ b/esphome/components/pca9554/__init__.py @@ -17,6 +17,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@hwstar", "@clydebarrow", "@bdraco"] AUTO_LOAD = ["gpio_expander"] DEPENDENCIES = ["i2c"] +DOMAIN = "pca9554" MULTI_CONF = True CONF_PIN_COUNT = "pin_count" pca9554_ns = cg.esphome_ns.namespace("pca9554") @@ -66,7 +67,7 @@ PCA9554_PIN_SCHEMA = pins.gpio_base_schema( mode_validator=validate_mode, ).extend( { - cv.Required(CONF_PCA9554): cv.use_id(PCA9554Component), + cv.Required(CONF_PCA9554): pins.use_id_or_address(PCA9554Component), } ) diff --git a/esphome/components/pca9554/pca9554.cpp b/esphome/components/pca9554/pca9554.cpp index c3ea6a3c0ce..f367d140678 100644 --- a/esphome/components/pca9554/pca9554.cpp +++ b/esphome/components/pca9554/pca9554.cpp @@ -9,7 +9,7 @@ const uint8_t OUTPUT_REG = 1; const uint8_t INVERT_REG = 2; const uint8_t CONFIG_REG = 3; -static const char *const TAG = "pca9554"; +ESPHOME_LOG_TAG(TAG, "pca9554"); void PCA9554Component::setup() { this->reg_width_ = (this->pin_count_ + 7) / 8; diff --git a/esphome/components/pca9685/__init__.py b/esphome/components/pca9685/__init__.py index 817cfd1c30a..16103aee443 100644 --- a/esphome/components/pca9685/__init__.py +++ b/esphome/components/pca9685/__init__.py @@ -10,6 +10,7 @@ from esphome.const import ( from esphome.types import ConfigType DEPENDENCIES = ["i2c"] +DOMAIN = "pca9685" MULTI_CONF = True pca9685_ns = cg.esphome_ns.namespace("pca9685") diff --git a/esphome/components/pca9685/pca9685_output.cpp b/esphome/components/pca9685/pca9685_output.cpp index 533b3391b14..fe417ed65de 100644 --- a/esphome/components/pca9685/pca9685_output.cpp +++ b/esphome/components/pca9685/pca9685_output.cpp @@ -5,7 +5,7 @@ namespace esphome::pca9685 { -static const char *const TAG = "pca9685"; +ESPHOME_LOG_TAG(TAG, "pca9685"); // PCA9685 mode constants are now inline constexpr in pca9685_output.h diff --git a/esphome/components/pcd8544/__init__.py b/esphome/components/pcd8544/__init__.py index e69de29bb2d..ff11812a302 100644 --- a/esphome/components/pcd8544/__init__.py +++ b/esphome/components/pcd8544/__init__.py @@ -0,0 +1 @@ +DOMAIN = "pcd8544" diff --git a/esphome/components/pcd8544/pcd_8544.cpp b/esphome/components/pcd8544/pcd_8544.cpp index c80283ffc98..953c4ac19b6 100644 --- a/esphome/components/pcd8544/pcd_8544.cpp +++ b/esphome/components/pcd8544/pcd_8544.cpp @@ -5,7 +5,7 @@ namespace esphome::pcd8544 { -static const char *const TAG = "pcd_8544"; +ESPHOME_LOG_TAG(TAG, "pcd_8544"); void PCD8544::setup_pins_() { this->spi_setup(); diff --git a/esphome/components/pcf85063/__init__.py b/esphome/components/pcf85063/__init__.py index e69de29bb2d..577cea5e1e0 100644 --- a/esphome/components/pcf85063/__init__.py +++ b/esphome/components/pcf85063/__init__.py @@ -0,0 +1 @@ +DOMAIN = "pcf85063" diff --git a/esphome/components/pcf85063/pcf85063.cpp b/esphome/components/pcf85063/pcf85063.cpp index 6ec61770f24..8ce76e9781c 100644 --- a/esphome/components/pcf85063/pcf85063.cpp +++ b/esphome/components/pcf85063/pcf85063.cpp @@ -6,7 +6,7 @@ namespace esphome::pcf85063 { -static const char *const TAG = "pcf85063"; +ESPHOME_LOG_TAG(TAG, "pcf85063"); void PCF85063Component::setup() { if (!this->read_rtc_()) { diff --git a/esphome/components/pcf8563/__init__.py b/esphome/components/pcf8563/__init__.py index e69de29bb2d..624ec1be8ce 100644 --- a/esphome/components/pcf8563/__init__.py +++ b/esphome/components/pcf8563/__init__.py @@ -0,0 +1 @@ +DOMAIN = "pcf8563" diff --git a/esphome/components/pcf8563/pcf8563.cpp b/esphome/components/pcf8563/pcf8563.cpp index 93c0f2bdf27..90ea4b3fd10 100644 --- a/esphome/components/pcf8563/pcf8563.cpp +++ b/esphome/components/pcf8563/pcf8563.cpp @@ -6,7 +6,7 @@ namespace esphome::pcf8563 { -static const char *const TAG = "PCF8563"; +ESPHOME_LOG_TAG(TAG, "PCF8563"); void PCF8563Component::setup() { if (!this->read_rtc_()) { diff --git a/esphome/components/pcf8574/__init__.py b/esphome/components/pcf8574/__init__.py index 9f8c7fabd25..6c9ed3cd98b 100644 --- a/esphome/components/pcf8574/__init__.py +++ b/esphome/components/pcf8574/__init__.py @@ -16,6 +16,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["gpio_expander"] DEPENDENCIES = ["i2c"] +DOMAIN = "pcf8574" MULTI_CONF = True pcf8574_ns = cg.esphome_ns.namespace("pcf8574") @@ -63,7 +64,7 @@ PCF8574_PIN_SCHEMA = pins.gpio_base_schema( invertible=True, ).extend( { - cv.Required(CONF_PCF8574): cv.use_id(PCF8574Component), + cv.Required(CONF_PCF8574): pins.use_id_or_address(PCF8574Component), } ) diff --git a/esphome/components/pcf8574/pcf8574.cpp b/esphome/components/pcf8574/pcf8574.cpp index 2e054b0683a..727e9a20265 100644 --- a/esphome/components/pcf8574/pcf8574.cpp +++ b/esphome/components/pcf8574/pcf8574.cpp @@ -3,7 +3,7 @@ namespace esphome::pcf8574 { -static const char *const TAG = "pcf8574"; +ESPHOME_LOG_TAG(TAG, "pcf8574"); void PCF8574Component::setup() { if (!this->read_gpio_()) { diff --git a/esphome/components/pcm5122/__init__.py b/esphome/components/pcm5122/__init__.py index 81e00ca74ba..f79d1eadfcf 100644 --- a/esphome/components/pcm5122/__init__.py +++ b/esphome/components/pcm5122/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@remcom"] +DOMAIN = "pcm5122" diff --git a/esphome/components/pcm5122/pcm5122.cpp b/esphome/components/pcm5122/pcm5122.cpp index 4f6417f6c0f..0ca851357f8 100644 --- a/esphome/components/pcm5122/pcm5122.cpp +++ b/esphome/components/pcm5122/pcm5122.cpp @@ -7,7 +7,7 @@ namespace esphome::pcm5122 { -static const char *const TAG = "pcm5122"; +ESPHOME_LOG_TAG(TAG, "pcm5122"); void PCM5122::setup() { // Hold XSMT low (soft mute asserted) until init completes diff --git a/esphome/components/pcm5122/pcm5122_gpio.cpp b/esphome/components/pcm5122/pcm5122_gpio.cpp index 1aef1304579..104b2cb5350 100644 --- a/esphome/components/pcm5122/pcm5122_gpio.cpp +++ b/esphome/components/pcm5122/pcm5122_gpio.cpp @@ -5,7 +5,7 @@ namespace esphome::pcm5122 { -static const char *const TAG = "pcm5122.gpio"; +ESPHOME_LOG_TAG(TAG, "pcm5122.gpio"); void PCM5122GPIOPin::setup() { this->pin_mode(this->flags_); } diff --git a/esphome/components/pi4ioe5v6408/__init__.py b/esphome/components/pi4ioe5v6408/__init__.py index 11a5dd62bb6..cd901cb448d 100644 --- a/esphome/components/pi4ioe5v6408/__init__.py +++ b/esphome/components/pi4ioe5v6408/__init__.py @@ -20,6 +20,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["gpio_expander"] CODEOWNERS = ["@jesserockz"] DEPENDENCIES = ["i2c"] +DOMAIN = "pi4ioe5v6408" MULTI_CONF = True @@ -74,7 +75,7 @@ PI4IOE5V6408_PIN_SCHEMA = pins.gpio_base_schema( mode_validator=validate_mode, ).extend( { - cv.Required(CONF_PI4IOE5V6408): cv.use_id(PI4IOE5V6408Component), + cv.Required(CONF_PI4IOE5V6408): pins.use_id_or_address(PI4IOE5V6408Component), } ) diff --git a/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp b/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp index e8e9530dba7..9bc10872d28 100644 --- a/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp +++ b/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp @@ -14,7 +14,7 @@ static const uint8_t PI4IOE5V6408_REGISTER_IN_STATE = 0x0F; static const uint8_t PI4IOE5V6408_REGISTER_INTERRUPT_ENABLE_MASK = 0x11; static const uint8_t PI4IOE5V6408_REGISTER_INTERRUPT_STATUS = 0x13; -static const char *const TAG = "pi4ioe5v6408"; +ESPHOME_LOG_TAG(TAG, "pi4ioe5v6408"); void PI4IOE5V6408Component::setup() { if (this->reset_) { diff --git a/esphome/components/pid/__init__.py b/esphome/components/pid/__init__.py index 71a87b6ae50..b7b1c5c7afc 100644 --- a/esphome/components/pid/__init__.py +++ b/esphome/components/pid/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@OttoWinter"] +DOMAIN = "pid" diff --git a/esphome/components/pid/pid_autotuner.cpp b/esphome/components/pid/pid_autotuner.cpp index a7ae631956a..8b9ce7bbee3 100644 --- a/esphome/components/pid/pid_autotuner.cpp +++ b/esphome/components/pid/pid_autotuner.cpp @@ -5,7 +5,7 @@ namespace esphome::pid { -static const char *const TAG = "pid.autotune"; +ESPHOME_LOG_TAG(TAG, "pid.autotune"); /* * # PID Autotuner diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index 1f0d45b0847..d33a7f75441 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -3,7 +3,9 @@ namespace esphome::pid { -static const char *const TAG = "pid.climate"; +ESPHOME_LOG_TAG(TAG, "pid.climate"); + +static constexpr uint32_t AUTOTUNE_PROGRESS_INTERVAL_ID = 0; bool PIDClimate::set_deadband_thresholds(float threshold_low, float threshold_high) { if (threshold_low > threshold_high) { @@ -191,7 +193,7 @@ void PIDClimate::start_autotune(std::unique_ptr &&autotune) { "Until your sensor provides a reading, the autotuner may display \'nan\'", this->get_name().c_str()); - this->set_interval("autotune-progress", 10000, [this]() { + this->set_interval(AUTOTUNE_PROGRESS_INTERVAL_ID, 10000, [this]() { if (this->autotuner_ != nullptr && !this->autotuner_->is_finished()) this->autotuner_->dump_config(); }); diff --git a/esphome/components/pid/sensor/pid_climate_sensor.cpp b/esphome/components/pid/sensor/pid_climate_sensor.cpp index afc77efde33..1fbb42a0b19 100644 --- a/esphome/components/pid/sensor/pid_climate_sensor.cpp +++ b/esphome/components/pid/sensor/pid_climate_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::pid { -static const char *const TAG = "pid.sensor"; +ESPHOME_LOG_TAG(TAG, "pid.sensor"); void PIDClimateSensor::setup() { this->parent_->add_on_pid_computed_callback([this]() { this->update_from_parent_(); }); diff --git a/esphome/components/pipsolar/__init__.py b/esphome/components/pipsolar/__init__.py index b404409145c..54337b0b130 100644 --- a/esphome/components/pipsolar/__init__.py +++ b/esphome/components/pipsolar/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType DEPENDENCIES = ["uart"] CODEOWNERS = ["@andreashergert1984"] AUTO_LOAD = ["binary_sensor", "text_sensor", "sensor", "switch", "output"] +DOMAIN = "pipsolar" MULTI_CONF = True CONF_PIPSOLAR_ID = "pipsolar_id" diff --git a/esphome/components/pipsolar/output/pipsolar_output.cpp b/esphome/components/pipsolar/output/pipsolar_output.cpp index 1af753fce33..21e1a4ffcbf 100644 --- a/esphome/components/pipsolar/output/pipsolar_output.cpp +++ b/esphome/components/pipsolar/output/pipsolar_output.cpp @@ -4,7 +4,7 @@ namespace esphome::pipsolar { -static const char *const TAG = "pipsolar.output"; +ESPHOME_LOG_TAG(TAG, "pipsolar.output"); void PipsolarOutput::write_state(float state) { char tmp[16]; diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index bd5733fe747..ade93b87359 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -4,7 +4,7 @@ namespace esphome::pipsolar { -static const char *const TAG = "pipsolar"; +ESPHOME_LOG_TAG(TAG, "pipsolar"); void Pipsolar::setup() { this->state_ = STATE_IDLE; diff --git a/esphome/components/pipsolar/switch/pipsolar_switch.cpp b/esphome/components/pipsolar/switch/pipsolar_switch.cpp index 1eedfed0fd3..266df246300 100644 --- a/esphome/components/pipsolar/switch/pipsolar_switch.cpp +++ b/esphome/components/pipsolar/switch/pipsolar_switch.cpp @@ -4,7 +4,7 @@ namespace esphome::pipsolar { -static const char *const TAG = "pipsolar.switch"; +ESPHOME_LOG_TAG(TAG, "pipsolar.switch"); void PipsolarSwitch::dump_config() { LOG_SWITCH("", "Pipsolar Switch", this); } void PipsolarSwitch::write_state(bool state) { diff --git a/esphome/components/pixoo/__init__.py b/esphome/components/pixoo/__init__.py index b1de57df8f1..a2c5172bd14 100644 --- a/esphome/components/pixoo/__init__.py +++ b/esphome/components/pixoo/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@jesserockz"] +DOMAIN = "pixoo" diff --git a/esphome/components/pixoo/pixoo.cpp b/esphome/components/pixoo/pixoo.cpp index aa035be347d..7e9b0bd89bd 100644 --- a/esphome/components/pixoo/pixoo.cpp +++ b/esphome/components/pixoo/pixoo.cpp @@ -8,7 +8,7 @@ namespace esphome::pixoo { -static const char *const TAG = "pixoo"; +ESPHOME_LOG_TAG(TAG, "pixoo"); // Divoom LED-board packet protocol. static constexpr uint8_t PACKET_HEAD = 0xAA; diff --git a/esphome/components/pm1006/__init__.py b/esphome/components/pm1006/__init__.py index e69de29bb2d..e79bb3f2a40 100644 --- a/esphome/components/pm1006/__init__.py +++ b/esphome/components/pm1006/__init__.py @@ -0,0 +1 @@ +DOMAIN = "pm1006" diff --git a/esphome/components/pm1006/pm1006.cpp b/esphome/components/pm1006/pm1006.cpp index d4c6824713c..ebd508b5352 100644 --- a/esphome/components/pm1006/pm1006.cpp +++ b/esphome/components/pm1006/pm1006.cpp @@ -3,7 +3,7 @@ namespace esphome::pm1006 { -static const char *const TAG = "pm1006"; +ESPHOME_LOG_TAG(TAG, "pm1006"); static const uint8_t PM1006_RESPONSE_HEADER[] = {0x16, 0x11, 0x0B}; static const uint8_t PM1006_REQUEST[] = {0x11, 0x02, 0x0B, 0x01, 0xE1}; diff --git a/esphome/components/pm2005/__init__.py b/esphome/components/pm2005/__init__.py index 3716dd7b5eb..bda634dc03a 100644 --- a/esphome/components/pm2005/__init__.py +++ b/esphome/components/pm2005/__init__.py @@ -1 +1,3 @@ """PM2005/2105 component for ESPHome.""" + +DOMAIN = "pm2005" diff --git a/esphome/components/pm2005/pm2005.cpp b/esphome/components/pm2005/pm2005.cpp index 54a98bf3adb..784a555e024 100644 --- a/esphome/components/pm2005/pm2005.cpp +++ b/esphome/components/pm2005/pm2005.cpp @@ -3,7 +3,7 @@ namespace esphome::pm2005 { -static const char *const TAG = "pm2005"; +ESPHOME_LOG_TAG(TAG, "pm2005"); // Converts a sensor situation to a human readable string static const LogString *pm2005_get_situation_string(int status) { diff --git a/esphome/components/pmsa003i/__init__.py b/esphome/components/pmsa003i/__init__.py index e69de29bb2d..e66395e3de4 100644 --- a/esphome/components/pmsa003i/__init__.py +++ b/esphome/components/pmsa003i/__init__.py @@ -0,0 +1 @@ +DOMAIN = "pmsa003i" diff --git a/esphome/components/pmsa003i/pmsa003i.cpp b/esphome/components/pmsa003i/pmsa003i.cpp index f20b93fff80..6885795e8a7 100644 --- a/esphome/components/pmsa003i/pmsa003i.cpp +++ b/esphome/components/pmsa003i/pmsa003i.cpp @@ -5,7 +5,7 @@ namespace esphome::pmsa003i { -static const char *const TAG = "pmsa003i"; +ESPHOME_LOG_TAG(TAG, "pmsa003i"); static const uint8_t COUNT_PAYLOAD_BYTES = 28; static const uint8_t COUNT_PAYLOAD_LENGTH_BYTES = 2; diff --git a/esphome/components/pmsx003/__init__.py b/esphome/components/pmsx003/__init__.py index e69de29bb2d..1c60a105dd2 100644 --- a/esphome/components/pmsx003/__init__.py +++ b/esphome/components/pmsx003/__init__.py @@ -0,0 +1 @@ +DOMAIN = "pmsx003" diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp index f8d890ac9ec..14b1f60df54 100644 --- a/esphome/components/pmsx003/pmsx003.cpp +++ b/esphome/components/pmsx003/pmsx003.cpp @@ -4,7 +4,7 @@ namespace esphome::pmsx003 { -static const char *const TAG = "pmsx003"; +ESPHOME_LOG_TAG(TAG, "pmsx003"); static const uint8_t START_CHARACTER_1 = 0x42; static const uint8_t START_CHARACTER_2 = 0x4D; diff --git a/esphome/components/pmwcs3/__init__.py b/esphome/components/pmwcs3/__init__.py index b04c30a0053..64229140cba 100644 --- a/esphome/components/pmwcs3/__init__.py +++ b/esphome/components/pmwcs3/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@SeByDocKy"] +DOMAIN = "pmwcs3" diff --git a/esphome/components/pmwcs3/pmwcs3.cpp b/esphome/components/pmwcs3/pmwcs3.cpp index 94c0c307665..7493f5d97fb 100644 --- a/esphome/components/pmwcs3/pmwcs3.cpp +++ b/esphome/components/pmwcs3/pmwcs3.cpp @@ -20,7 +20,7 @@ static const uint8_t PMWCS3_REG_RES = 0x0B; static const uint8_t PMWCS3_REG_RC = 0x0C; static const uint8_t PMWCS3_REG_RT = 0x0D; -static const char *const TAG = "pmwcs3"; +ESPHOME_LOG_TAG(TAG, "pmwcs3"); void PMWCS3Component::new_i2c_address(uint8_t address) { if (!this->write_byte(PMWCS3_SET_I2C_ADDRESS, address)) { diff --git a/esphome/components/pn532/__init__.py b/esphome/components/pn532/__init__.py index c9118834c56..b8efeb213c9 100644 --- a/esphome/components/pn532/__init__.py +++ b/esphome/components/pn532/__init__.py @@ -13,6 +13,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@OttoWinter", "@jesserockz"] AUTO_LOAD = ["binary_sensor", "nfc"] +DOMAIN = "pn532" MULTI_CONF = True CONF_PN532_ID = "pn532_id" diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index 89392105a24..8fd92c9031f 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -14,7 +14,7 @@ namespace esphome::pn532 { -static const char *const TAG = "pn532"; +ESPHOME_LOG_TAG(TAG, "pn532"); void PN532::setup() { // Get version data diff --git a/esphome/components/pn532/pn532_mifare_classic.cpp b/esphome/components/pn532/pn532_mifare_classic.cpp index 9f937b3e288..878e07825d4 100644 --- a/esphome/components/pn532/pn532_mifare_classic.cpp +++ b/esphome/components/pn532/pn532_mifare_classic.cpp @@ -8,7 +8,7 @@ namespace esphome::pn532 { -static const char *const TAG = "pn532.mifare_classic"; +ESPHOME_LOG_TAG(TAG, "pn532.mifare_classic"); std::unique_ptr PN532::read_mifare_classic_tag_(nfc::NfcTagUid &uid) { uint8_t current_block = 4; diff --git a/esphome/components/pn532/pn532_mifare_ultralight.cpp b/esphome/components/pn532/pn532_mifare_ultralight.cpp index 90c85237224..afb60bd843a 100644 --- a/esphome/components/pn532/pn532_mifare_ultralight.cpp +++ b/esphome/components/pn532/pn532_mifare_ultralight.cpp @@ -7,7 +7,7 @@ namespace esphome::pn532 { -static const char *const TAG = "pn532.mifare_ultralight"; +ESPHOME_LOG_TAG(TAG, "pn532.mifare_ultralight"); std::unique_ptr PN532::read_mifare_ultralight_tag_(nfc::NfcTagUid &uid) { UltralightReadBuffer data; diff --git a/esphome/components/pn532_i2c/__init__.py b/esphome/components/pn532_i2c/__init__.py index 16b877b1f12..bd2a9373d76 100644 --- a/esphome/components/pn532_i2c/__init__.py +++ b/esphome/components/pn532_i2c/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["pn532"] CODEOWNERS = ["@OttoWinter", "@jesserockz"] DEPENDENCIES = ["i2c"] +DOMAIN = "pn532_i2c" MULTI_CONF = True pn532_i2c_ns = cg.esphome_ns.namespace("pn532_i2c") diff --git a/esphome/components/pn532_i2c/pn532_i2c.cpp b/esphome/components/pn532_i2c/pn532_i2c.cpp index 7f00e18d4c0..1687edd5116 100644 --- a/esphome/components/pn532_i2c/pn532_i2c.cpp +++ b/esphome/components/pn532_i2c/pn532_i2c.cpp @@ -11,7 +11,7 @@ namespace esphome::pn532_i2c { -static const char *const TAG = "pn532_i2c"; +ESPHOME_LOG_TAG(TAG, "pn532_i2c"); bool PN532I2C::is_read_ready() { uint8_t status; diff --git a/esphome/components/pn532_spi/__init__.py b/esphome/components/pn532_spi/__init__.py index 734f7941803..0e24425f765 100644 --- a/esphome/components/pn532_spi/__init__.py +++ b/esphome/components/pn532_spi/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["pn532"] CODEOWNERS = ["@OttoWinter", "@jesserockz"] DEPENDENCIES = ["spi"] +DOMAIN = "pn532_spi" MULTI_CONF = True pn532_spi_ns = cg.esphome_ns.namespace("pn532_spi") diff --git a/esphome/components/pn532_spi/pn532_spi.cpp b/esphome/components/pn532_spi/pn532_spi.cpp index 73d65e4330d..e86153be86a 100644 --- a/esphome/components/pn532_spi/pn532_spi.cpp +++ b/esphome/components/pn532_spi/pn532_spi.cpp @@ -11,7 +11,7 @@ namespace esphome::pn532_spi { -static const char *const TAG = "pn532_spi"; +ESPHOME_LOG_TAG(TAG, "pn532_spi"); // Maximum bytes to log in verbose hex output static constexpr size_t PN532_MAX_LOG_BYTES = 64; diff --git a/esphome/components/pn7150/__init__.py b/esphome/components/pn7150/__init__.py index 14299ee310a..4fbe07e2e32 100644 --- a/esphome/components/pn7150/__init__.py +++ b/esphome/components/pn7150/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["pn71xx"] CODEOWNERS = ["@kbx81", "@jesserockz"] +DOMAIN = "pn7150" pn7150_ns = cg.esphome_ns.namespace("pn7150") PN7150 = pn7150_ns.class_("PN7150", pn71xx.PN71xx) diff --git a/esphome/components/pn7150/pn7150.cpp b/esphome/components/pn7150/pn7150.cpp index 25bb7a0c241..16bbac38e47 100644 --- a/esphome/components/pn7150/pn7150.cpp +++ b/esphome/components/pn7150/pn7150.cpp @@ -4,7 +4,7 @@ namespace esphome::pn7150 { -static const char *const TAG = "pn7150"; +ESPHOME_LOG_TAG(TAG, "pn7150"); void PN7150::dump_config() { ESP_LOGCONFIG(TAG, "PN7150:"); diff --git a/esphome/components/pn7150_i2c/__init__.py b/esphome/components/pn7150_i2c/__init__.py index d15c0b431e3..c0dfb4c2e4b 100644 --- a/esphome/components/pn7150_i2c/__init__.py +++ b/esphome/components/pn7150_i2c/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["pn7150"] CODEOWNERS = ["@kbx81", "@jesserockz"] DEPENDENCIES = ["i2c"] +DOMAIN = "pn7150_i2c" pn7150_i2c_ns = cg.esphome_ns.namespace("pn7150_i2c") PN7150I2C = pn7150_i2c_ns.class_("PN7150I2C", pn7150.PN7150, i2c.I2CDevice) diff --git a/esphome/components/pn7150_i2c/pn7150_i2c.cpp b/esphome/components/pn7150_i2c/pn7150_i2c.cpp index 58d9daa311e..a87bd7d32e5 100644 --- a/esphome/components/pn7150_i2c/pn7150_i2c.cpp +++ b/esphome/components/pn7150_i2c/pn7150_i2c.cpp @@ -4,7 +4,7 @@ namespace esphome::pn7150_i2c { -static const char *const TAG = "pn7150_i2c"; +ESPHOME_LOG_TAG(TAG, "pn7150_i2c"); uint8_t PN7150I2C::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) { if (this->wait_for_irq_(timeout) != nfc::STATUS_OK) { diff --git a/esphome/components/pn7160/__init__.py b/esphome/components/pn7160/__init__.py index 4cde36e9e73..f1bd6122ff1 100644 --- a/esphome/components/pn7160/__init__.py +++ b/esphome/components/pn7160/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["pn71xx"] CODEOWNERS = ["@kbx81", "@jesserockz"] +DOMAIN = "pn7160" CONF_DWL_REQ_PIN = "dwl_req_pin" CONF_WKUP_REQ_PIN = "wkup_req_pin" diff --git a/esphome/components/pn7160/pn7160.cpp b/esphome/components/pn7160/pn7160.cpp index 185ff0ce097..7460f7cb6f9 100644 --- a/esphome/components/pn7160/pn7160.cpp +++ b/esphome/components/pn7160/pn7160.cpp @@ -5,7 +5,7 @@ namespace esphome::pn7160 { -static const char *const TAG = "pn7160"; +ESPHOME_LOG_TAG(TAG, "pn7160"); void PN7160::setup() { if (this->dwl_req_pin_ != nullptr) { diff --git a/esphome/components/pn7160_i2c/__init__.py b/esphome/components/pn7160_i2c/__init__.py index d711c3e48fc..f9add96bdf8 100644 --- a/esphome/components/pn7160_i2c/__init__.py +++ b/esphome/components/pn7160_i2c/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["pn7160"] CODEOWNERS = ["@kbx81", "@jesserockz"] DEPENDENCIES = ["i2c"] +DOMAIN = "pn7160_i2c" pn7160_i2c_ns = cg.esphome_ns.namespace("pn7160_i2c") PN7160I2C = pn7160_i2c_ns.class_("PN7160I2C", pn7160.PN7160, i2c.I2CDevice) diff --git a/esphome/components/pn7160_i2c/pn7160_i2c.cpp b/esphome/components/pn7160_i2c/pn7160_i2c.cpp index d559c6c2517..c9da1f4ad2e 100644 --- a/esphome/components/pn7160_i2c/pn7160_i2c.cpp +++ b/esphome/components/pn7160_i2c/pn7160_i2c.cpp @@ -4,7 +4,7 @@ namespace esphome::pn7160_i2c { -static const char *const TAG = "pn7160_i2c"; +ESPHOME_LOG_TAG(TAG, "pn7160_i2c"); uint8_t PN7160I2C::read_nfcc(nfc::NciMessage &rx, const uint16_t timeout) { if (this->wait_for_irq_(timeout) != nfc::STATUS_OK) { diff --git a/esphome/components/pn7160_spi/__init__.py b/esphome/components/pn7160_spi/__init__.py index 7705dfa6f6a..32c1008f72a 100644 --- a/esphome/components/pn7160_spi/__init__.py +++ b/esphome/components/pn7160_spi/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["pn7160"] CODEOWNERS = ["@kbx81", "@jesserockz"] DEPENDENCIES = ["spi"] +DOMAIN = "pn7160_spi" MULTI_CONF = True pn7160_spi_ns = cg.esphome_ns.namespace("pn7160_spi") diff --git a/esphome/components/pn7160_spi/pn7160_spi.cpp b/esphome/components/pn7160_spi/pn7160_spi.cpp index 85a6c1fb7d0..2914b11e480 100644 --- a/esphome/components/pn7160_spi/pn7160_spi.cpp +++ b/esphome/components/pn7160_spi/pn7160_spi.cpp @@ -3,7 +3,7 @@ namespace esphome::pn7160_spi { -static const char *const TAG = "pn7160_spi"; +ESPHOME_LOG_TAG(TAG, "pn7160_spi"); void PN7160Spi::setup() { this->spi_setup(); diff --git a/esphome/components/pn71xx/__init__.py b/esphome/components/pn71xx/__init__.py index 8472d4192c4..3b40eb11419 100644 --- a/esphome/components/pn71xx/__init__.py +++ b/esphome/components/pn71xx/__init__.py @@ -17,6 +17,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["binary_sensor", "nfc"] CODEOWNERS = ["@kbx81", "@jesserockz"] +DOMAIN = "pn71xx" CONF_EMULATION_MESSAGE = "emulation_message" CONF_INCLUDE_ANDROID_APP_RECORD = "include_android_app_record" diff --git a/esphome/components/pn71xx/pn71xx.cpp b/esphome/components/pn71xx/pn71xx.cpp index 6a24acd843e..469700e01ed 100644 --- a/esphome/components/pn71xx/pn71xx.cpp +++ b/esphome/components/pn71xx/pn71xx.cpp @@ -10,7 +10,7 @@ namespace esphome::pn71xx { -static const char *const TAG = "pn71xx"; +ESPHOME_LOG_TAG(TAG, "pn71xx"); // Builds a message with a URI record and, optionally, the Home Assistant Android app record static std::unique_ptr build_uri_message(const std::string &uri, diff --git a/esphome/components/pn71xx/pn71xx_mifare_classic.cpp b/esphome/components/pn71xx/pn71xx_mifare_classic.cpp index c1c927634d4..e1e805d1087 100644 --- a/esphome/components/pn71xx/pn71xx_mifare_classic.cpp +++ b/esphome/components/pn71xx/pn71xx_mifare_classic.cpp @@ -8,7 +8,7 @@ namespace esphome::pn71xx { -static const char *const TAG = "pn71xx.mifare_classic"; +ESPHOME_LOG_TAG(TAG, "pn71xx.mifare_classic"); uint8_t PN71xx::read_mifare_classic_tag_(nfc::NfcTag &tag) { uint8_t current_block = 4; diff --git a/esphome/components/pn71xx/pn71xx_mifare_ultralight.cpp b/esphome/components/pn71xx/pn71xx_mifare_ultralight.cpp index 53874e18de5..68b86b6257b 100644 --- a/esphome/components/pn71xx/pn71xx_mifare_ultralight.cpp +++ b/esphome/components/pn71xx/pn71xx_mifare_ultralight.cpp @@ -8,7 +8,7 @@ namespace esphome::pn71xx { -static const char *const TAG = "pn71xx.mifare_ultralight"; +ESPHOME_LOG_TAG(TAG, "pn71xx.mifare_ultralight"); uint8_t PN71xx::read_mifare_ultralight_tag_(nfc::NfcTag &tag) { UltralightReadBuffer data; diff --git a/esphome/components/power_supply/__init__.py b/esphome/components/power_supply/__init__.py index b68118b3523..4d90642b5f5 100644 --- a/esphome/components/power_supply/__init__.py +++ b/esphome/components/power_supply/__init__.py @@ -11,6 +11,7 @@ from esphome.const import ( from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] +DOMAIN = "power_supply" power_supply_ns = cg.esphome_ns.namespace("power_supply") PowerSupply = power_supply_ns.class_("PowerSupply", cg.Component) MULTI_CONF = True diff --git a/esphome/components/power_supply/power_supply.cpp b/esphome/components/power_supply/power_supply.cpp index f094f6e2e98..8fa1b92c259 100644 --- a/esphome/components/power_supply/power_supply.cpp +++ b/esphome/components/power_supply/power_supply.cpp @@ -3,11 +3,18 @@ namespace esphome::power_supply { -static const char *const TAG = "power_supply"; +ESPHOME_LOG_TAG(TAG, "power_supply"); + +static constexpr uint32_t POWER_OFF_TIMEOUT_ID = 0; void PowerSupply::setup() { this->pin_->setup(); - this->pin_->digital_write(false); + if (this->pin_->is_held()) { + // Rail stayed on across the reset; drop it unless something asks for it + this->schedule_off_if_idle_(); + } else { + this->pin_->digital_write(false); + } if (this->enable_on_boot_) this->request_high_power(); } @@ -31,7 +38,7 @@ bool PowerSupply::is_enabled() const { return this->active_requests_ != 0; } void PowerSupply::request_high_power() { if (this->active_requests_ == 0) { - this->cancel_timeout("power-supply-off"); + this->cancel_timeout(POWER_OFF_TIMEOUT_ID); ESP_LOGV(TAG, "Enabling"); this->pin_->digital_write(true); delay(this->enable_time_); @@ -45,14 +52,21 @@ void PowerSupply::unrequest_high_power() { return; } this->active_requests_--; + this->schedule_off_if_idle_(); +} + +void PowerSupply::schedule_off_if_idle_() { if (this->active_requests_ == 0) { - this->set_timeout("power-supply-off", this->keep_on_time_, [this]() { + this->set_timeout(POWER_OFF_TIMEOUT_ID, this->keep_on_time_, [this]() { ESP_LOGV(TAG, "Disabling"); this->pin_->digital_write(false); }); } } + void PowerSupply::on_powerdown() { + if (this->pin_->get_hold()) + return; this->active_requests_ = 0; this->pin_->digital_write(false); } diff --git a/esphome/components/power_supply/power_supply.h b/esphome/components/power_supply/power_supply.h index eaf77af32e9..dc6b368ea8d 100644 --- a/esphome/components/power_supply/power_supply.h +++ b/esphome/components/power_supply/power_supply.h @@ -39,6 +39,7 @@ class PowerSupply final : public Component { uint32_t keep_on_time_; int16_t active_requests_{0}; // use signed integer to make catching negative requests easier. bool enable_on_boot_{false}; + void schedule_off_if_idle_(); }; class PowerSupplyRequester { diff --git a/esphome/components/preferences/__init__.py b/esphome/components/preferences/__init__.py index abd050ec3b2..142cfb32dce 100644 --- a/esphome/components/preferences/__init__.py +++ b/esphome/components/preferences/__init__.py @@ -7,6 +7,7 @@ from esphome.coroutine import CoroPriority from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] +DOMAIN = "preferences" preferences_ns = cg.esphome_ns.namespace("preferences") IntervalSyncer = preferences_ns.class_("IntervalSyncer", cg.PollingComponent) diff --git a/esphome/components/prometheus/__init__.py b/esphome/components/prometheus/__init__.py index 3624fb24421..8bf0163fa78 100644 --- a/esphome/components/prometheus/__init__.py +++ b/esphome/components/prometheus/__init__.py @@ -7,6 +7,7 @@ from esphome.cpp_types import EntityBase from esphome.types import ConfigType AUTO_LOAD = ["web_server_base"] +DOMAIN = "prometheus" prometheus_ns = cg.esphome_ns.namespace("prometheus") PrometheusHandler = prometheus_ns.class_("PrometheusHandler", cg.Component) diff --git a/esphome/components/provisioning/provisioning.cpp b/esphome/components/provisioning/provisioning.cpp index 02c089bfed3..0089bdcbb72 100644 --- a/esphome/components/provisioning/provisioning.cpp +++ b/esphome/components/provisioning/provisioning.cpp @@ -10,7 +10,7 @@ namespace esphome::provisioning { -static const char *const TAG = "provisioning"; +ESPHOME_LOG_TAG(TAG, "provisioning"); ProvisioningManager *global_provisioning_manager = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) nullptr; diff --git a/esphome/components/psram/psram.cpp b/esphome/components/psram/psram.cpp index ab680c96955..55b6873d055 100644 --- a/esphome/components/psram/psram.cpp +++ b/esphome/components/psram/psram.cpp @@ -9,7 +9,7 @@ #include namespace esphome::psram { -static const char *const TAG = "psram"; +ESPHOME_LOG_TAG(TAG, "psram"); void PsramComponent::dump_config() { ESP_LOGCONFIG(TAG, "PSRAM:"); diff --git a/esphome/components/pulse_counter/__init__.py b/esphome/components/pulse_counter/__init__.py index e69de29bb2d..9efceae6deb 100644 --- a/esphome/components/pulse_counter/__init__.py +++ b/esphome/components/pulse_counter/__init__.py @@ -0,0 +1 @@ +DOMAIN = "pulse_counter" diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.cpp b/esphome/components/pulse_counter/pulse_counter_sensor.cpp index 13bf3baf832..b104016fb8f 100644 --- a/esphome/components/pulse_counter/pulse_counter_sensor.cpp +++ b/esphome/components/pulse_counter/pulse_counter_sensor.cpp @@ -9,7 +9,7 @@ namespace esphome::pulse_counter { -static const char *const TAG = "pulse_counter"; +ESPHOME_LOG_TAG(TAG, "pulse_counter"); const char *const EDGE_MODE_TO_STRING[] = {"DISABLE", "INCREMENT", "DECREMENT"}; diff --git a/esphome/components/pulse_meter/__init__.py b/esphome/components/pulse_meter/__init__.py index e69de29bb2d..7c4fef139cf 100644 --- a/esphome/components/pulse_meter/__init__.py +++ b/esphome/components/pulse_meter/__init__.py @@ -0,0 +1 @@ +DOMAIN = "pulse_meter" diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.cpp b/esphome/components/pulse_meter/pulse_meter_sensor.cpp index d6959d1a964..c2d4c648ce9 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.cpp +++ b/esphome/components/pulse_meter/pulse_meter_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::pulse_meter { -static const char *const TAG = "pulse_meter"; +ESPHOME_LOG_TAG(TAG, "pulse_meter"); void PulseMeterSensor::set_total_pulses(uint32_t pulses) { this->total_pulses_ = pulses; diff --git a/esphome/components/pulse_width/__init__.py b/esphome/components/pulse_width/__init__.py index e69de29bb2d..97cf2e1c39c 100644 --- a/esphome/components/pulse_width/__init__.py +++ b/esphome/components/pulse_width/__init__.py @@ -0,0 +1 @@ +DOMAIN = "pulse_width" diff --git a/esphome/components/pulse_width/pulse_width.cpp b/esphome/components/pulse_width/pulse_width.cpp index 5209ed5352a..53896cbacdb 100644 --- a/esphome/components/pulse_width/pulse_width.cpp +++ b/esphome/components/pulse_width/pulse_width.cpp @@ -3,7 +3,7 @@ namespace esphome::pulse_width { -static const char *const TAG = "pulse_width"; +ESPHOME_LOG_TAG(TAG, "pulse_width"); void IRAM_ATTR PulseWidthSensorStore::gpio_intr(PulseWidthSensorStore *arg) { const bool new_level = arg->pin_.digital_read(); diff --git a/esphome/components/pvvx_mithermometer/__init__.py b/esphome/components/pvvx_mithermometer/__init__.py index e69de29bb2d..56767de8044 100644 --- a/esphome/components/pvvx_mithermometer/__init__.py +++ b/esphome/components/pvvx_mithermometer/__init__.py @@ -0,0 +1 @@ +DOMAIN = "pvvx_mithermometer" diff --git a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp index 64b8974901a..995c4aff50d 100644 --- a/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp +++ b/esphome/components/pvvx_mithermometer/display/pvvx_display.cpp @@ -7,7 +7,9 @@ namespace esphome::pvvx_mithermometer { -static const char *const TAG = "display.pvvx_mithermometer"; +ESPHOME_LOG_TAG(TAG, "display.pvvx_mithermometer"); + +static constexpr uint32_t DISCONNECT_TIMEOUT_ID = 0; void PVVXDisplay::dump_config() { char service_buf[ble_device_base::UUID_STR_LEN]; @@ -39,7 +41,7 @@ void PVVXDisplay::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t case ESP_GATTC_DISCONNECT_EVT: ESP_LOGV(TAG, "[%s] Disconnected", this->parent_->address_str()); this->connection_established_ = false; - this->cancel_timeout("disconnect"); + this->cancel_timeout(DISCONNECT_TIMEOUT_ID); this->char_handle_ = 0; break; case ESP_GATTC_SEARCH_CMPL_EVT: { @@ -147,8 +149,8 @@ void PVVXDisplay::send_to_setup_char_(uint8_t *blk, size_t size) { void PVVXDisplay::delayed_disconnect_() { if (this->disconnect_delay_ms_ == 0) return; - this->cancel_timeout("disconnect"); - this->set_timeout("disconnect", this->disconnect_delay_ms_, [this]() { this->parent_->set_enabled(false); }); + this->cancel_timeout(DISCONNECT_TIMEOUT_ID); + this->set_timeout(DISCONNECT_TIMEOUT_ID, this->disconnect_delay_ms_, [this]() { this->parent_->set_enabled(false); }); } void PVVXDisplay::sync_time_and_display_() { diff --git a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp index 9141d12b16c..0d7f440f15e 100644 --- a/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp +++ b/esphome/components/pvvx_mithermometer/pvvx_mithermometer.cpp @@ -3,7 +3,7 @@ namespace esphome::pvvx_mithermometer { -static const char *const TAG = "pvvx_mithermometer"; +ESPHOME_LOG_TAG(TAG, "pvvx_mithermometer"); void PVVXMiThermometer::dump_config() { ESP_LOGCONFIG(TAG, "PVVX MiThermometer"); diff --git a/esphome/components/pylontech/__init__.py b/esphome/components/pylontech/__init__.py index 242a613a6c3..f9eef4c8096 100644 --- a/esphome/components/pylontech/__init__.py +++ b/esphome/components/pylontech/__init__.py @@ -10,6 +10,7 @@ _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@functionpointer"] DEPENDENCIES = ["uart"] +DOMAIN = "pylontech" MULTI_CONF = True CONF_PYLONTECH_ID = "pylontech_id" diff --git a/esphome/components/pylontech/pylontech.cpp b/esphome/components/pylontech/pylontech.cpp index 932b71ba55c..0898b924869 100644 --- a/esphome/components/pylontech/pylontech.cpp +++ b/esphome/components/pylontech/pylontech.cpp @@ -26,7 +26,7 @@ namespace esphome::pylontech { -static const char *const TAG = "pylontech"; +ESPHOME_LOG_TAG(TAG, "pylontech"); static const int MAX_DATA_LENGTH_BYTES = 256; static const uint8_t ASCII_LF = 0x0A; diff --git a/esphome/components/pylontech/sensor/pylontech_sensor.cpp b/esphome/components/pylontech/sensor/pylontech_sensor.cpp index e2def28be54..dc236f47a55 100644 --- a/esphome/components/pylontech/sensor/pylontech_sensor.cpp +++ b/esphome/components/pylontech/sensor/pylontech_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::pylontech { -static const char *const TAG = "pylontech.sensor"; +ESPHOME_LOG_TAG(TAG, "pylontech.sensor"); PylontechSensor::PylontechSensor(int8_t bat_num) { this->bat_num_ = bat_num; } diff --git a/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp b/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp index a7c9db15996..1beb04064ec 100644 --- a/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp +++ b/esphome/components/pylontech/text_sensor/pylontech_text_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::pylontech { -static const char *const TAG = "pylontech.textsensor"; +ESPHOME_LOG_TAG(TAG, "pylontech.textsensor"); PylontechTextSensor::PylontechTextSensor(int8_t bat_num) { this->bat_num_ = bat_num; } diff --git a/esphome/components/pzem004t/__init__.py b/esphome/components/pzem004t/__init__.py index e69de29bb2d..f8d5b7a0c1c 100644 --- a/esphome/components/pzem004t/__init__.py +++ b/esphome/components/pzem004t/__init__.py @@ -0,0 +1 @@ +DOMAIN = "pzem004t" diff --git a/esphome/components/pzem004t/pzem004t.cpp b/esphome/components/pzem004t/pzem004t.cpp index a28b448340a..7809e803c9d 100644 --- a/esphome/components/pzem004t/pzem004t.cpp +++ b/esphome/components/pzem004t/pzem004t.cpp @@ -5,7 +5,7 @@ namespace esphome::pzem004t { -static const char *const TAG = "pzem004t"; +ESPHOME_LOG_TAG(TAG, "pzem004t"); void PZEM004T::setup() { // Clear UART buffer diff --git a/esphome/components/pzem6l24/__init__.py b/esphome/components/pzem6l24/__init__.py index e69de29bb2d..45ecb06188c 100644 --- a/esphome/components/pzem6l24/__init__.py +++ b/esphome/components/pzem6l24/__init__.py @@ -0,0 +1 @@ +DOMAIN = "pzem6l24" diff --git a/esphome/components/pzem6l24/pzem6l24.cpp b/esphome/components/pzem6l24/pzem6l24.cpp index f9f97606a82..41524eebd27 100644 --- a/esphome/components/pzem6l24/pzem6l24.cpp +++ b/esphome/components/pzem6l24/pzem6l24.cpp @@ -8,7 +8,7 @@ namespace esphome::pzem6l24 { -static const char *const TAG = "pzem6l24"; +ESPHOME_LOG_TAG(TAG, "pzem6l24"); // ----------------------------------------------------------------------- // Register map (input registers, starting address 0x0000): diff --git a/esphome/components/pzemac/__init__.py b/esphome/components/pzemac/__init__.py index e69de29bb2d..600881c3243 100644 --- a/esphome/components/pzemac/__init__.py +++ b/esphome/components/pzemac/__init__.py @@ -0,0 +1 @@ +DOMAIN = "pzemac" diff --git a/esphome/components/pzemac/pzemac.cpp b/esphome/components/pzemac/pzemac.cpp index c0d163c1dec..e291844d787 100644 --- a/esphome/components/pzemac/pzemac.cpp +++ b/esphome/components/pzemac/pzemac.cpp @@ -5,7 +5,7 @@ namespace esphome::pzemac { namespace helpers = modbus::helpers; -static const char *const TAG = "pzemac"; +ESPHOME_LOG_TAG(TAG, "pzemac"); static const uint8_t PZEM_CMD_RESET_ENERGY = 0x42; static const uint8_t PZEM_REGISTER_COUNT = 10; // 10x 16-bit registers diff --git a/esphome/components/pzemdc/__init__.py b/esphome/components/pzemdc/__init__.py index e69de29bb2d..81e2bf067d5 100644 --- a/esphome/components/pzemdc/__init__.py +++ b/esphome/components/pzemdc/__init__.py @@ -0,0 +1 @@ +DOMAIN = "pzemdc" diff --git a/esphome/components/pzemdc/pzemdc.cpp b/esphome/components/pzemdc/pzemdc.cpp index eb9a3558069..76b9c2b2e09 100644 --- a/esphome/components/pzemdc/pzemdc.cpp +++ b/esphome/components/pzemdc/pzemdc.cpp @@ -5,7 +5,7 @@ namespace esphome::pzemdc { namespace helpers = modbus::helpers; -static const char *const TAG = "pzemdc"; +ESPHOME_LOG_TAG(TAG, "pzemdc"); static const uint8_t PZEM_CMD_RESET_ENERGY = 0x42; static const uint8_t PZEM_REGISTER_COUNT = 8; // 8x 16-bit registers diff --git a/esphome/components/qmc5883l/__init__.py b/esphome/components/qmc5883l/__init__.py index e69de29bb2d..fb646a60c3b 100644 --- a/esphome/components/qmc5883l/__init__.py +++ b/esphome/components/qmc5883l/__init__.py @@ -0,0 +1 @@ +DOMAIN = "qmc5883l" diff --git a/esphome/components/qmc5883l/qmc5883l.cpp b/esphome/components/qmc5883l/qmc5883l.cpp index ba6a71f97d1..dcfa46abc5b 100644 --- a/esphome/components/qmc5883l/qmc5883l.cpp +++ b/esphome/components/qmc5883l/qmc5883l.cpp @@ -7,7 +7,7 @@ namespace esphome::qmc5883l { -static const char *const TAG = "qmc5883l"; +ESPHOME_LOG_TAG(TAG, "qmc5883l"); static const uint8_t QMC5883L_ADDRESS = 0x0D; diff --git a/esphome/components/qmi8658/__init__.py b/esphome/components/qmi8658/__init__.py index 67838dbc3c1..6c871dfc4e0 100644 --- a/esphome/components/qmi8658/__init__.py +++ b/esphome/components/qmi8658/__init__.py @@ -4,6 +4,7 @@ from esphome.components.motion import MotionComponent CODEOWNERS = ["@clydebarrow"] DEPENDENCIES = ["i2c", "motion"] +DOMAIN = "qmi8658" CONF_QMI8658_ID = "qmi8658_id" # C++ namespace / class diff --git a/esphome/components/qmi8658/qmi8658.cpp b/esphome/components/qmi8658/qmi8658.cpp index 2fd457d2900..4f35d5bf365 100644 --- a/esphome/components/qmi8658/qmi8658.cpp +++ b/esphome/components/qmi8658/qmi8658.cpp @@ -4,7 +4,7 @@ namespace esphome::qmi8658 { -static const char *const TAG = "qmi8658"; +ESPHOME_LOG_TAG(TAG, "qmi8658"); // Acceleration scale (g per LSB), indexed by accel_range_ >> 4. // Full-scale = range_g, mapped over a signed 16-bit value (2^15 counts). diff --git a/esphome/components/qmp6988/__init__.py b/esphome/components/qmp6988/__init__.py index 09bcf515895..86498d192a4 100644 --- a/esphome/components/qmp6988/__init__.py +++ b/esphome/components/qmp6988/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@andrewpc"] +DOMAIN = "qmp6988" diff --git a/esphome/components/qmp6988/qmp6988.cpp b/esphome/components/qmp6988/qmp6988.cpp index 547991f75e0..ea3bf19eb4c 100644 --- a/esphome/components/qmp6988/qmp6988.cpp +++ b/esphome/components/qmp6988/qmp6988.cpp @@ -43,7 +43,7 @@ static const uint8_t QMP6988_CONFIG_REG_FILTER_LEN = 3; static const uint32_t SUBTRACTOR = 8388608; -static const char *const TAG = "qmp6988"; +ESPHOME_LOG_TAG(TAG, "qmp6988"); static const char *oversampling_to_str(QMP6988Oversampling oversampling) { switch (oversampling) { diff --git a/esphome/components/qr_code/__init__.py b/esphome/components/qr_code/__init__.py index 8b773dcdf54..222041837bd 100644 --- a/esphome/components/qr_code/__init__.py +++ b/esphome/components/qr_code/__init__.py @@ -7,6 +7,7 @@ CONF_SCALE = "scale" CONF_ECC = "ecc" CODEOWNERS = ["@wjtje"] +DOMAIN = "qr_code" DEPENDENCIES = ["display"] MULTI_CONF = True diff --git a/esphome/components/qr_code/qr_code.cpp b/esphome/components/qr_code/qr_code.cpp index edb78b98e1e..78d20d9bfe0 100644 --- a/esphome/components/qr_code/qr_code.cpp +++ b/esphome/components/qr_code/qr_code.cpp @@ -5,7 +5,7 @@ namespace esphome::qr_code { -static const char *const TAG = "qr_code"; +ESPHOME_LOG_TAG(TAG, "qr_code"); void QrCode::dump_config() { ESP_LOGCONFIG(TAG, diff --git a/esphome/components/qspi_amoled/display.py b/esphome/components/qspi_amoled/display.py deleted file mode 100644 index 00773b516ad..00000000000 --- a/esphome/components/qspi_amoled/display.py +++ /dev/null @@ -1,3 +0,0 @@ -import esphome.config_validation as cv - -CONFIG_SCHEMA = cv.invalid("The qspi_amoled component has been renamed to qspi_dbi") diff --git a/esphome/components/qspi_dbi/__init__.py b/esphome/components/qspi_dbi/__init__.py index aebbe2fcc82..abf64f675af 100644 --- a/esphome/components/qspi_dbi/__init__.py +++ b/esphome/components/qspi_dbi/__init__.py @@ -1,4 +1,5 @@ CODEOWNERS = ["@clydebarrow"] +DOMAIN = "qspi_dbi" CONF_DRAW_FROM_ORIGIN = "draw_from_origin" diff --git a/esphome/components/qwiic_pir/__init__.py b/esphome/components/qwiic_pir/__init__.py index e69de29bb2d..2232f1e6707 100644 --- a/esphome/components/qwiic_pir/__init__.py +++ b/esphome/components/qwiic_pir/__init__.py @@ -0,0 +1 @@ +DOMAIN = "qwiic_pir" diff --git a/esphome/components/qwiic_pir/qwiic_pir.cpp b/esphome/components/qwiic_pir/qwiic_pir.cpp index eb338db772d..fffc3514d03 100644 --- a/esphome/components/qwiic_pir/qwiic_pir.cpp +++ b/esphome/components/qwiic_pir/qwiic_pir.cpp @@ -3,7 +3,7 @@ namespace esphome::qwiic_pir { -static const char *const TAG = "qwiic_pir"; +ESPHOME_LOG_TAG(TAG, "qwiic_pir"); void QwiicPIRComponent::setup() { // Verify I2C communcation by reading and verifying the chip ID diff --git a/esphome/components/radio_frequency/__init__.py b/esphome/components/radio_frequency/__init__.py index f303a142975..cd680450d50 100644 --- a/esphome/components/radio_frequency/__init__.py +++ b/esphome/components/radio_frequency/__init__.py @@ -10,22 +10,22 @@ Once the API is considered stable, this warning will be removed. from esphome import automation import esphome.codegen as cg +from esphome.components import ir_rf_base import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_ON_CONTROL -from esphome.core import CORE, coroutine_with_priority -from esphome.core.entity_helpers import queue_entity_register, setup_entity +from esphome.core import coroutine_with_priority +from esphome.core.entity_helpers import setup_entity from esphome.coroutine import CoroPriority from esphome.types import ConfigType, SafeExpType CODEOWNERS = ["@kbx81"] -AUTO_LOAD = ["remote_base"] +AUTO_LOAD = ["ir_rf_base"] +DOMAIN = "radio_frequency" IS_PLATFORM_COMPONENT = True radio_frequency_ns = cg.esphome_ns.namespace("radio_frequency") -RadioFrequency = radio_frequency_ns.class_( - "RadioFrequency", cg.EntityBase, cg.Component -) +RadioFrequency = radio_frequency_ns.class_("RadioFrequency", ir_rf_base.IrRfEntity) RadioFrequencyCall = radio_frequency_ns.class_("RadioFrequencyCall") RadioFrequencyTraits = radio_frequency_ns.class_("RadioFrequencyTraits") RadioFrequencyModulation = radio_frequency_ns.enum("RadioFrequencyModulation") @@ -55,11 +55,8 @@ async def setup_radio_frequency_core_(var: cg.MockObj, config: ConfigType) -> No async def register_radio_frequency(var: cg.MockObj, config: ConfigType) -> None: """Register a radio frequency device with the core.""" - cg.add_define("USE_RADIO_FREQUENCY") - await cg.register_component(var, config) - queue_entity_register("radio_frequency", config) + await ir_rf_base.register_ir_rf_entity(var, config, "radio_frequency") await setup_radio_frequency_core_(var, config) - CORE.register_platform_component("radio_frequency", var) for conf in config.get(CONF_ON_CONTROL, []): await automation.build_callback_automation( diff --git a/esphome/components/radio_frequency/radio_frequency.cpp b/esphome/components/radio_frequency/radio_frequency.cpp index 61e7feb9af4..231d6c3f898 100644 --- a/esphome/components/radio_frequency/radio_frequency.cpp +++ b/esphome/components/radio_frequency/radio_frequency.cpp @@ -4,73 +4,17 @@ #include "esphome/core/log.h" -#ifdef USE_API -#include "esphome/components/api/api_server.h" -#endif - namespace esphome::radio_frequency { -static const char *const TAG = "radio_frequency"; - -// ========== RadioFrequencyCall ========== - -RadioFrequencyCall &RadioFrequencyCall::set_frequency(uint32_t frequency_hz) { - this->frequency_hz_ = frequency_hz; - return *this; -} - -RadioFrequencyCall &RadioFrequencyCall::set_modulation(RadioFrequencyModulation modulation) { - this->modulation_ = modulation; - return *this; -} - -RadioFrequencyCall &RadioFrequencyCall::set_raw_timings(const std::vector &timings) { - this->raw_timings_ = &timings; - this->packed_data_ = nullptr; - this->base64url_ptr_ = nullptr; - return *this; -} - -RadioFrequencyCall &RadioFrequencyCall::set_raw_timings_base64url(const std::string &base64url) { - this->base64url_ptr_ = &base64url; - this->raw_timings_ = nullptr; - this->packed_data_ = nullptr; - return *this; -} - -RadioFrequencyCall &RadioFrequencyCall::set_raw_timings_packed(const uint8_t *data, uint16_t length, uint16_t count) { - this->packed_data_ = data; - this->packed_length_ = length; - this->packed_count_ = count; - this->raw_timings_ = nullptr; - this->base64url_ptr_ = nullptr; - return *this; -} - -RadioFrequencyCall &RadioFrequencyCall::set_repeat_count(uint32_t count) { - this->repeat_count_ = count; - return *this; -} - -void RadioFrequencyCall::perform() { - if (this->parent_ != nullptr) { - // Fire any on_control hooks (user-wired automations) before handing off to - // the platform-specific control() — gives users a chance to react to call - // parameters (e.g. retune an external RF front-end based on call.get_frequency()). - this->parent_->control_callback_.call(*this); - this->parent_->control(*this); - } -} - -// ========== RadioFrequency ========== +ESPHOME_LOG_TAG(TAG, "radio_frequency"); void RadioFrequency::dump_config() { ESP_LOGCONFIG(TAG, "Radio Frequency '%s'\n" " Supports Transmitter: %s\n" " Supports Receiver: %s", - this->get_name().c_str(), YESNO(this->traits_.get_supports_transmitter()), - YESNO(this->traits_.get_supports_receiver())); + this->get_name().c_str(), YESNO(this->get_supports_transmitter()), + YESNO(this->get_supports_receiver())); if (this->traits_.get_frequency_min_hz() > 0) { if (this->traits_.get_frequency_min_hz() == this->traits_.get_frequency_max_hz()) { ESP_LOGCONFIG(TAG, " Frequency: %" PRIu32 " Hz (fixed)", this->traits_.get_frequency_min_hz()); @@ -81,31 +25,9 @@ void RadioFrequency::dump_config() { } } -uint32_t RadioFrequency::get_capability_flags() const { - uint32_t flags = 0; - if (this->traits_.get_supports_transmitter()) - flags |= RadioFrequencyCapability::CAPABILITY_TRANSMITTER; - if (this->traits_.get_supports_receiver()) - flags |= RadioFrequencyCapability::CAPABILITY_RECEIVER; - return flags; -} - bool RadioFrequency::on_receive(remote_base::RemoteReceiveData data) { - // Invoke local callbacks this->receive_callback_.call(data); - - // Forward received RF data to API server -#if defined(USE_API) && defined(USE_RADIO_FREQUENCY) - if (api::global_api_server != nullptr) { -#ifdef USE_DEVICES - uint32_t device_id = this->get_device_id(); -#else - uint32_t device_id = 0; -#endif - api::global_api_server->send_infrared_rf_receive_event(device_id, this->get_object_id_hash(), &data.get_raw_data()); - } -#endif - return false; // Don't consume the event, allow other listeners to process it + return IrRfEntity::on_receive(data); } } // namespace esphome::radio_frequency diff --git a/esphome/components/radio_frequency/radio_frequency.h b/esphome/components/radio_frequency/radio_frequency.h index 8782c255f00..d084a6c0164 100644 --- a/esphome/components/radio_frequency/radio_frequency.h +++ b/esphome/components/radio_frequency/radio_frequency.h @@ -4,20 +4,12 @@ // without following the normal breaking changes policy. Use at your own risk. // Once the API is considered stable, this warning will be removed. -#include "esphome/core/component.h" -#include "esphome/core/entity_base.h" -#include "esphome/core/helpers.h" -#include "esphome/components/remote_base/remote_base.h" - -#include +#include "esphome/components/ir_rf_base/ir_rf_base.h" namespace esphome::radio_frequency { -/// Capability flags for individual radio frequency instances -enum RadioFrequencyCapability : uint32_t { - CAPABILITY_TRANSMITTER = 1 << 0, // Can transmit signals - CAPABILITY_RECEIVER = 1 << 1, // Can receive signals -}; +using ir_rf_base::CAPABILITY_RECEIVER; +using ir_rf_base::CAPABILITY_TRANSMITTER; /// Modulation types supported by radio frequency implementations enum RadioFrequencyModulation : uint8_t { @@ -25,95 +17,36 @@ enum RadioFrequencyModulation : uint8_t { // Future: RADIO_FREQUENCY_MODULATION_FSK, RADIO_FREQUENCY_MODULATION_GFSK, etc. }; -/// Forward declarations class RadioFrequency; /// RadioFrequencyCall - Builder pattern for transmitting radio frequency signals -class RadioFrequencyCall { +class RadioFrequencyCall : public ir_rf_base::IrRfCall { public: - explicit RadioFrequencyCall(RadioFrequency *parent) : parent_(parent) {} + explicit RadioFrequencyCall(RadioFrequency *parent) : IrRfCall(parent) {} /// Set the carrier frequency in Hz (e.g. 433920000 for 433.92 MHz) - RadioFrequencyCall &set_frequency(uint32_t frequency_hz); - + RadioFrequencyCall &set_frequency(uint32_t frequency_hz) { + this->frequency_hz_ = frequency_hz; + return *this; + } /// Set the modulation type (defaults to OOK) - RadioFrequencyCall &set_modulation(RadioFrequencyModulation modulation); - - // ===== Raw Timings Methods ===== - // All set_raw_timings_* methods store pointers/references to external data. - // The referenced data must remain valid until perform() completes. - // Safe pattern: call.set_raw_timings_xxx(data); call.perform(); // synchronous - // Unsafe pattern: call.set_raw_timings_xxx(data); defer([call]() { call.perform(); }); // data may be gone! - - /// Set the raw timings from a vector (positive = mark, negative = space) - /// @note Lifetime: Stores a pointer to the vector. The vector must outlive perform(). - /// @note Usage: Primarily for lambdas/automations where the vector is in scope. - RadioFrequencyCall &set_raw_timings(const std::vector &timings); - - /// Set the raw timings from base64url-encoded little-endian int32 data - /// @note Lifetime: Stores a pointer to the string. The string must outlive perform(). - /// @note Usage: For web_server - base64url is fully URL-safe (uses '-' and '_'). - /// @note Decoding happens at perform() time, directly into the transmit buffer. - RadioFrequencyCall &set_raw_timings_base64url(const std::string &base64url); - - /// Set the raw timings from packed protobuf sint32 data (zigzag + varint encoded) - /// @note Lifetime: Stores a pointer to the buffer. The buffer must outlive perform(). - /// @note Usage: For API component where data comes directly from the protobuf message. - RadioFrequencyCall &set_raw_timings_packed(const uint8_t *data, uint16_t length, uint16_t count); - - /// Set the number of times to repeat transmission (1 = transmit once, 2 = transmit twice, etc.) - RadioFrequencyCall &set_repeat_count(uint32_t count); - - /// Perform the transmission - void perform(); - + RadioFrequencyCall &set_modulation(RadioFrequencyModulation modulation) { + this->modulation_ = modulation; + return *this; + } /// Get the frequency in Hz const optional &get_frequency() const { return this->frequency_hz_; } /// Get the modulation type RadioFrequencyModulation get_modulation() const { return this->modulation_; } - /// Get the raw timings (only valid if set via set_raw_timings) - const std::vector &get_raw_timings() const { return *this->raw_timings_; } - /// Check if raw timings have been set (any format) - bool has_raw_timings() const { - return this->raw_timings_ != nullptr || this->packed_data_ != nullptr || this->base64url_ptr_ != nullptr; - } - /// Check if using packed data format - bool is_packed() const { return this->packed_data_ != nullptr; } - /// Check if using base64url data format - bool is_base64url() const { return this->base64url_ptr_ != nullptr; } - /// Get the base64url data string - const std::string &get_base64url_data() const { return *this->base64url_ptr_; } - /// Get packed data (only valid if set via set_raw_timings_packed) - const uint8_t *get_packed_data() const { return this->packed_data_; } - uint16_t get_packed_length() const { return this->packed_length_; } - uint16_t get_packed_count() const { return this->packed_count_; } - /// Get the repeat count - uint32_t get_repeat_count() const { return this->repeat_count_; } protected: optional frequency_hz_{}; - uint32_t repeat_count_{1}; - RadioFrequency *parent_; - // Pointer to vector-based timings (caller-owned, must outlive perform()) - const std::vector *raw_timings_{nullptr}; - // Pointer to base64url-encoded string (caller-owned, must outlive perform()) - const std::string *base64url_ptr_{nullptr}; - // Pointer to packed protobuf buffer (caller-owned, must outlive perform()) - const uint8_t *packed_data_{nullptr}; - uint16_t packed_length_{0}; - uint16_t packed_count_{0}; RadioFrequencyModulation modulation_{RADIO_FREQUENCY_MODULATION_OOK}; }; /// RadioFrequencyTraits - Describes the capabilities of a radio frequency implementation class RadioFrequencyTraits { public: - bool get_supports_transmitter() const { return this->supports_transmitter_; } - void set_supports_transmitter(bool supports) { this->supports_transmitter_ = supports; } - - bool get_supports_receiver() const { return this->supports_receiver_; } - void set_supports_receiver(bool supports) { this->supports_receiver_ = supports; } - /// Hardware-supported tunable frequency range in Hz. /// If min == max (and both non-zero): fixed-frequency hardware. /// If both 0: range unspecified. @@ -140,17 +73,14 @@ class RadioFrequencyTraits { uint32_t frequency_min_hz_{0}; // Minimum tunable frequency in Hz (0 = unspecified) uint32_t frequency_max_hz_{0}; // Maximum tunable frequency in Hz (0 = unspecified) uint32_t supported_modulations_{0}; // Bitmask of supported RadioFrequencyModulation values - bool supports_transmitter_{false}; - bool supports_receiver_{false}; }; /// RadioFrequency - Base class for radio frequency implementations -class RadioFrequency : public Component, public EntityBase, public remote_base::RemoteReceiverListener { +class RadioFrequency : public ir_rf_base::IrRfEntity { public: RadioFrequency() = default; void dump_config() override; - float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; } /// Get the traits for this radio frequency implementation RadioFrequencyTraits &get_traits() { return this->traits_; } @@ -159,9 +89,6 @@ class RadioFrequency : public Component, public EntityBase, public remote_base:: /// Create a call object for transmitting RadioFrequencyCall make_call() { return RadioFrequencyCall(this); } - /// Get capability flags for this radio frequency instance - uint32_t get_capability_flags() const; - /// Called when RF data is received (from RemoteReceiverListener) bool on_receive(remote_base::RemoteReceiveData data) override; @@ -180,11 +107,16 @@ class RadioFrequency : public Component, public EntityBase, public remote_base:: } protected: - friend class RadioFrequencyCall; + friend class ir_rf_base::IrRfCall; + /// Fires the on_control hooks before the platform-specific control() runs + void on_call_(const RadioFrequencyCall &call) { this->control_callback_.call(call); } /// Perform the actual transmission (called by RadioFrequencyCall::perform()) /// Platforms must override this to implement hardware-specific transmission. - virtual void control(const RadioFrequencyCall &call) = 0; + /// Returns false only when no frame was handed to the transmitter, in which case no + /// completion follows. + /// Without a remote_base transmitter, call api_transmit_done_() once the frame is out + virtual bool control(const RadioFrequencyCall &call) = 0; // Traits describing capabilities RadioFrequencyTraits traits_; diff --git a/esphome/components/radon_eye_ble/__init__.py b/esphome/components/radon_eye_ble/__init__.py index 0ca9f643264..e2af9763991 100644 --- a/esphome/components/radon_eye_ble/__init__.py +++ b/esphome/components/radon_eye_ble/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["ble_device_base"] CODEOWNERS = ["@jeffeb3"] +DOMAIN = "radon_eye_ble" radon_eye_ble_ns = cg.esphome_ns.namespace("radon_eye_ble") RadonEyeListener = radon_eye_ble_ns.class_( diff --git a/esphome/components/radon_eye_ble/radon_eye_listener.cpp b/esphome/components/radon_eye_ble/radon_eye_listener.cpp index 9ff279cab9f..bef59757858 100644 --- a/esphome/components/radon_eye_ble/radon_eye_listener.cpp +++ b/esphome/components/radon_eye_ble/radon_eye_listener.cpp @@ -4,7 +4,7 @@ namespace esphome::radon_eye_ble { -static const char *const TAG = "radon_eye_ble"; +ESPHOME_LOG_TAG(TAG, "radon_eye_ble"); bool RadonEyeListener::parse_device(const ble_device_base::ESPBTDevice &device) { // Radon Eye devices have names starting with "FR:" diff --git a/esphome/components/radon_eye_rd200/__init__.py b/esphome/components/radon_eye_rd200/__init__.py index 0740f6967b2..a2076ef9451 100644 --- a/esphome/components/radon_eye_rd200/__init__.py +++ b/esphome/components/radon_eye_rd200/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@jeffeb3"] +DOMAIN = "radon_eye_rd200" diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp index de5bd3d8d58..84b89f377be 100644 --- a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp @@ -7,7 +7,7 @@ namespace esphome::radon_eye_rd200 { -static const char *const TAG = "radon_eye_rd200"; +ESPHOME_LOG_TAG(TAG, "radon_eye_rd200"); static const esp32_ble_tracker::ESPBTUUID SERVICE_UUID_V1 = esp32_ble_tracker::ESPBTUUID::from_raw("00001523-1212-efde-1523-785feabcd123"); diff --git a/esphome/components/rc522/__init__.py b/esphome/components/rc522/__init__.py index e9e8dd7b73c..69cc713813b 100644 --- a/esphome/components/rc522/__init__.py +++ b/esphome/components/rc522/__init__.py @@ -13,6 +13,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@glmnet"] AUTO_LOAD = ["binary_sensor"] +DOMAIN = "rc522" CONF_RC522_ID = "rc522_id" diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp index 7c1b6ae3148..6549ba9846c 100644 --- a/esphome/components/rc522/rc522.cpp +++ b/esphome/components/rc522/rc522.cpp @@ -9,7 +9,7 @@ namespace esphome::rc522 { static const uint8_t WAIT_I_RQ = 0x30; // RxIRq and IdleIRq -static const char *const TAG = "rc522"; +ESPHOME_LOG_TAG(TAG, "rc522"); // Max UID size for RFID tags (4, 7, or 10 bytes) static constexpr size_t RC522_MAX_UID_SIZE = 10; diff --git a/esphome/components/rc522_i2c/__init__.py b/esphome/components/rc522_i2c/__init__.py index c3fd368bbde..3fa9ef8a7b2 100644 --- a/esphome/components/rc522_i2c/__init__.py +++ b/esphome/components/rc522_i2c/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@glmnet"] DEPENDENCIES = ["i2c"] AUTO_LOAD = ["rc522"] +DOMAIN = "rc522_i2c" MULTI_CONF = True rc522_i2c_ns = cg.esphome_ns.namespace("rc522_i2c") diff --git a/esphome/components/rc522_i2c/rc522_i2c.cpp b/esphome/components/rc522_i2c/rc522_i2c.cpp index dbc86ff8e78..7fc04160302 100644 --- a/esphome/components/rc522_i2c/rc522_i2c.cpp +++ b/esphome/components/rc522_i2c/rc522_i2c.cpp @@ -3,7 +3,7 @@ namespace esphome::rc522_i2c { -static const char *const TAG = "rc522_i2c"; +ESPHOME_LOG_TAG(TAG, "rc522_i2c"); void RC522I2C::dump_config() { RC522::dump_config(); diff --git a/esphome/components/rc522_spi/__init__.py b/esphome/components/rc522_spi/__init__.py index f2820bbaee2..6f517458bbf 100644 --- a/esphome/components/rc522_spi/__init__.py +++ b/esphome/components/rc522_spi/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@glmnet"] DEPENDENCIES = ["spi"] AUTO_LOAD = ["rc522"] +DOMAIN = "rc522_spi" MULTI_CONF = True rc522_spi_ns = cg.esphome_ns.namespace("rc522_spi") diff --git a/esphome/components/rc522_spi/rc522_spi.cpp b/esphome/components/rc522_spi/rc522_spi.cpp index b63ad1cfdc1..2d9bbf5b52c 100644 --- a/esphome/components/rc522_spi/rc522_spi.cpp +++ b/esphome/components/rc522_spi/rc522_spi.cpp @@ -7,7 +7,7 @@ namespace esphome::rc522_spi { -static const char *const TAG = "rc522_spi"; +ESPHOME_LOG_TAG(TAG, "rc522_spi"); void RC522Spi::setup() { this->spi_setup(); diff --git a/esphome/components/rd03d/__init__.py b/esphome/components/rd03d/__init__.py index 4fff41e4f63..96bf4ffd743 100644 --- a/esphome/components/rd03d/__init__.py +++ b/esphome/components/rd03d/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@jasstrong"] DEPENDENCIES = ["uart"] +DOMAIN = "rd03d" MULTI_CONF = True CONF_RD03D_ID = "rd03d_id" diff --git a/esphome/components/rd03d/rd03d.cpp b/esphome/components/rd03d/rd03d.cpp index 18328def9ff..7a2af571c78 100644 --- a/esphome/components/rd03d/rd03d.cpp +++ b/esphome/components/rd03d/rd03d.cpp @@ -8,7 +8,7 @@ namespace esphome::rd03d { -static const char *const TAG = "rd03d"; +ESPHOME_LOG_TAG(TAG, "rd03d"); // Delay before sending configuration commands to allow radar to initialize static constexpr uint32_t SETUP_TIMEOUT_MS = 100; diff --git a/esphome/components/rdm6300/__init__.py b/esphome/components/rdm6300/__init__.py index d3803bd3f7f..86eb2cd7380 100644 --- a/esphome/components/rdm6300/__init__.py +++ b/esphome/components/rdm6300/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType DEPENDENCIES = ["uart"] AUTO_LOAD = ["binary_sensor"] +DOMAIN = "rdm6300" MULTI_CONF = True rdm6300_ns = cg.esphome_ns.namespace("rdm6300") diff --git a/esphome/components/rdm6300/rdm6300.cpp b/esphome/components/rdm6300/rdm6300.cpp index 00c91cbc473..2bdbde91748 100644 --- a/esphome/components/rdm6300/rdm6300.cpp +++ b/esphome/components/rdm6300/rdm6300.cpp @@ -4,7 +4,7 @@ namespace esphome::rdm6300 { -static const char *const TAG = "rdm6300"; +ESPHOME_LOG_TAG(TAG, "rdm6300"); static const uint8_t RDM6300_START_BYTE = 0x02; static const uint8_t RDM6300_END_BYTE = 0x03; diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index befef872f0a..a880169fb20 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -51,6 +51,7 @@ from esphome.types import ConfigType, TemplateArgsType from esphome.util import Registry, SimpleRegistry AUTO_LOAD = ["binary_sensor"] +DOMAIN = "remote_base" CONF_RECEIVER_ID = "receiver_id" @@ -136,8 +137,8 @@ async def attach_receiver( add_listener(receiver, var) -async def register_transmittable(var, config): - transmitter_ = await cg.get_variable(config[CONF_TRANSMITTER_ID]) +async def register_transmittable(var, config, key: str = CONF_TRANSMITTER_ID): + transmitter_ = await cg.get_variable(config[key]) cg.add(var.set_transmitter(transmitter_)) @@ -1018,6 +1019,43 @@ async def nec_action(var, config, args): cg.add(var.set_command_repeats(template_)) +# Onkyo RI +OnkyoRIData, OnkyoRIBinarySensor, OnkyoRITrigger, OnkyoRIAction, OnkyoRIDumper = ( + declare_protocol("OnkyoRI") +) +ONKYORI_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_int_range(0, 0xFFF)}) + + +@register_binary_sensor("onkyori", OnkyoRIBinarySensor, ONKYORI_SCHEMA) +def onkyori_binary_sensor(var: MockObj, config: ConfigType) -> None: + cg.add( + var.set_data( + cg.StructInitializer( + OnkyoRIData, + ("data", config[CONF_DATA]), + ) + ) + ) + + +@register_trigger("onkyori", OnkyoRITrigger, OnkyoRIData) +def onkyori_trigger(var: MockObj, config: ConfigType) -> None: + """The trigger takes no options beyond the automation.""" + + +@register_dumper("onkyori", OnkyoRIDumper) +def onkyori_dumper(var: MockObj, config: ConfigType) -> None: + """The dumper takes no options.""" + + +@register_action("onkyori", OnkyoRIAction, ONKYORI_SCHEMA) +async def onkyori_action( + var: MockObj, config: ConfigType, args: TemplateArgsType +) -> None: + template_ = await cg.templatable(config[CONF_DATA], args, cg.uint16) + cg.add(var.set_data(template_)) + + # Pioneer ( PioneerData, @@ -1538,9 +1576,7 @@ def validate_rc_switch_raw_code(value): return value -def build_rc_switch_protocol(config): - if isinstance(config, int): - return rc_switch_protocol(config) +def build_custom_rc_switch_protocol(config: ConfigType) -> MockObj: pl = config[CONF_PULSE_LENGTH] return RCSwitchBase( config[CONF_SYNC][0] * pl, @@ -1553,6 +1589,24 @@ def build_rc_switch_protocol(config): ) +def rc_switch_protocol_in_flash(config: int | ConfigType) -> MockObj: + """Pointer to the protocol in flash: a built-in table entry or a shared custom table.""" + if isinstance(config, int): + return cg.RawExpression(f"&{RC_SWITCH_PROTOCOLS}[{config}]") + return cg.shared_progmem_array( + "rc_switch_custom_protocol", + RCSwitchBase, + [build_custom_rc_switch_protocol(config)], + ) + + +def rc_switch_protocol_value(config: int | ConfigType) -> MockObj: + """RAM copy of a constant protocol for the transmit actions, read from its flash table.""" + if isinstance(config, int): + return rc_switch_protocol(config) + return rc_switch_protocol_copy(rc_switch_protocol_in_flash(config)) + + RC_SWITCH_RAW_SCHEMA = cv.Schema( { cv.Required(CONF_CODE): validate_rc_switch_raw_code, @@ -1628,6 +1682,8 @@ RC_SWITCH_TRANSMITTER = cv.Schema( ) rc_switch_protocol = ns.rc_switch_protocol +rc_switch_protocol_copy = ns.rc_switch_protocol_copy +RC_SWITCH_PROTOCOLS = ns.RC_SWITCH_PROTOCOLS RCSwitchData = ns.struct("RCSwitchData") RCSwitchBase = ns.class_("RCSwitchBase") RCSwitchTrigger = ns.class_("RCSwitchTrigger", RemoteReceiverTrigger) @@ -1642,7 +1698,7 @@ RCSwitchRawReceiver = ns.class_("RCSwitchRawReceiver", RemoteReceiverBinarySenso @register_binary_sensor("rc_switch_raw", RCSwitchRawReceiver, RC_SWITCH_RAW_SCHEMA) def rc_switch_raw_binary_sensor(var, config): - cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL]))) + cg.add(var.set_protocol(rc_switch_protocol_in_flash(config[CONF_PROTOCOL]))) cg.add(var.set_code(config[CONF_CODE])) @@ -1653,7 +1709,7 @@ def rc_switch_raw_binary_sensor(var, config): ) async def rc_switch_raw_action(var, config, args): proto = await cg.templatable( - config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol + config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=rc_switch_protocol_value ) cg.add(var.set_protocol(proto)) cg.add(var.set_code(await cg.templatable(config[CONF_CODE], args, cg.std_string))) @@ -1663,7 +1719,7 @@ async def rc_switch_raw_action(var, config, args): "rc_switch_type_a", RCSwitchRawReceiver, RC_SWITCH_TYPE_A_SCHEMA ) def rc_switch_type_a_binary_sensor(var, config): - cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL]))) + cg.add(var.set_protocol(rc_switch_protocol_in_flash(config[CONF_PROTOCOL]))) cg.add(var.set_type_a(config[CONF_GROUP], config[CONF_DEVICE], config[CONF_STATE])) @@ -1674,7 +1730,7 @@ def rc_switch_type_a_binary_sensor(var, config): ) async def rc_switch_type_a_action(var, config, args): proto = await cg.templatable( - config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol + config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=rc_switch_protocol_value ) cg.add(var.set_protocol(proto)) cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.std_string))) @@ -1688,7 +1744,7 @@ async def rc_switch_type_a_action(var, config, args): "rc_switch_type_b", RCSwitchRawReceiver, RC_SWITCH_TYPE_B_SCHEMA ) def rc_switch_type_b_binary_sensor(var, config): - cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL]))) + cg.add(var.set_protocol(rc_switch_protocol_in_flash(config[CONF_PROTOCOL]))) cg.add( var.set_type_b(config[CONF_ADDRESS], config[CONF_CHANNEL], config[CONF_STATE]) ) @@ -1701,7 +1757,7 @@ def rc_switch_type_b_binary_sensor(var, config): ) async def rc_switch_type_b_action(var, config, args): proto = await cg.templatable( - config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol + config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=rc_switch_protocol_value ) cg.add(var.set_protocol(proto)) cg.add(var.set_address(await cg.templatable(config[CONF_ADDRESS], args, cg.uint8))) @@ -1713,7 +1769,7 @@ async def rc_switch_type_b_action(var, config, args): "rc_switch_type_c", RCSwitchRawReceiver, RC_SWITCH_TYPE_C_SCHEMA ) def rc_switch_type_c_binary_sensor(var, config): - cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL]))) + cg.add(var.set_protocol(rc_switch_protocol_in_flash(config[CONF_PROTOCOL]))) cg.add( var.set_type_c( config[CONF_FAMILY], @@ -1731,7 +1787,7 @@ def rc_switch_type_c_binary_sensor(var, config): ) async def rc_switch_type_c_action(var, config, args): proto = await cg.templatable( - config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol + config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=rc_switch_protocol_value ) cg.add(var.set_protocol(proto)) cg.add( @@ -1748,7 +1804,7 @@ async def rc_switch_type_c_action(var, config, args): RC_SWITCH_TYPE_D_SCHEMA.extend(RC_SWITCH_TRANSMITTER), ) def rc_switch_type_d_binary_sensor(var, config): - cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL]))) + cg.add(var.set_protocol(rc_switch_protocol_in_flash(config[CONF_PROTOCOL]))) cg.add(var.set_type_d(config[CONF_GROUP], config[CONF_DEVICE], config[CONF_STATE])) @@ -1759,7 +1815,7 @@ def rc_switch_type_d_binary_sensor(var, config): ) async def rc_switch_type_d_action(var, config, args): proto = await cg.templatable( - config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol + config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=rc_switch_protocol_value ) cg.add(var.set_protocol(proto)) cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.std_string))) @@ -2042,11 +2098,20 @@ def midea_dumper(var, config): pass +async def _code_bytes( + value: Any, args: TemplateArgsType, set_template: MockObj, set_static: MockObj +) -> None: + """Constant codes share one PROGMEM table pool across the remote protocols.""" + await automation.templatable_bytes( + value, args, set_template, set_static, "remote_base_code" + ) + + @register_action("midea", MideaAction, MIDEA_SCHEMA) async def midea_action(var, config, args): - vec_ = cg.std_vector.template(cg.uint8) - template_ = await cg.templatable(config[CONF_CODE], args, vec_, vec_) - cg.add(var.set_code(template_)) + await _code_bytes( + config[CONF_CODE], args, var.set_code_template, var.set_code_static + ) # AEHA @@ -2098,10 +2163,9 @@ def aeha_dumper(var, config): async def aeha_action(var, config, args): template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16) cg.add(var.set_address(template_)) - template_ = await cg.templatable( - config[CONF_DATA], args, cg.std_vector.template(cg.uint8) + await _code_bytes( + config[CONF_DATA], args, var.set_data_template, var.set_data_static ) - cg.add(var.set_data(template_)) templ = await cg.templatable(config[CONF_CARRIER_FREQUENCY], args, cg.uint32) cg.add(var.set_carrier_frequency(templ)) @@ -2197,9 +2261,9 @@ def haier_dumper(var, config): @register_action("haier", HaierAction, HAIER_SCHEMA) async def haier_action(var, config, args): - vec_ = cg.std_vector.template(cg.uint8) - template_ = await cg.templatable(config[CONF_CODE], args, vec_, vec_) - cg.add(var.set_code(template_)) + await _code_bytes( + config[CONF_CODE], args, var.set_code_template, var.set_code_static + ) # ABBWelcome @@ -2340,9 +2404,9 @@ def mirage_dumper(var, config): @register_action("mirage", MirageAction, MIRAGE_SCHEMA) async def mirage_action(var, config, args): - vec_ = cg.std_vector.template(cg.uint8) - template_ = await cg.templatable(config[CONF_CODE], args, vec_, vec_) - cg.add(var.set_code(template_)) + await _code_bytes( + config[CONF_CODE], args, var.set_code_template, var.set_code_static + ) # Toto diff --git a/esphome/components/remote_base/abbwelcome_protocol.cpp b/esphome/components/remote_base/abbwelcome_protocol.cpp index 2000148ca8f..638f33687a5 100644 --- a/esphome/components/remote_base/abbwelcome_protocol.cpp +++ b/esphome/components/remote_base/abbwelcome_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.abbwelcome"; +ESPHOME_LOG_TAG(TAG, "remote.abbwelcome"); static constexpr uint32_t BIT_ONE_SPACE_US = 102; static constexpr uint32_t BIT_ZERO_MARK_US = 32; // 18-44 diff --git a/esphome/components/remote_base/aeha_protocol.cpp b/esphome/components/remote_base/aeha_protocol.cpp index 69f91ba90e4..eb05f7d21e8 100644 --- a/esphome/components/remote_base/aeha_protocol.cpp +++ b/esphome/components/remote_base/aeha_protocol.cpp @@ -4,7 +4,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.aeha"; +ESPHOME_LOG_TAG(TAG, "remote.aeha"); static constexpr uint16_t BITWISE = 425; static constexpr uint16_t HEADER_HIGH_US = BITWISE * 8; diff --git a/esphome/components/remote_base/aeha_protocol.h b/esphome/components/remote_base/aeha_protocol.h index 98a55011552..57706a7e4f1 100644 --- a/esphome/components/remote_base/aeha_protocol.h +++ b/esphome/components/remote_base/aeha_protocol.h @@ -28,10 +28,9 @@ DECLARE_REMOTE_PROTOCOL(AEHA) template class AEHAAction : public RemoteTransmitterActionBase { public: TEMPLATABLE_VALUE(uint16_t, address) - TEMPLATABLE_VALUE(std::vector, data) + TEMPLATABLE_BYTES(data) TEMPLATABLE_VALUE(uint32_t, carrier_frequency); - void set_data(const std::vector &data) { data_ = data; } void encode(RemoteTransmitData *dst, Ts... x) override { AEHAData data{}; data.address = this->address_.value(x...); diff --git a/esphome/components/remote_base/beo4_protocol.cpp b/esphome/components/remote_base/beo4_protocol.cpp index 9291501ea3d..e79e05d8393 100644 --- a/esphome/components/remote_base/beo4_protocol.cpp +++ b/esphome/components/remote_base/beo4_protocol.cpp @@ -5,7 +5,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.beo4"; +ESPHOME_LOG_TAG(TAG, "remote.beo4"); // beo4 pulse width, high=carrier_pulse low=data_pulse constexpr uint16_t PW_CARR_US = 200; // carrier pulse length diff --git a/esphome/components/remote_base/brennenstuhl_protocol.cpp b/esphome/components/remote_base/brennenstuhl_protocol.cpp index 35261dc00a0..2537ba55603 100644 --- a/esphome/components/remote_base/brennenstuhl_protocol.cpp +++ b/esphome/components/remote_base/brennenstuhl_protocol.cpp @@ -5,7 +5,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.brennenstuhl"; +ESPHOME_LOG_TAG(TAG, "remote.brennenstuhl"); // receiver timing ranges [µs] constexpr uint32_t START_PULSE_MIN = 200; diff --git a/esphome/components/remote_base/byronsx_protocol.cpp b/esphome/components/remote_base/byronsx_protocol.cpp index 11d6d975d14..468b11f6956 100644 --- a/esphome/components/remote_base/byronsx_protocol.cpp +++ b/esphome/components/remote_base/byronsx_protocol.cpp @@ -5,7 +5,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.byronsx"; +ESPHOME_LOG_TAG(TAG, "remote.byronsx"); static constexpr uint32_t BIT_TIME_US = 333; static constexpr uint8_t NBITS_ADDRESS = 8; diff --git a/esphome/components/remote_base/canalsat_protocol.cpp b/esphome/components/remote_base/canalsat_protocol.cpp index eafa98ebcc4..ac83c372275 100644 --- a/esphome/components/remote_base/canalsat_protocol.cpp +++ b/esphome/components/remote_base/canalsat_protocol.cpp @@ -3,8 +3,8 @@ namespace esphome::remote_base { -static const char *const CANALSAT_TAG = "remote.canalsat"; -static const char *const CANALSATLD_TAG = "remote.canalsatld"; +ESPHOME_LOG_TAG(CANALSAT_TAG, "remote.canalsat"); +ESPHOME_LOG_TAG(CANALSATLD_TAG, "remote.canalsatld"); static constexpr uint16_t CANALSAT_FREQ = 55500; static constexpr uint16_t CANALSATLD_FREQ = 56000; diff --git a/esphome/components/remote_base/coolix_protocol.cpp b/esphome/components/remote_base/coolix_protocol.cpp index 53e1d59f135..e2e54afadb6 100644 --- a/esphome/components/remote_base/coolix_protocol.cpp +++ b/esphome/components/remote_base/coolix_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.coolix"; +ESPHOME_LOG_TAG(TAG, "remote.coolix"); static const int32_t TICK_US = 560; static const int32_t HEADER_MARK_US = 8 * TICK_US; diff --git a/esphome/components/remote_base/dish_protocol.cpp b/esphome/components/remote_base/dish_protocol.cpp index 9a6420afd56..6877d661bdd 100644 --- a/esphome/components/remote_base/dish_protocol.cpp +++ b/esphome/components/remote_base/dish_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.dish"; +ESPHOME_LOG_TAG(TAG, "remote.dish"); static constexpr uint32_t HEADER_HIGH_US = 400; static constexpr uint32_t HEADER_LOW_US = 6100; @@ -20,7 +20,7 @@ void DishProtocol::encode(RemoteTransmitData *dst, const DishData &data) { // Typically a DISH device needs to get a command a total of // at least 4 times to accept it. - for (uint i = 0; i < 4; i++) { + for (uint32_t i = 0; i < 4; i++) { // COMMAND (function, in MSB) for (uint8_t mask = 1UL << 5; mask; mask >>= 1) { if (data.command & mask) { @@ -39,7 +39,7 @@ void DishProtocol::encode(RemoteTransmitData *dst, const DishData &data) { } } // PADDING - for (uint j = 0; j < 6; j++) + for (uint32_t j = 0; j < 6; j++) dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); // FOOTER @@ -73,7 +73,7 @@ optional DishProtocol::decode(RemoteReceiveData src) { return {}; } } - for (uint j = 0; j < 6; j++) { + for (uint32_t j = 0; j < 6; j++) { if (!src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { return {}; } diff --git a/esphome/components/remote_base/dooya_protocol.cpp b/esphome/components/remote_base/dooya_protocol.cpp index 6dc7926b4a5..5291d69345c 100644 --- a/esphome/components/remote_base/dooya_protocol.cpp +++ b/esphome/components/remote_base/dooya_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.dooya"; +ESPHOME_LOG_TAG(TAG, "remote.dooya"); static constexpr uint32_t HEADER_HIGH_US = 5000; static constexpr uint32_t HEADER_LOW_US = 1500; diff --git a/esphome/components/remote_base/drayton_protocol.cpp b/esphome/components/remote_base/drayton_protocol.cpp index 2261bd04e99..598f1a476ef 100644 --- a/esphome/components/remote_base/drayton_protocol.cpp +++ b/esphome/components/remote_base/drayton_protocol.cpp @@ -5,7 +5,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.drayton"; +ESPHOME_LOG_TAG(TAG, "remote.drayton"); static constexpr uint32_t BIT_TIME_US = 500; static constexpr uint8_t CARRIER_KHZ = 2; diff --git a/esphome/components/remote_base/dyson_protocol.cpp b/esphome/components/remote_base/dyson_protocol.cpp index 942b40d26fc..f6a7943ebd4 100644 --- a/esphome/components/remote_base/dyson_protocol.cpp +++ b/esphome/components/remote_base/dyson_protocol.cpp @@ -5,7 +5,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.dyson"; +ESPHOME_LOG_TAG(TAG, "remote.dyson"); // pulsewidth [µs] constexpr uint32_t PW_MARK_US = 780; diff --git a/esphome/components/remote_base/gobox_protocol.cpp b/esphome/components/remote_base/gobox_protocol.cpp index 1d67be86b86..affafa694ca 100644 --- a/esphome/components/remote_base/gobox_protocol.cpp +++ b/esphome/components/remote_base/gobox_protocol.cpp @@ -4,7 +4,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.gobox"; +ESPHOME_LOG_TAG(TAG, "remote.gobox"); constexpr uint32_t BIT_MARK_US = 580; // 70us seems like a safe time delta for the receiver... constexpr uint32_t BIT_ONE_SPACE_US = 1640; diff --git a/esphome/components/remote_base/haier_protocol.cpp b/esphome/components/remote_base/haier_protocol.cpp index 8801f1049af..3c9ba7b61f0 100644 --- a/esphome/components/remote_base/haier_protocol.cpp +++ b/esphome/components/remote_base/haier_protocol.cpp @@ -4,7 +4,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.haier"; +ESPHOME_LOG_TAG(TAG, "remote.haier"); constexpr uint32_t HEADER_LOW_US = 3100; constexpr uint32_t HEADER_HIGH_US = 4400; diff --git a/esphome/components/remote_base/haier_protocol.h b/esphome/components/remote_base/haier_protocol.h index e1fd60411fc..90af88d2c78 100644 --- a/esphome/components/remote_base/haier_protocol.h +++ b/esphome/components/remote_base/haier_protocol.h @@ -25,7 +25,7 @@ DECLARE_REMOTE_PROTOCOL(Haier) template class HaierAction : public RemoteTransmitterActionBase { public: - TEMPLATABLE_VALUE(std::vector, code) + TEMPLATABLE_BYTES(code) void encode(RemoteTransmitData *dst, Ts... x) override { HaierData data{}; diff --git a/esphome/components/remote_base/hob2hood_protocol.cpp b/esphome/components/remote_base/hob2hood_protocol.cpp index 9e706b9fe66..95683ba8cc0 100644 --- a/esphome/components/remote_base/hob2hood_protocol.cpp +++ b/esphome/components/remote_base/hob2hood_protocol.cpp @@ -7,7 +7,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.hob2hood"; +ESPHOME_LOG_TAG(TAG, "remote.hob2hood"); // A frame is 25 bits: a leading 0, then the command byte, command + 1 and command + 2. Zero bits are marks // and one bits are spaces; equal neighbours merge into one run of n * BIT_TIME_US plus a fixed adjustment. diff --git a/esphome/components/remote_base/jvc_protocol.cpp b/esphome/components/remote_base/jvc_protocol.cpp index 86a47e757de..6fbbf6ee477 100644 --- a/esphome/components/remote_base/jvc_protocol.cpp +++ b/esphome/components/remote_base/jvc_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.jvc"; +ESPHOME_LOG_TAG(TAG, "remote.jvc"); static constexpr uint8_t NBITS = 16; static constexpr uint32_t HEADER_HIGH_US = 8400; diff --git a/esphome/components/remote_base/keeloq_protocol.cpp b/esphome/components/remote_base/keeloq_protocol.cpp index 8cc9de94b8d..caa950218ec 100644 --- a/esphome/components/remote_base/keeloq_protocol.cpp +++ b/esphome/components/remote_base/keeloq_protocol.cpp @@ -5,7 +5,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.keeloq"; +ESPHOME_LOG_TAG(TAG, "remote.keeloq"); static constexpr uint32_t BIT_TIME_US = 380; static constexpr uint8_t NBITS_PREAMBLE = 12; diff --git a/esphome/components/remote_base/lg_protocol.cpp b/esphome/components/remote_base/lg_protocol.cpp index e450659b42f..07f4b5a1dca 100644 --- a/esphome/components/remote_base/lg_protocol.cpp +++ b/esphome/components/remote_base/lg_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.lg"; +ESPHOME_LOG_TAG(TAG, "remote.lg"); static constexpr uint32_t HEADER_HIGH_US = 8000; static constexpr uint32_t HEADER_LOW_US = 4000; diff --git a/esphome/components/remote_base/magiquest_protocol.cpp b/esphome/components/remote_base/magiquest_protocol.cpp index f417827b389..5b47f7e438f 100644 --- a/esphome/components/remote_base/magiquest_protocol.cpp +++ b/esphome/components/remote_base/magiquest_protocol.cpp @@ -7,7 +7,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.magiquest"; +ESPHOME_LOG_TAG(TAG, "remote.magiquest"); static constexpr uint32_t MAGIQUEST_UNIT = 288; // us static constexpr uint32_t MAGIQUEST_ONE_MARK = 2 * MAGIQUEST_UNIT; diff --git a/esphome/components/remote_base/midea_protocol.cpp b/esphome/components/remote_base/midea_protocol.cpp index 6889c5d9b4f..57aeda2e90a 100644 --- a/esphome/components/remote_base/midea_protocol.cpp +++ b/esphome/components/remote_base/midea_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.midea"; +ESPHOME_LOG_TAG(TAG, "remote.midea"); static const int32_t TICK_US = 560; static const int32_t HEADER_MARK_US = 8 * TICK_US; diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index 85bbef1cb1f..19521e8f6d2 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -4,6 +4,7 @@ #include #include "esphome/core/component.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "remote_base.h" @@ -21,6 +22,9 @@ class MideaData { MideaData(const std::vector &data) { std::copy_n(data.begin(), std::min(data.size(), this->data_.size()), this->data_.begin()); } + MideaData(const uint8_t *data, size_t len) { + progmem_memcpy(this->data_.data(), data, std::min(len, this->data_.size())); + } uint8_t *data() { return this->data_.data(); } const uint8_t *data() const { return this->data_.data(); } @@ -75,10 +79,12 @@ class MideaProtocol : public RemoteProtocol { DECLARE_REMOTE_PROTOCOL(Midea) template class MideaAction : public RemoteTransmitterActionBase { - TEMPLATABLE_VALUE(std::vector, code) + public: + TEMPLATABLE_BYTES(code) void encode(RemoteTransmitData *dst, Ts... x) override { - MideaData data(this->code_.value(x...)); + MideaData data = this->code_.is_static() ? MideaData(this->code_.data(), this->code_.size()) + : MideaData(this->code_.value(x...)); data.finalize(); MideaProtocol().encode(dst, data); } diff --git a/esphome/components/remote_base/mirage_protocol.cpp b/esphome/components/remote_base/mirage_protocol.cpp index 380cfaecb26..c2a487080bc 100644 --- a/esphome/components/remote_base/mirage_protocol.cpp +++ b/esphome/components/remote_base/mirage_protocol.cpp @@ -4,7 +4,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.mirage"; +ESPHOME_LOG_TAG(TAG, "remote.mirage"); constexpr uint32_t HEADER_MARK_US = 8360; constexpr uint32_t HEADER_SPACE_US = 4248; diff --git a/esphome/components/remote_base/mirage_protocol.h b/esphome/components/remote_base/mirage_protocol.h index a37fb93f4fd..958127ba664 100644 --- a/esphome/components/remote_base/mirage_protocol.h +++ b/esphome/components/remote_base/mirage_protocol.h @@ -25,7 +25,7 @@ DECLARE_REMOTE_PROTOCOL(Mirage) template class MirageAction : public RemoteTransmitterActionBase { public: - TEMPLATABLE_VALUE(std::vector, code) + TEMPLATABLE_BYTES(code) void encode(RemoteTransmitData *dst, Ts... x) override { MirageData data{}; diff --git a/esphome/components/remote_base/nec_protocol.cpp b/esphome/components/remote_base/nec_protocol.cpp index e639248b4e4..3edb50141c4 100644 --- a/esphome/components/remote_base/nec_protocol.cpp +++ b/esphome/components/remote_base/nec_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.nec"; +ESPHOME_LOG_TAG(TAG, "remote.nec"); static constexpr uint32_t HEADER_HIGH_US = 9000; static constexpr uint32_t HEADER_LOW_US = 4500; diff --git a/esphome/components/remote_base/nexa_protocol.cpp b/esphome/components/remote_base/nexa_protocol.cpp index e691a695987..2d0d2cf7579 100644 --- a/esphome/components/remote_base/nexa_protocol.cpp +++ b/esphome/components/remote_base/nexa_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.nexa"; +ESPHOME_LOG_TAG(TAG, "remote.nexa"); static constexpr uint8_t NBITS = 32; static constexpr uint32_t HEADER_HIGH_US = 319; diff --git a/esphome/components/remote_base/onkyori_protocol.cpp b/esphome/components/remote_base/onkyori_protocol.cpp new file mode 100644 index 00000000000..6290e5bc507 --- /dev/null +++ b/esphome/components/remote_base/onkyori_protocol.cpp @@ -0,0 +1,59 @@ +#include "onkyori_protocol.h" +#include "esphome/core/log.h" + +namespace esphome::remote_base { + +ESPHOME_LOG_TAG(TAG, "remote.onkyori"); + +static constexpr uint8_t NBITS = 12; +static constexpr uint16_t MAX_DATA = (1U << NBITS) - 1; +static constexpr uint32_t HEADER_HIGH_US = 3000; +static constexpr uint32_t HEADER_LOW_US = 1000; +static constexpr uint32_t BIT_HIGH_US = 1000; +static constexpr uint32_t BIT_ONE_LOW_US = 2000; +static constexpr uint32_t BIT_ZERO_LOW_US = 1000; +static constexpr uint32_t TRAILER_HIGH_US = 1000; + +void OnkyoRIProtocol::encode(RemoteTransmitData *dst, const OnkyoRIData &data) { + if (data.data > MAX_DATA) { + ESP_LOGE(TAG, "Data 0x%04X does not fit in a 12-bit RI frame, not sending", data.data); + return; + } + + // RI is a baseband signal on a wire, so it has no carrier to modulate. + dst->set_carrier_frequency(0); + dst->reserve(2 + NBITS * 2u + 1); + + dst->item(HEADER_HIGH_US, HEADER_LOW_US); + + for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) { + dst->item(BIT_HIGH_US, (data.data & mask) != 0 ? BIT_ONE_LOW_US : BIT_ZERO_LOW_US); + } + + dst->mark(TRAILER_HIGH_US); +} + +optional OnkyoRIProtocol::decode(RemoteReceiveData src) { + OnkyoRIData out{.data = 0}; + + if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) + return {}; + + for (uint8_t i = 0; i < NBITS; i++) { + out.data <<= 1; + if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) { + out.data |= 1; + } else if (!src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) { + return {}; + } + } + + if (!src.expect_mark(TRAILER_HIGH_US)) + return {}; + + return out; +} + +void OnkyoRIProtocol::dump(const OnkyoRIData &data) { ESP_LOGI(TAG, "Received OnkyoRI: data=0x%03X", data.data); } + +} // namespace esphome::remote_base diff --git a/esphome/components/remote_base/onkyori_protocol.h b/esphome/components/remote_base/onkyori_protocol.h new file mode 100644 index 00000000000..c6f0f1dd97f --- /dev/null +++ b/esphome/components/remote_base/onkyori_protocol.h @@ -0,0 +1,33 @@ +#pragma once + +#include "remote_base.h" + +namespace esphome::remote_base { + +struct OnkyoRIData { + uint16_t data; + + bool operator==(const OnkyoRIData &rhs) const { return data == rhs.data; } +}; + +class OnkyoRIProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const OnkyoRIData &data); + optional decode(RemoteReceiveData src); + void dump(const OnkyoRIData &data); +}; + +DECLARE_REMOTE_PROTOCOL(OnkyoRI) + +template class OnkyoRIAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint16_t, data) + + void encode(RemoteTransmitData *dst, Ts... x) override { + OnkyoRIData data{}; + data.data = this->data_.value(x...); + OnkyoRIProtocol().encode(dst, data); + } +}; + +} // namespace esphome::remote_base diff --git a/esphome/components/remote_base/panasonic_protocol.cpp b/esphome/components/remote_base/panasonic_protocol.cpp index b0262130804..c2fc35d3ec9 100644 --- a/esphome/components/remote_base/panasonic_protocol.cpp +++ b/esphome/components/remote_base/panasonic_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.panasonic"; +ESPHOME_LOG_TAG(TAG, "remote.panasonic"); static constexpr uint32_t HEADER_HIGH_US = 3502; static constexpr uint32_t HEADER_LOW_US = 1750; diff --git a/esphome/components/remote_base/pioneer_protocol.cpp b/esphome/components/remote_base/pioneer_protocol.cpp index f4d6aa4026c..20603848f1f 100644 --- a/esphome/components/remote_base/pioneer_protocol.cpp +++ b/esphome/components/remote_base/pioneer_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.pioneer"; +ESPHOME_LOG_TAG(TAG, "remote.pioneer"); static constexpr uint32_t HEADER_HIGH_US = 9000; static constexpr uint32_t HEADER_LOW_US = 4500; diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp index dc128d4622d..e47ba892917 100644 --- a/esphome/components/remote_base/pronto_protocol.cpp +++ b/esphome/components/remote_base/pronto_protocol.cpp @@ -37,7 +37,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.pronto"; +ESPHOME_LOG_TAG(TAG, "remote.pronto"); bool ProntoData::operator==(const ProntoData &rhs) const { std::vector data1 = encode_pronto(data); diff --git a/esphome/components/remote_base/raw_protocol.cpp b/esphome/components/remote_base/raw_protocol.cpp index 02c29168496..134204aa2a0 100644 --- a/esphome/components/remote_base/raw_protocol.cpp +++ b/esphome/components/remote_base/raw_protocol.cpp @@ -4,7 +4,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.raw"; +ESPHOME_LOG_TAG(TAG, "remote.raw"); bool RawDumper::dump(RemoteReceiveData src) { char buffer[256]; diff --git a/esphome/components/remote_base/rc5_protocol.cpp b/esphome/components/remote_base/rc5_protocol.cpp index fd136a4e6d9..cec747eb480 100644 --- a/esphome/components/remote_base/rc5_protocol.cpp +++ b/esphome/components/remote_base/rc5_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.rc5"; +ESPHOME_LOG_TAG(TAG, "remote.rc5"); static constexpr uint32_t BIT_TIME_US = 889; static constexpr uint8_t NBITS = 14; diff --git a/esphome/components/remote_base/rc6_protocol.cpp b/esphome/components/remote_base/rc6_protocol.cpp index ddd9e532580..067b8b812e4 100644 --- a/esphome/components/remote_base/rc6_protocol.cpp +++ b/esphome/components/remote_base/rc6_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const RC6_TAG = "remote.rc6"; +ESPHOME_LOG_TAG(RC6_TAG, "remote.rc6"); static constexpr uint16_t RC6_FREQ = 36000; static constexpr uint16_t RC6_UNIT = 444; diff --git a/esphome/components/remote_base/rc_switch_protocol.cpp b/esphome/components/remote_base/rc_switch_protocol.cpp index de16c55cb02..a469cf2d454 100644 --- a/esphome/components/remote_base/rc_switch_protocol.cpp +++ b/esphome/components/remote_base/rc_switch_protocol.cpp @@ -6,15 +6,19 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.rc_switch"; +ESPHOME_LOG_TAG(TAG, "remote.rc_switch"); RCSwitchBase rc_switch_protocol(uint8_t index) { - RCSwitchBase protocol; // entry 0 is the all-zero protocol, so an out of range index from a lambda transmits nothing if (index >= std::size(RC_SWITCH_PROTOCOLS)) index = 0; - progmem_memcpy(&protocol, &RC_SWITCH_PROTOCOLS[index], sizeof(protocol)); - return protocol; + return rc_switch_protocol_copy(&RC_SWITCH_PROTOCOLS[index]); +} + +RCSwitchBase rc_switch_protocol_copy(const RCSwitchBase *protocol) { + RCSwitchBase copy; + progmem_memcpy(©, protocol, sizeof(copy)); + return copy; } void RCSwitchBase::one(RemoteTransmitData *dst) const { @@ -232,7 +236,7 @@ uint64_t decode_binary_string_mask(const std::string &data) { bool RCSwitchRawReceiver::matches(RemoteReceiveData src) { uint64_t decoded_code; uint8_t decoded_nbits; - if (!this->protocol_.decode(src, &decoded_code, &decoded_nbits)) + if (!this->protocol_->decode(src, &decoded_code, &decoded_nbits)) return false; return decoded_nbits == this->nbits_ && (decoded_code & this->mask_) == (this->code_ & this->mask_); diff --git a/esphome/components/remote_base/rc_switch_protocol.h b/esphome/components/remote_base/rc_switch_protocol.h index 9ccea4d15a5..a102da3ff67 100644 --- a/esphome/components/remote_base/rc_switch_protocol.h +++ b/esphome/components/remote_base/rc_switch_protocol.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "esphome/core/component.h" #include "remote_base.h" @@ -66,6 +68,9 @@ class RCSwitchBase { uint32_t one_high_{}; uint32_t one_low_{}; uint32_t inverted_{}; // bool widened so every field is a word: the table is read from flash + + // A bool here would still pad to 28 bytes, so the size check below alone would not catch it. + static_assert(std::is_same_v, "inverted_ must stay a word for flash reads"); }; // Constant-initialized and kept in flash on every platform. The decoder reads entries in place @@ -87,6 +92,9 @@ inline constexpr RCSwitchBase RC_SWITCH_PROTOCOLS[] PROGMEM = { /// RAM copy of RC_SWITCH_PROTOCOLS[index] (0 when out of range) for the transmit actions and the dumper, made with /// progmem_memcpy so no byte load ever touches the flash table on ESP8266 RCSwitchBase rc_switch_protocol(uint8_t index); +/// RAM copy of a protocol stored in flash, made with progmem_memcpy (own name: `rc_switch_protocol(0)` stays +/// unambiguous) +RCSwitchBase rc_switch_protocol_copy(const RCSwitchBase *protocol); uint64_t decode_binary_string(const std::string &data); @@ -200,7 +208,8 @@ template class RCSwitchTypeDAction : public RemoteTransmitterAct class RCSwitchRawReceiver : public RemoteReceiverBinarySensorBase { public: - void set_protocol(const RCSwitchBase &a_protocol) { this->protocol_ = a_protocol; } + /// `protocol` must outlive the receiver: a RC_SWITCH_PROTOCOLS entry or a codegen flash table. + void set_protocol(const RCSwitchBase *protocol) { this->protocol_ = protocol; } void set_code(uint64_t code) { this->code_ = code; } void set_code(const std::string &code) { this->code_ = decode_binary_string(code); @@ -228,7 +237,7 @@ class RCSwitchRawReceiver : public RemoteReceiverBinarySensorBase { protected: bool matches(RemoteReceiveData src) override; - RCSwitchBase protocol_; + const RCSwitchBase *protocol_{nullptr}; // in flash; decoded in place (word-only fields) uint64_t code_; uint64_t mask_{0xFFFFFFFFFFFFFFFF}; uint8_t nbits_; diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index 5d1bba16b61..7205fb6221a 100644 --- a/esphome/components/remote_base/remote_base.cpp +++ b/esphome/components/remote_base/remote_base.cpp @@ -4,7 +4,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote_base"; +ESPHOME_LOG_TAG(TAG, "remote_base"); /* RemoteReceiveData */ @@ -182,7 +182,7 @@ bool RemoteTransmitData::set_data_from_base64url(const std::string &base64url) { /* RemoteTransmitterBase */ -void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) { +void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait, [[maybe_unused]] uint16_t seq) { #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE const auto &vec = this->temp_.get_data(); char buffer[256]; @@ -213,6 +213,10 @@ void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) { if (pos != 0) { ESP_LOGVV(TAG, "%s", buffer); } +#endif + this->flush_pending_completion(); +#ifdef USE_IR_RF_TRANSMIT_COMPLETE + this->current_seq_ = seq; #endif this->send_internal(send_times, send_wait); } diff --git a/esphome/components/remote_base/remote_base.h b/esphome/components/remote_base/remote_base.h index 67e5799bcaf..36d2a44c509 100644 --- a/esphome/components/remote_base/remote_base.h +++ b/esphome/components/remote_base/remote_base.h @@ -143,6 +143,14 @@ class RemoteRMTChannel { #endif // SOC_RMT_SUPPORTED #endif // USE_ESP32 +class RemoteTransmitterBase; + +#ifdef USE_IR_RF_TRANSMIT_COMPLETE +/// Defined by ir_rf_base: answers the API request waiting on the entity that submitted seq; +/// sent is false when the platform never put the frame on the wire. +/// One function for the whole build instead of a callback list on every transmitter. +void ir_rf_transmit_complete(RemoteTransmitterBase *transmitter, uint16_t seq, bool sent); +#endif // Protocol shapes, checked where a protocol is used so a missing method fails at the use site // instead of deep inside a template body. Receive-only protocols such as RCSwitchBase decode // without encoding. @@ -164,21 +172,24 @@ class RemoteTransmitterBase : public RemoteComponentBase { RemoteTransmitterBase(InternalGPIOPin *pin) : RemoteComponentBase(pin) {} class TransmitCall { public: - explicit TransmitCall(RemoteTransmitterBase *parent) : parent_(parent) {} + TransmitCall(RemoteTransmitterBase *parent, uint16_t seq) : parent_(parent), seq_(seq) {} RemoteTransmitData *get_data() { return &this->parent_->temp_; } void set_send_times(uint32_t send_times) { send_times_ = send_times; } void set_send_wait(uint32_t send_wait) { send_wait_ = send_wait; } - void perform() { this->parent_->send_(this->send_times_, this->send_wait_); } + /// Identifies this transmission in the completion hook + uint16_t get_seq() const { return this->seq_; } + void perform() { this->parent_->send_(this->send_times_, this->send_wait_, this->seq_); } protected: RemoteTransmitterBase *parent_; uint32_t send_times_{1}; uint32_t send_wait_{0}; + uint16_t seq_; }; TransmitCall transmit() { this->temp_.reset(); - return TransmitCall(this); + return TransmitCall(this, this->take_seq_()); } template void transmit(const typename Protocol::ProtocolData &data, uint32_t send_times = 1, uint32_t send_wait = 0) { @@ -190,9 +201,25 @@ class RemoteTransmitterBase : public RemoteComponentBase { } protected: - void send_(uint32_t send_times, uint32_t send_wait); + void send_(uint32_t send_times, uint32_t send_wait, uint16_t seq); virtual void send_internal(uint32_t send_times, uint32_t send_wait) = 0; - void send_single_() { this->send_(1, 0); } + /// Platforms that report completion later wait out the previous frame here, before send_() + /// assigns the next seq, so that completion carries the seq it belongs to + virtual void flush_pending_completion() {} + void send_single_() { this->send_(1, 0, this->take_seq_()); } +#ifdef USE_IR_RF_TRANSMIT_COMPLETE + /// Reports the frame handed to the platform last, after its final repeat and before the + /// on_complete trigger; a seq only has to be unique within the 30 s reply window + void notify_complete_(bool sent) { ir_rf_transmit_complete(this, this->current_seq_, sent); } + uint16_t take_seq_() { return ++this->next_seq_; } + + uint16_t next_seq_{0}; + uint16_t current_seq_{0}; +#else + // seq tracking only exists for the API completion reply + void notify_complete_(bool /*sent*/) {} + static uint16_t take_seq_() { return 0; } +#endif /// Use same vector for all transmits, avoids many allocations RemoteTransmitData temp_; diff --git a/esphome/components/remote_base/roomba_protocol.cpp b/esphome/components/remote_base/roomba_protocol.cpp index 8053792a60c..bd40a7a5448 100644 --- a/esphome/components/remote_base/roomba_protocol.cpp +++ b/esphome/components/remote_base/roomba_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.roomba"; +ESPHOME_LOG_TAG(TAG, "remote.roomba"); static constexpr uint8_t NBITS = 8; static constexpr uint32_t BIT_ONE_HIGH_US = 3000; diff --git a/esphome/components/remote_base/samsung36_protocol.cpp b/esphome/components/remote_base/samsung36_protocol.cpp index ded8c71aa38..c59ccf0106f 100644 --- a/esphome/components/remote_base/samsung36_protocol.cpp +++ b/esphome/components/remote_base/samsung36_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.samsung36"; +ESPHOME_LOG_TAG(TAG, "remote.samsung36"); static constexpr uint8_t NBITS = 78; diff --git a/esphome/components/remote_base/samsung_protocol.cpp b/esphome/components/remote_base/samsung_protocol.cpp index 7190e974038..7fce46313b8 100644 --- a/esphome/components/remote_base/samsung_protocol.cpp +++ b/esphome/components/remote_base/samsung_protocol.cpp @@ -4,7 +4,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.samsung"; +ESPHOME_LOG_TAG(TAG, "remote.samsung"); static constexpr uint32_t HEADER_HIGH_US = 4500; static constexpr uint32_t HEADER_LOW_US = 4500; diff --git a/esphome/components/remote_base/sony_protocol.cpp b/esphome/components/remote_base/sony_protocol.cpp index 0abb7fc0e02..00d5aee9c8c 100644 --- a/esphome/components/remote_base/sony_protocol.cpp +++ b/esphome/components/remote_base/sony_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.sony"; +ESPHOME_LOG_TAG(TAG, "remote.sony"); static constexpr uint32_t HEADER_HIGH_US = 2400; static constexpr uint32_t HEADER_LOW_US = 600; diff --git a/esphome/components/remote_base/symphony_protocol.cpp b/esphome/components/remote_base/symphony_protocol.cpp index 8b39abf9ad9..962cf12ff68 100644 --- a/esphome/components/remote_base/symphony_protocol.cpp +++ b/esphome/components/remote_base/symphony_protocol.cpp @@ -5,7 +5,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.symphony"; +ESPHOME_LOG_TAG(TAG, "remote.symphony"); // Reference implementation and timing details: // IRremoteESP8266 ir_Symphony.cpp diff --git a/esphome/components/remote_base/toshiba_ac_protocol.cpp b/esphome/components/remote_base/toshiba_ac_protocol.cpp index 077b4340fa6..addcd6f9cbf 100644 --- a/esphome/components/remote_base/toshiba_ac_protocol.cpp +++ b/esphome/components/remote_base/toshiba_ac_protocol.cpp @@ -4,7 +4,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.toshibaac"; +ESPHOME_LOG_TAG(TAG, "remote.toshibaac"); static constexpr uint32_t HEADER_HIGH_US = 4500; static constexpr uint32_t HEADER_LOW_US = 4500; diff --git a/esphome/components/remote_base/toto_protocol.cpp b/esphome/components/remote_base/toto_protocol.cpp index 042efcbc363..c94fce87be0 100644 --- a/esphome/components/remote_base/toto_protocol.cpp +++ b/esphome/components/remote_base/toto_protocol.cpp @@ -3,7 +3,7 @@ namespace esphome::remote_base { -static const char *const TAG = "remote.toto"; +ESPHOME_LOG_TAG(TAG, "remote.toto"); static constexpr uint32_t PREAMBLE_HIGH_US = 6200; static constexpr uint32_t PREAMBLE_LOW_US = 2800; diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index 866e108131e..481acaa06c9 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -29,6 +29,7 @@ CONF_FILTER_SYMBOLS = "filter_symbols" CONF_RECEIVE_SYMBOLS = "receive_symbols" AUTO_LOAD = ["remote_base"] +DOMAIN = "remote_receiver" remote_receiver_ns = cg.esphome_ns.namespace("remote_receiver") remote_base_ns = cg.esphome_ns.namespace("remote_base") diff --git a/esphome/components/remote_receiver/remote_receiver.cpp b/esphome/components/remote_receiver/remote_receiver.cpp index b3e4649096b..58c72a8bb8c 100644 --- a/esphome/components/remote_receiver/remote_receiver.cpp +++ b/esphome/components/remote_receiver/remote_receiver.cpp @@ -7,7 +7,7 @@ namespace esphome::remote_receiver { -static const char *const TAG = "remote_receiver"; +ESPHOME_LOG_TAG(TAG, "remote_receiver"); static void IRAM_ATTR HOT write_value(RemoteReceiverComponentStore *arg, uint32_t delta, bool level) { // convert level to -1 or +1 and write the delta to the buffer diff --git a/esphome/components/remote_receiver/remote_receiver_rmt.cpp b/esphome/components/remote_receiver/remote_receiver_rmt.cpp index 64392aa7eeb..2b76ab8989b 100644 --- a/esphome/components/remote_receiver/remote_receiver_rmt.cpp +++ b/esphome/components/remote_receiver/remote_receiver_rmt.cpp @@ -10,7 +10,7 @@ namespace esphome::remote_receiver { -static const char *const TAG = "remote_receiver"; +ESPHOME_LOG_TAG(TAG, "remote_receiver"); static constexpr uint32_t DEFAULT_BUFFER_SLOTS = 4; static bool IRAM_ATTR HOT rmt_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *event, void *arg) { diff --git a/esphome/components/remote_transmitter/__init__.py b/esphome/components/remote_transmitter/__init__.py index 68eac315d51..7553ed767b3 100644 --- a/esphome/components/remote_transmitter/__init__.py +++ b/esphome/components/remote_transmitter/__init__.py @@ -26,6 +26,7 @@ from esphome.types import ConfigType _LOGGER = logging.getLogger(__name__) AUTO_LOAD = ["remote_base"] +DOMAIN = "remote_transmitter" CONF_EOT_LEVEL = "eot_level" CONF_NON_BLOCKING = "non_blocking" diff --git a/esphome/components/remote_transmitter/remote_transmitter.cpp b/esphome/components/remote_transmitter/remote_transmitter.cpp index 5e82213a48d..34eb67b3c53 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter.cpp @@ -7,7 +7,7 @@ namespace esphome::remote_transmitter { -static const char *const TAG = "remote_transmitter"; +ESPHOME_LOG_TAG(TAG, "remote_transmitter"); void RemoteTransmitterComponent::setup() { this->pin_->setup(); @@ -114,7 +114,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen } } } - this->complete_trigger_.trigger(); + this->fire_complete_(send_times != 0); // same answer as the ISR backend for a frame sent zero times } } // namespace esphome::remote_transmitter diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index 99e1ce9504d..fad273a1185 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -82,6 +82,16 @@ class RemoteTransmitterComponent final : public remote_base::RemoteTransmitterBa protected: void send_internal(uint32_t send_times, uint32_t send_wait) override; + // the API reply is answered before the user's on_complete automation; sent is false on a + // bail-out that never put the frame on the wire, on_complete fires either way + void fire_complete_(bool sent = true) { + this->notify_complete_(sent); + this->complete_trigger_.trigger(); + } +#if (defined(USE_ESP32) && SOC_RMT_SUPPORTED) || defined(USE_LIBRETINY_VARIANT_RTL8720C) || \ + defined(REMOTE_TRANSMITTER_BK_PWM) + void flush_pending_completion() override; +#endif #if defined(USE_ESP8266) || \ (defined(USE_LIBRETINY) && !defined(USE_LIBRETINY_VARIANT_RTL8720C) && !defined(REMOTE_TRANSMITTER_BK_PWM)) || \ defined(USE_RP2) || (defined(USE_ESP32) && !SOC_RMT_SUPPORTED) diff --git a/esphome/components/remote_transmitter/remote_transmitter_bk72xx.cpp b/esphome/components/remote_transmitter/remote_transmitter_bk72xx.cpp index 822389ccf95..dced3720b44 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_bk72xx.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_bk72xx.cpp @@ -19,7 +19,7 @@ namespace esphome::remote_transmitter { -static const char *const TAG = "remote_transmitter"; +ESPHOME_LOG_TAG(TAG, "remote_transmitter"); #ifdef REMOTE_TRANSMITTER_BK_PWM diff --git a/esphome/components/remote_transmitter/remote_transmitter_libretiny_isr.cpp b/esphome/components/remote_transmitter/remote_transmitter_libretiny_isr.cpp index fad91f593ff..55f18277cef 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_libretiny_isr.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_libretiny_isr.cpp @@ -12,7 +12,7 @@ namespace esphome::remote_transmitter { -static const char *const TAG = "remote_transmitter"; +ESPHOME_LOG_TAG(TAG, "remote_transmitter"); // Margin past a transmission's expected duration before the chain is declared stalled static constexpr uint32_t STALL_MARGIN_MS = 1000; @@ -110,7 +110,7 @@ void RemoteTransmitterComponent::deliver_completion_() { if (!this->stall_aborted_) this->status_clear_warning(); this->complete_pending_ = false; - this->complete_trigger_.trigger(); + this->fire_complete_(!this->stall_aborted_); } // Waits until no chain is in flight, delivering any deferred completions; a completion @@ -152,20 +152,21 @@ void RemoteTransmitterComponent::arm_chain_(uint32_t send_times, uint32_t send_w this->start_isr_item_(0); } +void RemoteTransmitterComponent::flush_pending_completion() { this->wait_until_idle_(); } + void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { if (!this->envelope_ready_()) { // both triggers still fire, so an on_complete-sequenced automation does not stall ESP_LOGW(TAG, "Cannot send: PWM not initialized"); this->transmit_trigger_.trigger(); - this->deliver_completion_(); + this->fire_complete_(false); return; } - this->wait_until_idle_(); if (send_times == 0) { // parity with the loop-based implementations: transmit nothing, but both triggers // still fire so an on_complete-sequenced automation does not stall this->transmit_trigger_.trigger(); - this->deliver_completion_(); + this->fire_complete_(false); return; } ESP_LOGD(TAG, "Sending remote code"); @@ -175,7 +176,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen if (this->isr_data_.empty()) { ESP_LOGW(TAG, "Empty data"); this->transmit_trigger_.trigger(); - this->deliver_completion_(); + this->fire_complete_(false); return; } // trigger first: the deadline computed in arm_chain_ must not be charged for user code diff --git a/esphome/components/remote_transmitter/remote_transmitter_rmt.cpp b/esphome/components/remote_transmitter/remote_transmitter_rmt.cpp index 6d27be8d472..22a63ca4fd3 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_rmt.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_rmt.cpp @@ -9,7 +9,9 @@ namespace esphome::remote_transmitter { -static const char *const TAG = "remote_transmitter"; +ESPHOME_LOG_TAG(TAG, "remote_transmitter"); + +static constexpr uint32_t COMPLETE_TIMEOUT_ID = 0; // Maximum RMT symbol duration (15-bit field) static constexpr uint32_t RMT_SYMBOL_DURATION_MAX = 0x7FFF; @@ -200,25 +202,35 @@ void RemoteTransmitterComponent::wait_for_rmt_() { this->status_set_warning(); } - this->complete_trigger_.trigger(); + this->fire_complete_(error == ESP_OK); } #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) +void RemoteTransmitterComponent::flush_pending_completion() { + // a frame still on the wire is waited out, and its completion reported, before the next one + if (this->non_blocking_ && this->cancel_timeout(COMPLETE_TIMEOUT_ID)) { + this->wait_for_rmt_(); + } +} + void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { uint64_t total_duration = 0; if (this->is_failed()) { + // both triggers still fire, so a paced API client or on_complete automation is not left waiting + this->transmit_trigger_.trigger(); + this->fire_complete_(false); return; } - // if the timeout was cancelled, block until the tx is complete - if (this->non_blocking_ && this->cancel_timeout("complete")) { - this->wait_for_rmt_(); - } - if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) { this->current_carrier_frequency_ = this->temp_.get_carrier_frequency(); this->configure_rmt_(); + if (this->is_failed()) { // the carrier change failed, there is no channel to send on + this->transmit_trigger_.trigger(); + this->fire_complete_(false); + return; + } } this->rmt_temp_.clear(); @@ -258,6 +270,8 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen if ((this->rmt_temp_.data() == nullptr) || this->rmt_temp_.size() <= offset) { ESP_LOGE(TAG, "Empty data"); + this->transmit_trigger_.trigger(); + this->fire_complete_(false); return; } @@ -273,24 +287,36 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen if (error != ESP_OK) { ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error)); this->status_set_warning(); - } else { - this->status_clear_warning(); + // nothing was queued, so there is no frame to wait for + this->fire_complete_(false); + return; } + this->status_clear_warning(); if (this->non_blocking_) { - this->set_timeout("complete", total_duration / 1000, [this]() { this->wait_for_rmt_(); }); + this->set_timeout(COMPLETE_TIMEOUT_ID, total_duration / 1000, [this]() { this->wait_for_rmt_(); }); } else { this->wait_for_rmt_(); } } #else +void RemoteTransmitterComponent::flush_pending_completion() {} + void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { - if (this->is_failed()) + if (this->is_failed()) { + this->transmit_trigger_.trigger(); + this->fire_complete_(false); return; + } if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) { this->current_carrier_frequency_ = this->temp_.get_carrier_frequency(); this->configure_rmt_(); + if (this->is_failed()) { // the carrier change failed, there is no channel to send on + this->transmit_trigger_.trigger(); + this->fire_complete_(false); + return; + } } this->rmt_temp_.clear(); @@ -328,9 +354,12 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen if ((this->rmt_temp_.data() == nullptr) || this->rmt_temp_.empty()) { ESP_LOGE(TAG, "Empty data"); + this->transmit_trigger_.trigger(); + this->fire_complete_(false); return; } this->transmit_trigger_.trigger(); + bool sent = send_times != 0; // same answer as the ISR backend for a frame sent zero times for (uint32_t i = 0; i < send_times; i++) { rmt_transmit_config_t config; memset(&config, 0, sizeof(config)); @@ -340,18 +369,21 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen if (error != ESP_OK) { ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error)); this->status_set_warning(); - } else { - this->status_clear_warning(); + sent = false; } error = rmt_tx_wait_all_done(this->channel_, -1); if (error != ESP_OK) { ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error)); this->status_set_warning(); + sent = false; } if (i + 1 < send_times) delayMicroseconds(send_wait); } - this->complete_trigger_.trigger(); + // a later repeat must not clear the warning a failed one raised + if (sent) + this->status_clear_warning(); + this->fire_complete_(sent); } #endif diff --git a/esphome/components/remote_transmitter/remote_transmitter_rtl87xx.cpp b/esphome/components/remote_transmitter/remote_transmitter_rtl87xx.cpp index 6db9faac368..62be203ee32 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_rtl87xx.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_rtl87xx.cpp @@ -15,7 +15,7 @@ namespace esphome::remote_transmitter { -static const char *const TAG = "remote_transmitter"; +ESPHOME_LOG_TAG(TAG, "remote_transmitter"); // PWM peripheral carrier, envelope paced by a gtimer interrupt chain. Bit-banging would need // interrupts disabled for the whole frame, but this core's micros() derives from the FreeRTOS @@ -146,6 +146,8 @@ void RemoteTransmitterComponent::await_target_time_() { void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { if (this->pwm_ == nullptr) { ESP_LOGW(TAG, "Cannot send: PWM not initialized"); + this->transmit_trigger_.trigger(); + this->fire_complete_(false); return; } ESP_LOGD(TAG, "Sending remote code"); @@ -194,7 +196,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen } } } - this->complete_trigger_.trigger(); + this->fire_complete_(); } #endif // USE_LIBRETINY_VARIANT_RTL8720C diff --git a/esphome/components/resampler/__init__.py b/esphome/components/resampler/__init__.py index b9b070e0154..e41b004b86c 100644 --- a/esphome/components/resampler/__init__.py +++ b/esphome/components/resampler/__init__.py @@ -3,6 +3,8 @@ from typing import Any import esphome.codegen as cg import esphome.config_validation as cv +DOMAIN = "resampler" + resampler_ns = cg.esphome_ns.namespace("resampler") CONF_TAPS = "taps" diff --git a/esphome/components/resampler/microphone/resampler_microphone.cpp b/esphome/components/resampler/microphone/resampler_microphone.cpp index 0dcd5233800..a50f6adea80 100644 --- a/esphome/components/resampler/microphone/resampler_microphone.cpp +++ b/esphome/components/resampler/microphone/resampler_microphone.cpp @@ -9,7 +9,7 @@ namespace esphome::resampler { -static const char *const TAG = "resampler.microphone"; +ESPHOME_LOG_TAG(TAG, "resampler.microphone"); // Duration of audio the resampler converts per step; longer source chunks are processed in several steps static constexpr uint32_t BUFFER_DURATION_MS = 16; diff --git a/esphome/components/resampler/speaker/resampler_speaker.cpp b/esphome/components/resampler/speaker/resampler_speaker.cpp index 16d2d5dc9e2..99b00ec0948 100644 --- a/esphome/components/resampler/speaker/resampler_speaker.cpp +++ b/esphome/components/resampler/speaker/resampler_speaker.cpp @@ -22,7 +22,7 @@ static const uint32_t TASK_STACK_SIZE = 3072; static const uint32_t STATE_TRANSITION_TIMEOUT_MS = 5000; -static const char *const TAG = "resampler_speaker"; +ESPHOME_LOG_TAG(TAG, "resampler_speaker"); enum ResamplingEventGroupBits : uint32_t { COMMAND_STOP = (1 << 0), // signals stop request diff --git a/esphome/components/resistance/__init__.py b/esphome/components/resistance/__init__.py index e69de29bb2d..5bba63cc1b1 100644 --- a/esphome/components/resistance/__init__.py +++ b/esphome/components/resistance/__init__.py @@ -0,0 +1 @@ +DOMAIN = "resistance" diff --git a/esphome/components/resistance/resistance_sensor.cpp b/esphome/components/resistance/resistance_sensor.cpp index 7522d026a4e..d6bc324fa42 100644 --- a/esphome/components/resistance/resistance_sensor.cpp +++ b/esphome/components/resistance/resistance_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::resistance { -static const char *const TAG = "resistance"; +ESPHOME_LOG_TAG(TAG, "resistance"); void ResistanceSensor::dump_config() { LOG_SENSOR("", "Resistance Sensor", this); diff --git a/esphome/components/restart/__init__.py b/esphome/components/restart/__init__.py index f70ffa95208..6337520ebd9 100644 --- a/esphome/components/restart/__init__.py +++ b/esphome/components/restart/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@esphome/core"] +DOMAIN = "restart" diff --git a/esphome/components/restart/button/restart_button.cpp b/esphome/components/restart/button/restart_button.cpp index d6404315ea9..cffa47896ce 100644 --- a/esphome/components/restart/button/restart_button.cpp +++ b/esphome/components/restart/button/restart_button.cpp @@ -5,7 +5,7 @@ namespace esphome::restart { -static const char *const TAG = "restart.button"; +ESPHOME_LOG_TAG(TAG, "restart.button"); void RestartButton::press_action() { ESP_LOGI(TAG, "Restarting device"); diff --git a/esphome/components/restart/switch/restart_switch.cpp b/esphome/components/restart/switch/restart_switch.cpp index 96a4fc40f5c..50102366fc3 100644 --- a/esphome/components/restart/switch/restart_switch.cpp +++ b/esphome/components/restart/switch/restart_switch.cpp @@ -5,7 +5,7 @@ namespace esphome::restart { -static const char *const TAG = "restart"; +ESPHOME_LOG_TAG(TAG, "restart"); void RestartSwitch::write_state(bool state) { // Acknowledge diff --git a/esphome/components/rf_bridge/__init__.py b/esphome/components/rf_bridge/__init__.py index fed8365e9ee..e967335c180 100644 --- a/esphome/components/rf_bridge/__init__.py +++ b/esphome/components/rf_bridge/__init__.py @@ -16,6 +16,7 @@ from esphome.const import ( DEPENDENCIES = ["uart"] CODEOWNERS = ["@jesserockz"] +DOMAIN = "rf_bridge" rf_bridge_ns = cg.esphome_ns.namespace("rf_bridge") RFBridgeComponent = rf_bridge_ns.class_( diff --git a/esphome/components/rf_bridge/rf_bridge.cpp b/esphome/components/rf_bridge/rf_bridge.cpp index a4a4da5d8c0..86daaf4b08b 100644 --- a/esphome/components/rf_bridge/rf_bridge.cpp +++ b/esphome/components/rf_bridge/rf_bridge.cpp @@ -7,7 +7,7 @@ namespace esphome::rf_bridge { -static const char *const TAG = "rf_bridge"; +ESPHOME_LOG_TAG(TAG, "rf_bridge"); void RFBridgeComponent::ack_() { ESP_LOGV(TAG, "Sending ACK"); diff --git a/esphome/components/rgb/__init__.py b/esphome/components/rgb/__init__.py index e69de29bb2d..11e05410bec 100644 --- a/esphome/components/rgb/__init__.py +++ b/esphome/components/rgb/__init__.py @@ -0,0 +1 @@ +DOMAIN = "rgb" diff --git a/esphome/components/rgbct/__init__.py b/esphome/components/rgbct/__init__.py index e69de29bb2d..b8a02914c57 100644 --- a/esphome/components/rgbct/__init__.py +++ b/esphome/components/rgbct/__init__.py @@ -0,0 +1 @@ +DOMAIN = "rgbct" diff --git a/esphome/components/rgbw/__init__.py b/esphome/components/rgbw/__init__.py index e69de29bb2d..0635bc36c37 100644 --- a/esphome/components/rgbw/__init__.py +++ b/esphome/components/rgbw/__init__.py @@ -0,0 +1 @@ +DOMAIN = "rgbw" diff --git a/esphome/components/rgbww/__init__.py b/esphome/components/rgbww/__init__.py index e69de29bb2d..ce95d11431f 100644 --- a/esphome/components/rgbww/__init__.py +++ b/esphome/components/rgbww/__init__.py @@ -0,0 +1 @@ +DOMAIN = "rgbww" diff --git a/esphome/components/ring_buffer/__init__.py b/esphome/components/ring_buffer/__init__.py index b53476dcacb..d053a7e409c 100644 --- a/esphome/components/ring_buffer/__init__.py +++ b/esphome/components/ring_buffer/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg CODEOWNERS = ["@kahrendt"] DEPENDENCIES = ["esp32"] +DOMAIN = "ring_buffer" ring_buffer_ns = cg.esphome_ns.namespace("ring_buffer") RingBuffer = ring_buffer_ns.class_("RingBuffer") diff --git a/esphome/components/ring_buffer/ring_buffer.cpp b/esphome/components/ring_buffer/ring_buffer.cpp index dfcc53ebb2e..7197a073481 100644 --- a/esphome/components/ring_buffer/ring_buffer.cpp +++ b/esphome/components/ring_buffer/ring_buffer.cpp @@ -7,7 +7,7 @@ namespace esphome::ring_buffer { -static const char *const TAG = "ring_buffer"; +ESPHOME_LOG_TAG(TAG, "ring_buffer"); RingBuffer::~RingBuffer() { // create() only builds the ring buffer once storage_ is allocated diff --git a/esphome/components/rotary_encoder/__init__.py b/esphome/components/rotary_encoder/__init__.py index e69de29bb2d..5f448e531cd 100644 --- a/esphome/components/rotary_encoder/__init__.py +++ b/esphome/components/rotary_encoder/__init__.py @@ -0,0 +1 @@ +DOMAIN = "rotary_encoder" diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index 0734ca87d36..4c438308888 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -4,7 +4,7 @@ namespace esphome::rotary_encoder { -static const char *const TAG = "rotary_encoder"; +ESPHOME_LOG_TAG(TAG, "rotary_encoder"); // based on https://github.com/jkDesignDE/MechInputs/blob/master/QEIx4.cpp static const uint8_t STATE_LUT_MASK = 0x1C; // clears upper counter increment/decrement bits and pin states diff --git a/esphome/components/router/__init__.py b/esphome/components/router/__init__.py index e69de29bb2d..49a5eec4264 100644 --- a/esphome/components/router/__init__.py +++ b/esphome/components/router/__init__.py @@ -0,0 +1 @@ +DOMAIN = "router" diff --git a/esphome/components/router/speaker/router_speaker.cpp b/esphome/components/router/speaker/router_speaker.cpp index dd2428e4df4..5fdc1ac716a 100644 --- a/esphome/components/router/speaker/router_speaker.cpp +++ b/esphome/components/router/speaker/router_speaker.cpp @@ -12,7 +12,7 @@ namespace esphome::router { -static const char *const TAG = "router.speaker"; +ESPHOME_LOG_TAG(TAG, "router.speaker"); // Maximum time to wait for the active output to report running after start() before giving up static const uint32_t STATE_TRANSITION_TIMEOUT_MS = 5000; diff --git a/esphome/components/rp2/__init__.py b/esphome/components/rp2/__init__.py index 23346ca6f4b..76dd240f7ac 100644 --- a/esphome/components/rp2/__init__.py +++ b/esphome/components/rp2/__init__.py @@ -57,6 +57,7 @@ from .gpio import rp2_pin_to_code # noqa: F401 _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@jesserockz"] AUTO_LOAD = ["preferences"] +DOMAIN = "rp2" IS_TARGET_PLATFORM = True # Legacy top-level YAML keys that route here. The framework diff --git a/esphome/components/rp2/crash_handler.cpp b/esphome/components/rp2/crash_handler.cpp index 9bcdc8bee4e..0741cc24e39 100644 --- a/esphome/components/rp2/crash_handler.cpp +++ b/esphome/components/rp2/crash_handler.cpp @@ -53,7 +53,7 @@ static constexpr size_t MAX_BACKTRACE = 4; namespace esphome::rp2 { -static const char *const TAG = "rp2.crash"; +ESPHOME_LOG_TAG(TAG, "rp2.crash"); // Filled from the watchdog scratch registers on the first read. static struct CrashData { diff --git a/esphome/components/rp2/gpio.cpp b/esphome/components/rp2/gpio.cpp index b95da9bcdbc..2eeb460e954 100644 --- a/esphome/components/rp2/gpio.cpp +++ b/esphome/components/rp2/gpio.cpp @@ -1,12 +1,13 @@ #ifdef USE_RP2 #include "gpio.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" namespace esphome { namespace rp2 { -static const char *const TAG = "rp2"; +ESPHOME_LOG_TAG(TAG, "rp2"); static int flags_to_mode(gpio::Flags flags, uint8_t pin) { flags = @@ -40,7 +41,7 @@ ISRInternalGPIOPin RP2GPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } -void RP2GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { +void RP2GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { PinStatus arduino_mode = LOW; switch (type) { case gpio::INTERRUPT_RISING_EDGE: diff --git a/esphome/components/rp2/gpio.h b/esphome/components/rp2/gpio.h index 538fef619a2..b1d4c9a8f61 100644 --- a/esphome/components/rp2/gpio.h +++ b/esphome/components/rp2/gpio.h @@ -3,11 +3,11 @@ #ifdef USE_RP2 #include -#include "esphome/core/hal.h" +#include "esphome/core/gpio_pin.h" namespace esphome::rp2 { -class RP2GPIOPin final : public InternalGPIOPin { +class RP2GPIOPin final : public GPIOPin { public: void set_pin(uint8_t pin) { pin_ = pin; } void set_inverted(bool inverted) { inverted_ = inverted; } @@ -18,14 +18,18 @@ class RP2GPIOPin final : public InternalGPIOPin { bool digital_read() override; void digital_write(bool value) override; size_t dump_summary(char *buffer, size_t len) const override; - void detach_interrupt() const override; - ISRInternalGPIOPin to_isr() const override; - uint8_t get_pin() const override { return pin_; } + void detach_interrupt() const; + template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { + this->attach_interrupt_(reinterpret_cast(func), arg, type); + } + ISRInternalGPIOPin to_isr() const; + uint8_t get_pin() const { return pin_; } gpio::Flags get_flags() const override { return flags_; } - bool is_inverted() const override { return inverted_; } + bool is_inverted() const { return inverted_; } + bool is_internal() override { return true; } protected: - void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const; uint8_t pin_; bool inverted_{}; diff --git a/esphome/components/rp2/hal.h b/esphome/components/rp2/hal.h index ec46937bab5..2a538f15713 100644 --- a/esphome/components/rp2/hal.h +++ b/esphome/components/rp2/hal.h @@ -4,6 +4,8 @@ #include +#include + #include "esphome/core/time_conversion.h" #define IRAM_ATTR __attribute__((noinline, long_call, section(".time_critical"))) @@ -40,6 +42,9 @@ __attribute__((always_inline)) inline bool in_isr_context() { return ipsr != 0; } +/// arduino-pico runs setup() and loop() on core 0; ESPHome never uses core 1. +__attribute__((always_inline)) inline bool is_main_loop_thread() { return !in_isr_context() && get_core_num() == 0; } + __attribute__((always_inline)) inline void yield() { ::yield(); } __attribute__((always_inline)) inline void delay(uint32_t ms) { ::delay(ms); } __attribute__((always_inline)) inline uint32_t micros() { return static_cast(::micros()); } diff --git a/esphome/components/rp2/preferences.cpp b/esphome/components/rp2/preferences.cpp index d1e0bc555f5..dfe19f4bf4f 100644 --- a/esphome/components/rp2/preferences.cpp +++ b/esphome/components/rp2/preferences.cpp @@ -14,7 +14,7 @@ namespace esphome::rp2 { -static const char *const TAG = "preferences"; +ESPHOME_LOG_TAG(TAG, "preferences"); static constexpr uint32_t RP2040_FLASH_STORAGE_SIZE = 512; diff --git a/esphome/components/rp2040_ble/__init__.py b/esphome/components/rp2040_ble/__init__.py index d2a08e9fc05..ad79551792b 100644 --- a/esphome/components/rp2040_ble/__init__.py +++ b/esphome/components/rp2040_ble/__init__.py @@ -8,10 +8,10 @@ from esphome.types import ConfigType DEPENDENCIES = ["rp2"] CODEOWNERS = ["@bdraco"] +DOMAIN = "rp2040_ble" CONF_RP2040_BLE_ID = "rp2040_ble_id" -KEY_RP2040_BLE = "rp2040_ble" KEY_USED_CONNECTION_SLOTS = "used_connection_slots" # Hard platform cap on concurrent GATT connections: the BTstack pool overrides @@ -49,7 +49,7 @@ def consume_connection_slots( the total is checked against MAX_CONNECTIONS in final validation.""" def _consume_connection_slots(config: MutableMapping) -> MutableMapping: - data: dict = CORE.data.setdefault(KEY_RP2040_BLE, {}) + data: dict = CORE.data.setdefault(DOMAIN, {}) slots: list[str] = data.setdefault(KEY_USED_CONNECTION_SLOTS, []) slots.extend([consumer] * value) return config @@ -62,7 +62,7 @@ def validate_connection_slots() -> None: # Skip in testing mode to allow component grouping (esp32_ble parity). if CORE.testing_mode: return - used = CORE.data.get(KEY_RP2040_BLE, {}).get(KEY_USED_CONNECTION_SLOTS, []) + used = CORE.data.get(DOMAIN, {}).get(KEY_USED_CONNECTION_SLOTS, []) if len(used) > MAX_CONNECTIONS: raise cv.Invalid( f"BLE components require {len(used)} connection slots but the " diff --git a/esphome/components/rp2040_ble/rp2040_ble.cpp b/esphome/components/rp2040_ble/rp2040_ble.cpp index 80e8bf94158..fca0c45d6f5 100644 --- a/esphome/components/rp2040_ble/rp2040_ble.cpp +++ b/esphome/components/rp2040_ble/rp2040_ble.cpp @@ -11,7 +11,7 @@ namespace esphome::rp2040_ble { -static const char *const TAG = "rp2040_ble"; +ESPHOME_LOG_TAG(TAG, "rp2040_ble"); // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) RP2040BLE *global_ble = nullptr; diff --git a/esphome/components/rp2040_pio_led_strip/__init__.py b/esphome/components/rp2040_pio_led_strip/__init__.py index 4c9aa2d1552..10b24efa099 100644 --- a/esphome/components/rp2040_pio_led_strip/__init__.py +++ b/esphome/components/rp2040_pio_led_strip/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@Papa-DMan"] +DOMAIN = "rp2040_pio_led_strip" diff --git a/esphome/components/rp2040_pio_led_strip/led_strip.cpp b/esphome/components/rp2040_pio_led_strip/led_strip.cpp index 1f4bea9ecd8..8ea9880c7c0 100644 --- a/esphome/components/rp2040_pio_led_strip/led_strip.cpp +++ b/esphome/components/rp2040_pio_led_strip/led_strip.cpp @@ -14,7 +14,7 @@ namespace esphome::rp2040_pio_led_strip { -static const char *const TAG = "rp2040_pio_led_strip"; +ESPHOME_LOG_TAG(TAG, "rp2040_pio_led_strip"); // DMA interrupt service routine void RP2040PIOLEDStripLightOutput::dma_write_complete_handler() { diff --git a/esphome/components/rp2040_pwm/__init__.py b/esphome/components/rp2040_pwm/__init__.py index e69de29bb2d..3abe0b8c831 100644 --- a/esphome/components/rp2040_pwm/__init__.py +++ b/esphome/components/rp2040_pwm/__init__.py @@ -0,0 +1 @@ +DOMAIN = "rp2040_pwm" diff --git a/esphome/components/rp2040_pwm/rp2040_pwm.cpp b/esphome/components/rp2040_pwm/rp2040_pwm.cpp index 270cc335519..8324da3dcfc 100644 --- a/esphome/components/rp2040_pwm/rp2040_pwm.cpp +++ b/esphome/components/rp2040_pwm/rp2040_pwm.cpp @@ -13,7 +13,7 @@ namespace esphome::rp2040_pwm { -static const char *const TAG = "rp2040_pwm"; +ESPHOME_LOG_TAG(TAG, "rp2040_pwm"); void RP2040PWM::setup() { this->setup_pwm_(); } diff --git a/esphome/components/rp2_ble_tracker/__init__.py b/esphome/components/rp2_ble_tracker/__init__.py index 12857a556fc..4a3d3f788bf 100644 --- a/esphome/components/rp2_ble_tracker/__init__.py +++ b/esphome/components/rp2_ble_tracker/__init__.py @@ -33,6 +33,7 @@ from esphome.types import ConfigType DEPENDENCIES = ["rp2"] AUTO_LOAD = ["ble_device_base", "rp2040_ble"] CODEOWNERS = ["@bdraco"] +DOMAIN = "rp2_ble_tracker" ble_device_base.register_hub_provider("rp2_ble_tracker") diff --git a/esphome/components/rp2_ble_tracker/rp2_ble_tracker.cpp b/esphome/components/rp2_ble_tracker/rp2_ble_tracker.cpp index a0a04b981d0..192e19455ed 100644 --- a/esphome/components/rp2_ble_tracker/rp2_ble_tracker.cpp +++ b/esphome/components/rp2_ble_tracker/rp2_ble_tracker.cpp @@ -9,7 +9,7 @@ namespace esphome::rp2_ble_tracker { -static const char *const TAG = "rp2_ble_tracker"; +ESPHOME_LOG_TAG(TAG, "rp2_ble_tracker"); // Floor between controller start attempts; insurance against a failing // scan_start() being retried every loop. diff --git a/esphome/components/rp2_pio/__init__.py b/esphome/components/rp2_pio/__init__.py index 915ab73b876..1b2236ae8e9 100644 --- a/esphome/components/rp2_pio/__init__.py +++ b/esphome/components/rp2_pio/__init__.py @@ -5,6 +5,7 @@ import esphome.config_validation as cv from esphome.types import ConfigType DEPENDENCIES = ["rp2"] +DOMAIN = "rp2_pio" PIOASM_REPO_VERSION = "1.5.0-b" diff --git a/esphome/components/rpi_dpi_rgb/__init__.py b/esphome/components/rpi_dpi_rgb/__init__.py index 8628f0e0639..20629fd7305 100644 --- a/esphome/components/rpi_dpi_rgb/__init__.py +++ b/esphome/components/rpi_dpi_rgb/__init__.py @@ -1,4 +1,5 @@ CODEOWNERS = ["@clydebarrow"] +DOMAIN = "rpi_dpi_rgb" DEPRECATED_COMPONENT = """ The 'rpi_dpi_rgb' component is deprecated and no new models will be added to it. diff --git a/esphome/components/rtl87xx/__init__.py b/esphome/components/rtl87xx/__init__.py index 88928b2bcee..d3d5150819c 100644 --- a/esphome/components/rtl87xx/__init__.py +++ b/esphome/components/rtl87xx/__init__.py @@ -35,6 +35,7 @@ from .boards import RTL87XX_BOARD_PINS, RTL87XX_BOARDS CODEOWNERS = ["@kuba2k2"] AUTO_LOAD = ["libretiny"] +DOMAIN = "rtl87xx" IS_TARGET_PLATFORM = True COMPONENT_DATA = LibreTinyComponent( diff --git a/esphome/components/rtttl/__init__.py b/esphome/components/rtttl/__init__.py index 4f4d17b441e..47b8f26e3ff 100644 --- a/esphome/components/rtttl/__init__.py +++ b/esphome/components/rtttl/__init__.py @@ -12,6 +12,7 @@ from esphome.types import ConfigType _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@glmnet", "@ximex"] +DOMAIN = "rtttl" CONF_RTTTL = "rtttl" CONF_ON_FINISHED_PLAYBACK = "on_finished_playback" diff --git a/esphome/components/rtttl/rtttl.cpp b/esphome/components/rtttl/rtttl.cpp index a5f8567c9da..fb2bf63a646 100644 --- a/esphome/components/rtttl/rtttl.cpp +++ b/esphome/components/rtttl/rtttl.cpp @@ -6,7 +6,7 @@ namespace esphome::rtttl { -static const char *const TAG = "rtttl"; +ESPHOME_LOG_TAG(TAG, "rtttl"); static constexpr uint8_t SONG_NAME_LENGTH_LIMIT = 64; static constexpr uint8_t SEMITONES_IN_OCTAVE = 12; diff --git a/esphome/components/runtime_image/__init__.py b/esphome/components/runtime_image/__init__.py index e27fcadbee9..fcf5cb770c1 100644 --- a/esphome/components/runtime_image/__init__.py +++ b/esphome/components/runtime_image/__init__.py @@ -17,6 +17,7 @@ from esphome.core import CORE AUTO_LOAD = ["image"] CODEOWNERS = ["@guillempages", "@clydebarrow", "@kahrendt"] +DOMAIN = "runtime_image" CONF_PLACEHOLDER = "placeholder" CONF_TRANSPARENCY = "transparency" diff --git a/esphome/components/runtime_image/bmp_decoder.cpp b/esphome/components/runtime_image/bmp_decoder.cpp index 204d6cc14b4..9260860f408 100644 --- a/esphome/components/runtime_image/bmp_decoder.cpp +++ b/esphome/components/runtime_image/bmp_decoder.cpp @@ -10,7 +10,7 @@ namespace esphome::runtime_image { -static const char *const TAG = "image_decoder.bmp"; +ESPHOME_LOG_TAG(TAG, "image_decoder.bmp"); void BmpDecoder::reset() { ImageDecoder::reset(); diff --git a/esphome/components/runtime_image/image_decoder.cpp b/esphome/components/runtime_image/image_decoder.cpp index f2c4f5c8cdc..7056ce35aa8 100644 --- a/esphome/components/runtime_image/image_decoder.cpp +++ b/esphome/components/runtime_image/image_decoder.cpp @@ -6,7 +6,7 @@ namespace esphome::runtime_image { -static const char *const TAG = "image_decoder"; +ESPHOME_LOG_TAG(TAG, "image_decoder"); bool ImageDecoder::set_size(int width, int height) { bool success = this->image_->resize(width, height) > 0; diff --git a/esphome/components/runtime_image/jpeg_decoder.cpp b/esphome/components/runtime_image/jpeg_decoder.cpp index 85ec945259d..74ced63a4e8 100644 --- a/esphome/components/runtime_image/jpeg_decoder.cpp +++ b/esphome/components/runtime_image/jpeg_decoder.cpp @@ -11,7 +11,7 @@ #include "esp_task_wdt.h" #endif -static const char *const TAG = "image_decoder.jpeg"; +ESPHOME_LOG_TAG(TAG, "image_decoder.jpeg"); namespace esphome::runtime_image { diff --git a/esphome/components/runtime_image/png_decoder.cpp b/esphome/components/runtime_image/png_decoder.cpp index 106f25bbe18..ae763471188 100644 --- a/esphome/components/runtime_image/png_decoder.cpp +++ b/esphome/components/runtime_image/png_decoder.cpp @@ -6,7 +6,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -static const char *const TAG = "image_decoder.png"; +ESPHOME_LOG_TAG(TAG, "image_decoder.png"); namespace esphome::runtime_image { diff --git a/esphome/components/runtime_image/qoi_decoder.cpp b/esphome/components/runtime_image/qoi_decoder.cpp index b0fabb5233a..dacaace3fd2 100644 --- a/esphome/components/runtime_image/qoi_decoder.cpp +++ b/esphome/components/runtime_image/qoi_decoder.cpp @@ -10,7 +10,7 @@ namespace esphome::runtime_image { -static const char *const TAG = "image_decoder.qoi"; +ESPHOME_LOG_TAG(TAG, "image_decoder.qoi"); constexpr uint8_t QOI_OP_RGB = 0b11111110; constexpr uint8_t QOI_OP_RGBA = 0b11111111; diff --git a/esphome/components/runtime_image/runtime_image.cpp b/esphome/components/runtime_image/runtime_image.cpp index 5c14d398071..6c101945746 100644 --- a/esphome/components/runtime_image/runtime_image.cpp +++ b/esphome/components/runtime_image/runtime_image.cpp @@ -21,7 +21,7 @@ namespace esphome::runtime_image { -static const char *const TAG = "runtime_image"; +ESPHOME_LOG_TAG(TAG, "runtime_image"); // Widest supported format is 4 bytes/pixel, so 32767 * 32767 * 4 still fits a 32-bit size_t static constexpr int MAX_IMAGE_DIMENSION = 32767; diff --git a/esphome/components/runtime_stats/__init__.py b/esphome/components/runtime_stats/__init__.py index 62f5f46cb40..2d1a6ac910b 100644 --- a/esphome/components/runtime_stats/__init__.py +++ b/esphome/components/runtime_stats/__init__.py @@ -8,6 +8,7 @@ from esphome.const import CONF_ID from esphome.types import ConfigType CODEOWNERS = ["@bdraco"] +DOMAIN = "runtime_stats" CONF_LOG_INTERVAL = "log_interval" diff --git a/esphome/components/ruuvi_ble/__init__.py b/esphome/components/ruuvi_ble/__init__.py index ac4bafbb1a3..ccfa8ecf104 100644 --- a/esphome/components/ruuvi_ble/__init__.py +++ b/esphome/components/ruuvi_ble/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_ID from esphome.types import ConfigType AUTO_LOAD = ["ble_device_base"] +DOMAIN = "ruuvi_ble" ruuvi_ble_ns = cg.esphome_ns.namespace("ruuvi_ble") RuuviListener = ruuvi_ble_ns.class_( diff --git a/esphome/components/ruuvi_ble/ruuvi_ble.cpp b/esphome/components/ruuvi_ble/ruuvi_ble.cpp index 19753b3c9ea..c781e282fe7 100644 --- a/esphome/components/ruuvi_ble/ruuvi_ble.cpp +++ b/esphome/components/ruuvi_ble/ruuvi_ble.cpp @@ -3,7 +3,7 @@ namespace esphome::ruuvi_ble { -static const char *const TAG = "ruuvi_ble"; +ESPHOME_LOG_TAG(TAG, "ruuvi_ble"); bool parse_ruuvi_data_byte(const ble_device_base::adv_data_t &adv_data, RuuviParseResult &result) { const uint8_t data_type = adv_data[0]; diff --git a/esphome/components/ruuvitag/__init__.py b/esphome/components/ruuvitag/__init__.py index e69de29bb2d..2a0e78efc83 100644 --- a/esphome/components/ruuvitag/__init__.py +++ b/esphome/components/ruuvitag/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ruuvitag" diff --git a/esphome/components/ruuvitag/ruuvitag.cpp b/esphome/components/ruuvitag/ruuvitag.cpp index 1536befb1b2..3dc4a3a223a 100644 --- a/esphome/components/ruuvitag/ruuvitag.cpp +++ b/esphome/components/ruuvitag/ruuvitag.cpp @@ -3,7 +3,7 @@ namespace esphome::ruuvitag { -static const char *const TAG = "ruuvitag"; +ESPHOME_LOG_TAG(TAG, "ruuvitag"); void RuuviTag::dump_config() { ESP_LOGCONFIG(TAG, "RuuviTag"); diff --git a/esphome/components/rx8025t/__init__.py b/esphome/components/rx8025t/__init__.py index e69de29bb2d..e0b74646569 100644 --- a/esphome/components/rx8025t/__init__.py +++ b/esphome/components/rx8025t/__init__.py @@ -0,0 +1 @@ +DOMAIN = "rx8025t" diff --git a/esphome/components/rx8025t/rx8025t.cpp b/esphome/components/rx8025t/rx8025t.cpp index fee2c83a5fe..502f6f265d6 100644 --- a/esphome/components/rx8025t/rx8025t.cpp +++ b/esphome/components/rx8025t/rx8025t.cpp @@ -11,7 +11,7 @@ static constexpr uint8_t RX8025T_REG_FLAG = 0x0E; static constexpr uint8_t RX8025T_FLAG_VDET = 0x01; static constexpr uint8_t RX8025T_FLAG_VLF = 0x02; -static const char *const TAG = "rx8025t"; +ESPHOME_LOG_TAG(TAG, "rx8025t"); constexpr uint8_t bcd2dec(uint8_t val) { return (val >> 4) * 10 + (val & 0x0f); } constexpr uint8_t dec2bcd(uint8_t val) { return ((val / 10) << 4) + (val % 10); } diff --git a/esphome/components/rx8130/__init__.py b/esphome/components/rx8130/__init__.py index e69de29bb2d..3b5225c024f 100644 --- a/esphome/components/rx8130/__init__.py +++ b/esphome/components/rx8130/__init__.py @@ -0,0 +1 @@ +DOMAIN = "rx8130" diff --git a/esphome/components/rx8130/rx8130.cpp b/esphome/components/rx8130/rx8130.cpp index 1b5b71f0e0e..63153c5db3f 100644 --- a/esphome/components/rx8130/rx8130.cpp +++ b/esphome/components/rx8130/rx8130.cpp @@ -21,7 +21,7 @@ static const uint8_t RX8130_BIT_CTRL_STOP = 0x40; static const uint8_t RX8130_BAT_FLAGS = 0x30; static const uint8_t RX8130_CLEAR_FLAGS = 0x00; -static const char *const TAG = "rx8130"; +ESPHOME_LOG_TAG(TAG, "rx8130"); constexpr uint8_t bcd2dec(uint8_t val) { return (val >> 4) * 10 + (val & 0x0f); } constexpr uint8_t dec2bcd(uint8_t val) { return ((val / 10) << 4) + (val % 10); } diff --git a/esphome/components/safe_mode/__init__.py b/esphome/components/safe_mode/__init__.py index 5d03ed70a83..8d77ff363fc 100644 --- a/esphome/components/safe_mode/__init__.py +++ b/esphome/components/safe_mode/__init__.py @@ -6,7 +6,6 @@ from esphome.const import ( CONF_ID, CONF_NUM_ATTEMPTS, CONF_REBOOT_TIMEOUT, - CONF_SAFE_MODE, CONF_STORAGE, KEY_PAST_SAFE_MODE, ) @@ -15,6 +14,7 @@ from esphome.cpp_generator import RawExpression from esphome.types import ConfigType CODEOWNERS = ["@paulmonigatti", "@jsuanet", "@kbx81"] +DOMAIN = "safe_mode" CONF_BOOT_IS_GOOD_AFTER = "boot_is_good_after" CONF_BOOT_IS_GOOD_ON_SHUTDOWN = "boot_is_good_on_shutdown" @@ -92,5 +92,5 @@ async def to_code(config: ConfigType) -> None: ) cg.add(RawExpression(f"if ({condition}) return")) - CORE.data[CONF_SAFE_MODE] = {} - CORE.data[CONF_SAFE_MODE][KEY_PAST_SAFE_MODE] = True + CORE.data[DOMAIN] = {} + CORE.data[DOMAIN][KEY_PAST_SAFE_MODE] = True diff --git a/esphome/components/safe_mode/button/safe_mode_button.cpp b/esphome/components/safe_mode/button/safe_mode_button.cpp index 982ecf8402d..e9779953f02 100644 --- a/esphome/components/safe_mode/button/safe_mode_button.cpp +++ b/esphome/components/safe_mode/button/safe_mode_button.cpp @@ -5,7 +5,7 @@ namespace esphome::safe_mode { -static const char *const TAG = "safe_mode.button"; +ESPHOME_LOG_TAG(TAG, "safe_mode.button"); void SafeModeButton::press_action() { ESP_LOGI(TAG, "Restarting in safe mode"); diff --git a/esphome/components/safe_mode/safe_mode.cpp b/esphome/components/safe_mode/safe_mode.cpp index 8fd5911ab5c..3099df58601 100644 --- a/esphome/components/safe_mode/safe_mode.cpp +++ b/esphome/components/safe_mode/safe_mode.cpp @@ -21,7 +21,7 @@ namespace esphome::safe_mode { -static const char *const TAG = "safe_mode"; +ESPHOME_LOG_TAG(TAG, "safe_mode"); #if defined(USE_ESP32) && defined(USE_OTA_ROLLBACK) && !defined(USE_OTA_PARTITIONS) // Find a non-running app partition. If verify is true, only returns a partition diff --git a/esphome/components/safe_mode/switch/safe_mode_switch.cpp b/esphome/components/safe_mode/switch/safe_mode_switch.cpp index b4b97357572..c60c59dbfcc 100644 --- a/esphome/components/safe_mode/switch/safe_mode_switch.cpp +++ b/esphome/components/safe_mode/switch/safe_mode_switch.cpp @@ -5,7 +5,7 @@ namespace esphome::safe_mode { -static const char *const TAG = "safe_mode.switch"; +ESPHOME_LOG_TAG(TAG, "safe_mode.switch"); void SafeModeSwitch::write_state(bool state) { // Acknowledge diff --git a/esphome/components/scd30/__init__.py b/esphome/components/scd30/__init__.py index e69de29bb2d..71e20c7e131 100644 --- a/esphome/components/scd30/__init__.py +++ b/esphome/components/scd30/__init__.py @@ -0,0 +1 @@ +DOMAIN = "scd30" diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index a7802977562..5802503627a 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -8,7 +8,9 @@ namespace esphome::scd30 { -static const char *const TAG = "scd30"; +ESPHOME_LOG_TAG(TAG, "scd30"); + +static constexpr uint32_t STATUS_CHECK_INTERVAL_ID = 0; static const uint16_t SCD30_CMD_GET_FIRMWARE_VERSION = 0xd100; static const uint16_t SCD30_CMD_START_CONTINUOUS_MEASUREMENTS = 0x0010; @@ -107,7 +109,7 @@ void SCD30Component::setup() { } // check each 500ms if data is ready, and read it in that case - this->set_interval("status-check", 500, [this]() { + this->set_interval(STATUS_CHECK_INTERVAL_ID, 500, [this]() { if (this->is_data_ready_()) this->update(); }); diff --git a/esphome/components/scd4x/__init__.py b/esphome/components/scd4x/__init__.py index e69de29bb2d..338bfdf59bb 100644 --- a/esphome/components/scd4x/__init__.py +++ b/esphome/components/scd4x/__init__.py @@ -0,0 +1 @@ +DOMAIN = "scd4x" diff --git a/esphome/components/scd4x/scd4x.cpp b/esphome/components/scd4x/scd4x.cpp index d9a2439bb90..8249a6feb1e 100644 --- a/esphome/components/scd4x/scd4x.cpp +++ b/esphome/components/scd4x/scd4x.cpp @@ -4,7 +4,7 @@ namespace esphome::scd4x { -static const char *const TAG = "scd4x"; +ESPHOME_LOG_TAG(TAG, "scd4x"); static const uint16_t SCD41_ID = 0x1408; static const uint16_t SCD40_ID = 0x440; diff --git a/esphome/components/script/__init__.py b/esphome/components/script/__init__.py index 2c79e9ed0a1..48a208b7f7f 100644 --- a/esphome/components/script/__init__.py +++ b/esphome/components/script/__init__.py @@ -6,6 +6,7 @@ from esphome.const import CONF_ID, CONF_MODE, CONF_PARAMETERS, CONF_RESTART from esphome.core import CORE, EsphomeError CODEOWNERS = ["@esphome/core"] +DOMAIN = "script" script_ns = cg.esphome_ns.namespace("script") Script = script_ns.class_("Script", automation.Trigger.template()) ScriptExecuteAction = script_ns.class_("ScriptExecuteAction", automation.Action) @@ -15,7 +16,6 @@ RestartScript = script_ns.class_("RestartScript", Script) QueueingScript = script_ns.class_("QueueingScript", Script, cg.Component) ParallelScript = script_ns.class_("ParallelScript", Script) -CONF_SCRIPT = "script" CONF_SINGLE = "single" CONF_QUEUED = "queued" CONF_PARALLEL = "parallel" @@ -35,7 +35,7 @@ PARAMETER_TYPE_TRANSLATIONS = { def get_script(script_id): - scripts = CORE.config.get(CONF_SCRIPT, {}) + scripts = CORE.config.get(DOMAIN, {}) for script in scripts: if script.get(CONF_ID, None) == script_id: return script diff --git a/esphome/components/script/script.cpp b/esphome/components/script/script.cpp index bfe96e1b23a..7174a9a0b42 100644 --- a/esphome/components/script/script.cpp +++ b/esphome/components/script/script.cpp @@ -3,7 +3,7 @@ namespace esphome::script { -static const char *const TAG = "script"; +ESPHOME_LOG_TAG(TAG, "script"); void ScriptLogger::esp_log_(int level, int line, ProgmemStr format, const char *param) { esp_log_printf_(level, TAG, line, format, param); diff --git a/esphome/components/sdl/__init__.py b/esphome/components/sdl/__init__.py index 872d8318500..a8a2391a3c6 100644 --- a/esphome/components/sdl/__init__.py +++ b/esphome/components/sdl/__init__.py @@ -1,6 +1,7 @@ import esphome.codegen as cg CODEOWNERS = ["@clydebarrow"] +DOMAIN = "sdl" SDL_KeyCode = cg.global_ns.enum("SDL_KeyCode") diff --git a/esphome/components/sdm_meter/__init__.py b/esphome/components/sdm_meter/__init__.py index e69de29bb2d..b13d6e18b62 100644 --- a/esphome/components/sdm_meter/__init__.py +++ b/esphome/components/sdm_meter/__init__.py @@ -0,0 +1 @@ +DOMAIN = "sdm_meter" diff --git a/esphome/components/sdm_meter/sdm_meter.cpp b/esphome/components/sdm_meter/sdm_meter.cpp index c1b359cc97a..5389325cc22 100644 --- a/esphome/components/sdm_meter/sdm_meter.cpp +++ b/esphome/components/sdm_meter/sdm_meter.cpp @@ -6,7 +6,7 @@ namespace esphome::sdm_meter { namespace helpers = modbus::helpers; -static const char *const TAG = "sdm_meter"; +ESPHOME_LOG_TAG(TAG, "sdm_meter"); static const uint8_t MODBUS_REGISTER_COUNT = 80; // 80 x 16-bit registers (40 float values) diff --git a/esphome/components/sdp3x/__init__.py b/esphome/components/sdp3x/__init__.py index e69de29bb2d..50ffb263bf5 100644 --- a/esphome/components/sdp3x/__init__.py +++ b/esphome/components/sdp3x/__init__.py @@ -0,0 +1 @@ +DOMAIN = "sdp3x" diff --git a/esphome/components/sdp3x/sdp3x.cpp b/esphome/components/sdp3x/sdp3x.cpp index 7fcd47a265e..98b5d9940a3 100644 --- a/esphome/components/sdp3x/sdp3x.cpp +++ b/esphome/components/sdp3x/sdp3x.cpp @@ -5,7 +5,7 @@ namespace esphome::sdp3x { -static const char *const TAG = "sdp3x.sensor"; +ESPHOME_LOG_TAG(TAG, "sdp3x.sensor"); static const uint16_t SDP3X_SOFT_RESET = 0x0006; static const uint16_t SDP3X_READ_ID1 = 0x367C; static const uint16_t SDP3X_READ_ID2 = 0xE102; diff --git a/esphome/components/sds011/__init__.py b/esphome/components/sds011/__init__.py index e69de29bb2d..386bc61be5d 100644 --- a/esphome/components/sds011/__init__.py +++ b/esphome/components/sds011/__init__.py @@ -0,0 +1 @@ +DOMAIN = "sds011" diff --git a/esphome/components/sds011/sds011.cpp b/esphome/components/sds011/sds011.cpp index dfc7857266d..3d46f0613e0 100644 --- a/esphome/components/sds011/sds011.cpp +++ b/esphome/components/sds011/sds011.cpp @@ -4,7 +4,7 @@ namespace esphome::sds011 { -static const char *const TAG = "sds011"; +ESPHOME_LOG_TAG(TAG, "sds011"); static const uint8_t SDS011_MSG_REQUEST_LENGTH = 19; static const uint8_t SDS011_MSG_RESPONSE_LENGTH = 10; diff --git a/esphome/components/seeed_mr24hpc1/__init__.py b/esphome/components/seeed_mr24hpc1/__init__.py index 56630f18f42..4ec01e46c67 100644 --- a/esphome/components/seeed_mr24hpc1/__init__.py +++ b/esphome/components/seeed_mr24hpc1/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType DEPENDENCIES = ["uart"] # is the code owner of the relevant code base CODEOWNERS = ["@limengdu"] +DOMAIN = "seeed_mr24hpc1" # The current component or platform can be configured or defined multiple times in the same configuration file. MULTI_CONF = True diff --git a/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp index a0dc7ae4cb1..31c8e89af3a 100644 --- a/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp +++ b/esphome/components/seeed_mr24hpc1/seeed_mr24hpc1.cpp @@ -7,7 +7,7 @@ namespace esphome::seeed_mr24hpc1 { -static const char *const TAG = "seeed_mr24hpc1"; +ESPHOME_LOG_TAG(TAG, "seeed_mr24hpc1"); // Prints the component's configuration data. dump_config() prints all of the component's configuration // items in an easy-to-read format, including the configuration key-value pairs. diff --git a/esphome/components/seeed_mr60bha2/__init__.py b/esphome/components/seeed_mr60bha2/__init__.py index 6bf8657af9a..7a85567510a 100644 --- a/esphome/components/seeed_mr60bha2/__init__.py +++ b/esphome/components/seeed_mr60bha2/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@limengdu"] DEPENDENCIES = ["uart"] +DOMAIN = "seeed_mr60bha2" MULTI_CONF = True mr60bha2_ns = cg.esphome_ns.namespace("seeed_mr60bha2") diff --git a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp index cbf0415f6c8..c5d12924583 100644 --- a/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp +++ b/esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp @@ -7,7 +7,7 @@ namespace esphome::seeed_mr60bha2 { -static const char *const TAG = "seeed_mr60bha2"; +ESPHOME_LOG_TAG(TAG, "seeed_mr60bha2"); // Maximum bytes to log in verbose hex output static constexpr size_t MR60BHA2_MAX_LOG_BYTES = 64; diff --git a/esphome/components/seeed_mr60fda2/__init__.py b/esphome/components/seeed_mr60fda2/__init__.py index 159a1ece9c2..cc227415831 100644 --- a/esphome/components/seeed_mr60fda2/__init__.py +++ b/esphome/components/seeed_mr60fda2/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@limengdu"] DEPENDENCIES = ["uart"] +DOMAIN = "seeed_mr60fda2" MULTI_CONF = True mr60fda2_ns = cg.esphome_ns.namespace("seeed_mr60fda2") diff --git a/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp index 2d1cd0fbb4a..332c59061b1 100644 --- a/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp +++ b/esphome/components/seeed_mr60fda2/seeed_mr60fda2.cpp @@ -7,7 +7,7 @@ namespace esphome::seeed_mr60fda2 { -static const char *const TAG = "seeed_mr60fda2"; +ESPHOME_LOG_TAG(TAG, "seeed_mr60fda2"); // Maximum bytes to log in verbose hex output static constexpr size_t MR60FDA2_MAX_LOG_BYTES = 64; diff --git a/esphome/components/selec_meter/__init__.py b/esphome/components/selec_meter/__init__.py index e69de29bb2d..469119b8ac7 100644 --- a/esphome/components/selec_meter/__init__.py +++ b/esphome/components/selec_meter/__init__.py @@ -0,0 +1 @@ +DOMAIN = "selec_meter" diff --git a/esphome/components/selec_meter/selec_meter.cpp b/esphome/components/selec_meter/selec_meter.cpp index 3ad1f8b87c5..a11cc6a008a 100644 --- a/esphome/components/selec_meter/selec_meter.cpp +++ b/esphome/components/selec_meter/selec_meter.cpp @@ -6,7 +6,7 @@ namespace esphome::selec_meter { namespace helpers = modbus::helpers; -static const char *const TAG = "selec_meter"; +ESPHOME_LOG_TAG(TAG, "selec_meter"); static const uint8_t MODBUS_REGISTER_COUNT = 34; // 34 x 16-bit registers diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py index ae78e729e6e..07f86c1330f 100644 --- a/esphome/components/select/__init__.py +++ b/esphome/components/select/__init__.py @@ -30,6 +30,7 @@ from esphome.cpp_types import global_ns from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] +DOMAIN = "select" IS_PLATFORM_COMPONENT = True select_ns = cg.esphome_ns.namespace("select") @@ -95,7 +96,9 @@ def select_schema( @setup_entity("select") async def setup_select_core_(var, config, *, options: list[str]): - cg.add(var.traits.set_options(options)) + if options: + table = cg.shared_progmem_array("select_options", cg.const_char_ptr, options) + cg.add(var.traits.set_options_static(table, len(options))) for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) @@ -187,14 +190,8 @@ automation.register_apply_action( async def select_is_to_code(config, condition_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) if options := config.get(CONF_OPTIONS): - # List of constant options - # Create a constexpr and pass that with a template length - arr_id = ID( - f"{condition_id}_data", - is_declaration=True, - type=global_ns.namespace("constexpr char * const"), - ) - arg = cg.static_const_array(arr_id, cg.ArrayInitializer(*options)) + # Shared flash table of option pointers, length passed as a template argument + arg = cg.shared_progmem_array("select_is_options", cg.const_char_ptr, options) template_arg = TemplateArguments(len(options), *template_arg) else: # Lambda diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index 05a0ee1ed91..4d77c6d6f6c 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -6,7 +6,7 @@ namespace esphome::select { -static const char *const TAG = "select"; +ESPHOME_LOG_TAG(TAG, "select"); void Select::publish_state(const char *state) { auto index = this->index_of(state); diff --git a/esphome/components/select/select_call.cpp b/esphome/components/select/select_call.cpp index 9d2fb725f31..167e6769a4a 100644 --- a/esphome/components/select/select_call.cpp +++ b/esphome/components/select/select_call.cpp @@ -4,7 +4,7 @@ namespace esphome::select { -static const char *const TAG = "select"; +ESPHOME_LOG_TAG(TAG, "select"); SelectCall &SelectCall::set_option(const char *option, size_t len) { return this->with_option(option, len); } diff --git a/esphome/components/select/select_traits.cpp b/esphome/components/select/select_traits.cpp index 67a5118646a..23d1f47cce1 100644 --- a/esphome/components/select/select_traits.cpp +++ b/esphome/components/select/select_traits.cpp @@ -2,13 +2,18 @@ namespace esphome::select { -void SelectTraits::set_options(const std::initializer_list &options) { this->options_ = options; } +// Runtime option lists are copied, since the argument may not outlive the select; one +// out of line copy keeps a single instance of the copy code. +void SelectTraits::set_options_copy_(const char *const *options, size_t count) { + this->options_.assign_copy(options, count); +} + +void SelectTraits::set_options(const std::initializer_list &options) { + this->set_options_copy_(options.begin(), options.size()); +} void SelectTraits::set_options(const FixedVector &options) { - this->options_.init(options.size()); - for (const auto &opt : options) { - this->options_.push_back(opt); - } + this->set_options_copy_(options.begin(), options.size()); } } // namespace esphome::select diff --git a/esphome/components/select/select_traits.h b/esphome/components/select/select_traits.h index e1b261bc96f..47997ac7f28 100644 --- a/esphome/components/select/select_traits.h +++ b/esphome/components/select/select_traits.h @@ -5,14 +5,28 @@ namespace esphome::select { +/// Option strings: a shared codegen table, or a copy of a runtime list. +using SelectOptions = ConstVector; + class SelectTraits { public: + SelectTraits() = default; + SelectTraits(const SelectTraits &) = delete; + SelectTraits &operator=(const SelectTraits &) = delete; + + /// Codegen only: points at a table that outlives the select. Call before any runtime set_options; + /// it does not free a previous copy (generated setup() runs before any lambda or automation). + void set_options_static(const char *const *options, size_t count) { this->options_.assign_static(options, count); } + /// Runtime lists: the pointer list is copied, as before; the strings must still outlive the select. + void set_options(const SelectOptions &options) { this->set_options_copy_(options.data(), options.size()); } void set_options(const std::initializer_list &options); void set_options(const FixedVector &options); - const FixedVector &get_options() const { return this->options_; } + const SelectOptions &get_options() const { return this->options_; } protected: - FixedVector options_; + void set_options_copy_(const char *const *options, size_t count); + + SelectOptions options_; }; } // namespace esphome::select diff --git a/esphome/components/sen0321/__init__.py b/esphome/components/sen0321/__init__.py index 458ffa67f95..905618bfba3 100644 --- a/esphome/components/sen0321/__init__.py +++ b/esphome/components/sen0321/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@notjj"] +DOMAIN = "sen0321" diff --git a/esphome/components/sen0321/sen0321.cpp b/esphome/components/sen0321/sen0321.cpp index e074934d0f0..564e05e0394 100644 --- a/esphome/components/sen0321/sen0321.cpp +++ b/esphome/components/sen0321/sen0321.cpp @@ -4,7 +4,7 @@ namespace esphome::sen0321_sensor { -static const char *const TAG = "sen0321_sensor.sensor"; +ESPHOME_LOG_TAG(TAG, "sen0321_sensor.sensor"); void Sen0321Sensor::setup() { if (!this->write_byte(SENSOR_MODE_REGISTER, SENSOR_MODE_AUTO)) { diff --git a/esphome/components/sen21231/__init__.py b/esphome/components/sen21231/__init__.py index e69de29bb2d..c2802220f61 100644 --- a/esphome/components/sen21231/__init__.py +++ b/esphome/components/sen21231/__init__.py @@ -0,0 +1 @@ +DOMAIN = "sen21231" diff --git a/esphome/components/sen21231/sen21231.cpp b/esphome/components/sen21231/sen21231.cpp index 3f6212e7f2b..29175d845cc 100644 --- a/esphome/components/sen21231/sen21231.cpp +++ b/esphome/components/sen21231/sen21231.cpp @@ -3,7 +3,7 @@ namespace esphome::sen21231_sensor { -static const char *const TAG = "sen21231_sensor.sensor"; +ESPHOME_LOG_TAG(TAG, "sen21231_sensor.sensor"); void Sen21231Sensor::update() { this->read_data_(); } diff --git a/esphome/components/sen5x/__init__.py b/esphome/components/sen5x/__init__.py index e69de29bb2d..e58c474fae4 100644 --- a/esphome/components/sen5x/__init__.py +++ b/esphome/components/sen5x/__init__.py @@ -0,0 +1 @@ +DOMAIN = "sen5x" diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index f8df89ee33f..f192a402887 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -7,7 +7,7 @@ namespace esphome::sen5x { -static const char *const TAG = "sen5x"; +ESPHOME_LOG_TAG(TAG, "sen5x"); static const uint16_t SEN5X_CMD_AUTO_CLEANING_INTERVAL = 0x8004; static const uint16_t SEN5X_CMD_GET_DATA_READY_STATUS = 0x0202; diff --git a/esphome/components/sen6x/__init__.py b/esphome/components/sen6x/__init__.py index e69de29bb2d..abd5e7e50c1 100644 --- a/esphome/components/sen6x/__init__.py +++ b/esphome/components/sen6x/__init__.py @@ -0,0 +1 @@ +DOMAIN = "sen6x" diff --git a/esphome/components/sen6x/sen6x.cpp b/esphome/components/sen6x/sen6x.cpp index ed6cb24c527..91762e0f079 100644 --- a/esphome/components/sen6x/sen6x.cpp +++ b/esphome/components/sen6x/sen6x.cpp @@ -5,7 +5,7 @@ namespace esphome::sen6x { -static const char *const TAG = "sen6x"; +ESPHOME_LOG_TAG(TAG, "sen6x"); static constexpr uint8_t POLL_RETRIES = 24; // 24 attempts static constexpr uint32_t I2C_READ_DELAY = 20; // 20 ms to wait for I2C read to complete diff --git a/esphome/components/sendspin/__init__.py b/esphome/components/sendspin/__init__.py index 42dae464ede..c557a7960d2 100644 --- a/esphome/components/sendspin/__init__.py +++ b/esphome/components/sendspin/__init__.py @@ -1,9 +1,12 @@ from dataclasses import dataclass, field +import logging +from typing import Any from esphome import automation import esphome.codegen as cg from esphome.components import esp32, mdns, network, psram, socket, wifi -from esphome.components.const import CONF_MANUFACTURER +from esphome.components.const import CONF_ENABLED, CONF_MANUFACTURER +from esphome.components.mdns import DOMAIN as MDNS_DOMAIN import esphome.config_validation as cv from esphome.const import ( CONF_BUFFER_SIZE, @@ -11,7 +14,6 @@ from esphome.const import ( CONF_FORMAT, CONF_HEIGHT, CONF_ID, - CONF_MDNS, CONF_MODEL, CONF_NAME, CONF_PROJECT, @@ -25,6 +27,8 @@ from esphome.core import CORE from esphome.cpp_generator import MockObj from esphome.types import ConfigType +_LOGGER = logging.getLogger(__name__) + # mdns for autodiscovery AUTO_LOAD = ["mdns"] CODEOWNERS = ["@kahrendt"] @@ -46,6 +50,33 @@ CONF_FIXED_DELAY = "fixed_delay" CONF_DECODE_MEMORY = "decode_memory" CONF_CODECS = "codecs" +CONF_STATIC_PAIRING_CODE = "static_pairing_code" +CONF_UNPAIRED_ACCESS = "unpaired_access" +CONF_ON_OPEN_PAIRING_WINDOW = "on_open_pairing_window" +CONF_ON_CLOSE_PAIRING_WINDOW = "on_close_pairing_window" +CONF_ON_DISPLAY_PAIRING_CODE = "on_display_pairing_code" +CONF_ON_CLEAR_PAIRING_CODE = "on_clear_pairing_code" +CONF_ON_PAIRING_SUCCEEDED = "on_pairing_succeeded" +CONF_ON_PAIRING_FAILED = "on_pairing_failed" + +# A static pairing code is exactly 8 decimal digits. +STATIC_PAIRING_CODE_DIGITS = 8 + + +def _validate_static_pairing_code(value: Any) -> str: + # string_strict so leading zeros survive and `!secret` works. + value = cv.string_strict(value) + if len(value) != STATIC_PAIRING_CODE_DIGITS or not ( + value.isascii() and value.isdigit() + ): + raise cv.Invalid( + f"{CONF_STATIC_PAIRING_CODE} must be exactly " + f"{STATIC_PAIRING_CODE_DIGITS} decimal digits " + '(quote the value so leading zeros are preserved, e.g. "01234567")' + ) + return value + + # Matches ARTWORK_MAX_SLOTS in sendspin-cpp. MAX_ARTWORK_SLOTS = 4 @@ -76,7 +107,6 @@ OPUS_SAMPLE_RATE = 48000 SendspinImageFormat = sendspin_library_ns.enum("SendspinImageFormat", is_class=True) IMAGE_FORMAT_JPEG = SendspinImageFormat.enum("JPEG") IMAGE_FORMAT_PNG = SendspinImageFormat.enum("PNG") -IMAGE_FORMAT_BMP = SendspinImageFormat.enum("BMP") SendspinImageSource = sendspin_library_ns.enum("SendspinImageSource", is_class=True) IMAGE_SOURCE_ALBUM = SendspinImageSource.enum("ALBUM") @@ -109,6 +139,34 @@ SendspinHub = sendspin_ns.class_( ) +_CALLBACK_AUTOMATIONS = ( + automation.CallbackAutomation( + CONF_ON_OPEN_PAIRING_WINDOW, "add_on_open_pairing_window_callback" + ), + automation.CallbackAutomation( + CONF_ON_CLOSE_PAIRING_WINDOW, "add_on_close_pairing_window_callback" + ), + automation.CallbackAutomation( + CONF_ON_DISPLAY_PAIRING_CODE, + "add_on_display_pairing_code_callback", + [(cg.std_string, "code")], + ), + automation.CallbackAutomation( + CONF_ON_CLEAR_PAIRING_CODE, "add_on_clear_pairing_code_callback" + ), + automation.CallbackAutomation( + CONF_ON_PAIRING_SUCCEEDED, + "add_on_pairing_succeeded_callback", + [(cg.std_string, "server_id")], + ), + automation.CallbackAutomation( + CONF_ON_PAIRING_FAILED, + "add_on_pairing_failed_callback", + [(cg.std_string, "server_id"), (cg.StringRef, "reason")], + ), +) + + @dataclass class SendspinConfiguration: artwork_support: bool = False @@ -116,6 +174,8 @@ class SendspinConfiguration: metadata_support: bool = False player_support: bool = False visualizer_support: bool = False + pairing_code_display_support: bool = False + switch_types: set[str] = field(default_factory=set) artwork_preferences: list[ConfigType] = field(default_factory=list) player_config: ConfigType | None = None @@ -152,6 +212,16 @@ def request_visualizer_support() -> None: _get_data().visualizer_support = True +def request_pairing_code_display_support() -> None: + """Mark that the device can emit a dynamic pairing code (e.g. a pairing_code text sensor).""" + _get_data().pairing_code_display_support = True + + +def request_switch(switch_type: str) -> None: + """Mark that a sendspin switch of this type drives the matching hub setting.""" + _get_data().switch_types.add(switch_type) + + def register_artwork_preference(config: ConfigType) -> int: """Register an artwork slot preference and return the slot it was given. @@ -208,28 +278,85 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_MANUFACTURER): DEVICE_INFO_STRING, cv.Optional(CONF_MODEL): DEVICE_INFO_STRING, cv.Optional(CONF_FIRMWARE_VERSION): DEVICE_INFO_STRING, + cv.Optional(CONF_STATIC_PAIRING_CODE): cv.sensitive( + _validate_static_pairing_code + ), + # No default: the unpaired access switch rejects this key when it is set. + cv.Optional(CONF_UNPAIRED_ACCESS): cv.boolean, + cv.Optional(CONF_ON_OPEN_PAIRING_WINDOW): automation.validate_automation( + {} + ), + cv.Optional(CONF_ON_CLOSE_PAIRING_WINDOW): automation.validate_automation( + {} + ), + cv.Optional(CONF_ON_DISPLAY_PAIRING_CODE): automation.validate_automation( + {} + ), + cv.Optional(CONF_ON_CLEAR_PAIRING_CODE): automation.validate_automation({}), + cv.Optional(CONF_ON_PAIRING_SUCCEEDED): automation.validate_automation({}), + cv.Optional(CONF_ON_PAIRING_FAILED): automation.validate_automation({}), } ), cv.only_on_esp32, + # sendspin-cpp needs noise-c as an ESP-IDF component, which Arduino below IDF 6.0 cannot use. + cv.only_with_framework("esp-idf"), _request_high_performance_networking, ) +def _offers_dynamic_pairing_code(config: ConfigType) -> bool: + """Whether the device can show a dynamic pairing code, so dynamic_pairing_code is advertised.""" + return bool( + config.get(CONF_ON_DISPLAY_PAIRING_CODE) + or _get_data().pairing_code_display_support + ) + + +def _has_pairing_method(config: ConfigType) -> bool: + """Whether the config gives a server any way to pair with the device.""" + return CONF_STATIC_PAIRING_CODE in config or _offers_dynamic_pairing_code(config) + + +def _final_validate(config: ConfigType) -> ConfigType: + dynamic_code = _offers_dynamic_pairing_code(config) + # The protocol allows only one pairing code method. + if dynamic_code and CONF_STATIC_PAIRING_CODE in config: + raise cv.Invalid( + f"'{CONF_STATIC_PAIRING_CODE}' cannot be used with a dynamic pairing code " + f"({CONF_ON_DISPLAY_PAIRING_CODE} or a pairing_code text sensor), since only " + "one pairing code method can be offered", + path=[CONF_STATIC_PAIRING_CODE], + ) + if not config.get(CONF_UNPAIRED_ACCESS, True) and not _has_pairing_method(config): + _LOGGER.warning( + "'%s' is off but there is no pairing method (%s or a dynamic pairing code), so no " + "new server can pair with this device", + CONF_UNPAIRED_ACCESS, + CONF_STATIC_PAIRING_CODE, + ) + return config + + +FINAL_VALIDATE_SCHEMA = _final_validate + + def _request_controller_role(config: ConfigType) -> ConfigType: """Request the controller role for the sendspin.switch action.""" request_controller_support() return config +# Selects the hub. sendspin.switch adds the controller role it needs; the pairing window actions need no role. +SENDSPIN_HUB_ACTION_SCHEMA = automation.maybe_simple_id( + cv.Schema( + { + cv.GenerateID(): cv.use_id(SendspinHub), + } + ) +) + SENDSPIN_SIMPLE_ACTION_SCHEMA = cv.All( - automation.maybe_simple_id( - cv.Schema( - { - cv.GenerateID(): cv.use_id(SendspinHub), - } - ) - ), - _request_controller_role, + SENDSPIN_HUB_ACTION_SCHEMA, _request_controller_role ) @@ -240,6 +367,19 @@ automation.register_apply_action( ) +automation.register_apply_action( + "sendspin.confirm_pairing_window", + SENDSPIN_HUB_ACTION_SCHEMA, + automation.ApplyCall("confirm_pairing_window()"), +) + +automation.register_apply_action( + "sendspin.cancel_pairing_window", + SENDSPIN_HUB_ACTION_SCHEMA, + automation.ApplyCall("cancel_pairing_window()"), +) + + async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) @@ -264,20 +404,38 @@ async def to_code(config: ConfigType) -> None: if value: cg.add(setter(value)) - # sendspin-cpp library - esp32.add_idf_component(name="sendspin/sendspin-cpp", ref="0.8.0") + if (code := config.get(CONF_STATIC_PAIRING_CODE)) is not None: + cg.add(var.set_static_pairing_code(code)) - cg.add_define("USE_SENDSPIN", True) # for MDNS + # The client starts once both are set: here, or by the switch that drives the setting. + switch_types = _get_data().switch_types + if CONF_ENABLED not in switch_types: + cg.add(var.set_enabled(True)) + if CONF_UNPAIRED_ACCESS not in switch_types: + cg.add(var.set_unpaired_access_enabled(config.get(CONF_UNPAIRED_ACCESS, True))) + + if _offers_dynamic_pairing_code(config): + cg.add(var.set_pairing_code_display_supported(True)) + + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) + + # sendspin-cpp library + esp32.add_idf_component(name="sendspin/sendspin-cpp", ref="0.9.3") + # esp_websocket_client links esp_tls even for ws:// connections. + esp32.request_tls() + + cg.add_define("USE_SENDSPIN", True) # for MDNS and the native API # Service starts disabled and the hub enables it; always advertised where unsupported if mdns.request_service_enable_disable(): - mdns_var = await cg.get_variable(CORE.config[CONF_MDNS][CONF_ID]) + mdns_var = await cg.get_variable(CORE.config[MDNS_DOMAIN][CONF_ID]) cg.add(var.set_mdns(mdns_var)) data = _get_data() - # The color role is not yet wired up in ESPHome; disable it in the library for now. + # The color and source roles are not yet wired up in ESPHome; disable them in the library for now. esp32.add_idf_sdkconfig_option("CONFIG_SENDSPIN_ENABLE_COLOR", False) + esp32.add_idf_sdkconfig_option("CONFIG_SENDSPIN_ENABLE_SOURCE", False) # Configure Sendspin roles based on requested features (ESPHome internally via USE_SENDSPIN_*) # and disable building unused code paths in the sendspin-cpp library (IDF SDKConfig via CONFIG_SENDSPIN_ENABLE_*). @@ -351,7 +509,8 @@ async def to_code(config: ConfigType) -> None: ("audio_formats", audio_format_structs), ("audio_buffer_capacity", player_cfg[CONF_BUFFER_SIZE]), ("fixed_delay_us", player_cfg[CONF_FIXED_DELAY]), - ("initial_static_delay_ms", player_cfg[CONF_INITIAL_STATIC_DELAY]), + # The released YAML key name is kept for compatibility. + ("initial_output_delay_ms", player_cfg[CONF_INITIAL_STATIC_DELAY]), ("psram_stack", psram_stack), ] if (decode_memory := player_cfg.get(CONF_DECODE_MEMORY)) is not None: @@ -366,6 +525,9 @@ async def to_code(config: ConfigType) -> None: else: esp32.add_idf_sdkconfig_option("CONFIG_SENDSPIN_ENABLE_PLAYER", False) + if not data.player_support or CODEC_OPUS not in data.player_config[CONF_CODECS]: + esp32.add_idf_sdkconfig_option("CONFIG_SENDSPIN_ENABLE_OPUS", False) + if data.visualizer_support: cg.add_define("USE_SENDSPIN_VISUALIZER", True) else: diff --git a/esphome/components/sendspin/image/__init__.py b/esphome/components/sendspin/image/__init__.py index e26e8204aaf..a5336d5e052 100644 --- a/esphome/components/sendspin/image/__init__.py +++ b/esphome/components/sendspin/image/__init__.py @@ -1,5 +1,7 @@ """Sendspin image platform.""" +from typing import Any + from esphome import automation import esphome.codegen as cg from esphome.components import runtime_image @@ -21,7 +23,6 @@ from esphome.types import ConfigType from .. import ( CONF_DISPLAY_OFFSET, CONF_SENDSPIN_ID, - IMAGE_FORMAT_BMP, IMAGE_FORMAT_JPEG, IMAGE_FORMAT_PNG, IMAGE_SOURCE_ALBUM, @@ -57,9 +58,16 @@ _FORMAT_TO_SENDSPIN_ENUM = { "JPEG": IMAGE_FORMAT_JPEG, "JPG": IMAGE_FORMAT_JPEG, "PNG": IMAGE_FORMAT_PNG, - "BMP": IMAGE_FORMAT_BMP, } + +# Remove before 2027.4.0 +def _reject_bmp(value: Any) -> Any: + if isinstance(value, str) and value.upper() == "BMP": + raise cv.Invalid("BMP artwork is no longer supported, use JPEG or PNG instead.") + return value + + # The library's SendspinImageSource::NONE is its internal "unset" sentinel; a slot advertising it # would never receive artwork while still paying for two frame buffers, so it is not offered here. IMAGE_SOURCES = { @@ -121,7 +129,9 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(CONF_SENDSPIN_ID): cv.use_id(SendspinHub), # Narrow runtime_image's format list to what the library can request, so the # accepted set and the enum map below cannot drift apart. - cv.Required(CONF_FORMAT): cv.one_of(*_FORMAT_TO_SENDSPIN_ENUM, upper=True), + cv.Required(CONF_FORMAT): cv.All( + _reject_bmp, cv.one_of(*_FORMAT_TO_SENDSPIN_ENUM, upper=True) + ), cv.Required(CONF_RESIZE): cv.dimensions, cv.Required(CONF_CURRENT_IMAGE): _IMAGE_SCHEMA, cv.Optional(CONF_TRANSITION_IMAGE): _IMAGE_SCHEMA, diff --git a/esphome/components/sendspin/image/sendspin_image.cpp b/esphome/components/sendspin/image/sendspin_image.cpp index 558a292d5b0..a93c2eb4430 100644 --- a/esphome/components/sendspin/image/sendspin_image.cpp +++ b/esphome/components/sendspin/image/sendspin_image.cpp @@ -1,6 +1,6 @@ #include "sendspin_image.h" -#if defined(USE_ESP32) && defined(USE_SENDSPIN_ARTWORK) +#if defined(USE_ESP_IDF) && defined(USE_SENDSPIN_ARTWORK) #include "esphome/core/log.h" @@ -8,7 +8,9 @@ namespace esphome::sendspin_ { -static const char *const TAG = "sendspin.image"; +ESPHOME_LOG_TAG(TAG, "sendspin.image"); + +static constexpr uint32_t TRANSITION_ACK_TIMEOUT_ID = 0; // How long a displayed frame may wait for sendspin.image.transition_finished before a warning // names the missing ack. Generous next to a typical fade of a second or two. @@ -125,7 +127,7 @@ bool SendspinImageSlot::decode_frame_(const uint8_t *data, size_t length, const } // A decode that asked for other dimensions had the buffer taken away from it, so it painted - // nothing (or stopped partway). JPEG and BMP report that as an error above; PNG carries on + // nothing (or stopped partway). JPEG reports that as an error above; PNG carries on // regardless, so the frame is dropped here. return this->decode_sink_.decoded_into(target); } @@ -163,7 +165,7 @@ void SendspinImageSlot::on_display_(uint32_t lateness_ms) { // that never reaches the action stalls the slot with nothing in the log. Name the cause after // a generous wait. Arming again replaces the previous timeout, so it cannot fire for a frame // that was already acked and superseded. - this->set_timeout("transition_ack", TRANSITION_ACK_WARNING_MS, [this]() { + this->set_timeout(TRANSITION_ACK_TIMEOUT_ID, TRANSITION_ACK_WARNING_MS, [this]() { if (this->transition_pending_) { ESP_LOGW(TAG, "Slot %u: displayed artwork was never acknowledged; no new artwork will arrive until " diff --git a/esphome/components/sendspin/image/sendspin_image.h b/esphome/components/sendspin/image/sendspin_image.h index 2f6f4e8a4d9..43dd4ed66b3 100644 --- a/esphome/components/sendspin/image/sendspin_image.h +++ b/esphome/components/sendspin/image/sendspin_image.h @@ -2,7 +2,7 @@ #include "esphome/core/defines.h" -#if defined(USE_ESP32) && defined(USE_SENDSPIN_ARTWORK) +#if defined(USE_ESP_IDF) && defined(USE_SENDSPIN_ARTWORK) #include "esphome/components/image/image.h" #include "esphome/components/runtime_image/runtime_image.h" @@ -29,7 +29,7 @@ class ArtworkDecodeSink : public runtime_image::RuntimeImage { /// @brief True when the decode ended with the given buffer still in place. /// /// An external buffer is dropped rather than resized, so a decode that wanted other dimensions - /// leaves the sink holding nothing. The JPEG and BMP decoders report that as a decode error, but + /// leaves the sink holding nothing. The JPEG decoder reports that as a decode error, but /// the PNG decoder ignores it and reports success, so the outcome is checked here as well. bool decoded_into(const uint8_t *buffer) const { return this->buffer_ == buffer; } }; diff --git a/esphome/components/sendspin/media_player/sendspin_media_player.cpp b/esphome/components/sendspin/media_player/sendspin_media_player.cpp index 59ead1bb539..8c82022f1e8 100644 --- a/esphome/components/sendspin/media_player/sendspin_media_player.cpp +++ b/esphome/components/sendspin/media_player/sendspin_media_player.cpp @@ -1,6 +1,6 @@ #include "sendspin_media_player.h" -#if defined(USE_ESP32) && defined(USE_MEDIA_PLAYER) && defined(USE_SENDSPIN_CONTROLLER) +#if defined(USE_ESP_IDF) && defined(USE_MEDIA_PLAYER) && defined(USE_SENDSPIN_CONTROLLER) #include "esphome/core/application.h" #include "esphome/core/log.h" @@ -16,7 +16,7 @@ namespace esphome::sendspin_ { -static const char *const TAG = "sendspin.media_player"; +ESPHOME_LOG_TAG(TAG, "sendspin.media_player"); // THREAD CONTEXT: Main loop. The callbacks registered here also fire on the main loop, // since SendspinHub dispatches group updates and controller state from client_->loop(). diff --git a/esphome/components/sendspin/media_player/sendspin_media_player.h b/esphome/components/sendspin/media_player/sendspin_media_player.h index ff764731898..bc07254efe4 100644 --- a/esphome/components/sendspin/media_player/sendspin_media_player.h +++ b/esphome/components/sendspin/media_player/sendspin_media_player.h @@ -2,7 +2,7 @@ #include "esphome/core/defines.h" -#if defined(USE_ESP32) && defined(USE_MEDIA_PLAYER) && defined(USE_SENDSPIN_CONTROLLER) +#if defined(USE_ESP_IDF) && defined(USE_MEDIA_PLAYER) && defined(USE_SENDSPIN_CONTROLLER) #include "esphome/components/media_player/media_player.h" #include "esphome/components/sendspin/sendspin_hub.h" diff --git a/esphome/components/sendspin/media_source/__init__.py b/esphome/components/sendspin/media_source/__init__.py index 743d0bb62cf..31c454fb71c 100644 --- a/esphome/components/sendspin/media_source/__init__.py +++ b/esphome/components/sendspin/media_source/__init__.py @@ -11,7 +11,9 @@ from esphome.const import ( from esphome.types import ConfigType from .. import ( + CODEC_FLAC, CODEC_OPUS, + CODEC_PCM, CODECS, CONF_CODECS, CONF_DECODE_MEMORY, @@ -58,6 +60,11 @@ def _resolve_codecs(config: ConfigType) -> ConfigType: f"Codec '{CODEC_OPUS}' requires a {CONF_SAMPLE_RATE} of {OPUS_SAMPLE_RATE}", path=[CONF_CODECS], ) + if CODEC_FLAC not in codecs and CODEC_PCM not in codecs: + raise cv.Invalid( + f"Codecs must include '{CODEC_FLAC}' or '{CODEC_PCM}'", + path=[CONF_CODECS], + ) return config diff --git a/esphome/components/sendspin/media_source/sendspin_media_source.cpp b/esphome/components/sendspin/media_source/sendspin_media_source.cpp index c3fb1fe1cbd..5e5cf187994 100644 --- a/esphome/components/sendspin/media_source/sendspin_media_source.cpp +++ b/esphome/components/sendspin/media_source/sendspin_media_source.cpp @@ -1,6 +1,6 @@ #include "sendspin_media_source.h" -#if defined(USE_ESP32) && defined(USE_SENDSPIN_CONTROLLER) && defined(USE_SENDSPIN_PLAYER) +#if defined(USE_ESP_IDF) && defined(USE_SENDSPIN_CONTROLLER) && defined(USE_SENDSPIN_PLAYER) #include "esphome/components/audio/audio.h" #include "esphome/core/log.h" @@ -9,7 +9,7 @@ namespace esphome::sendspin_ { -static const char *const TAG = "sendspin.media_source"; +ESPHOME_LOG_TAG(TAG, "sendspin.media_source"); static constexpr char URI_PREFIX[] = "sendspin://"; @@ -24,7 +24,7 @@ void SendspinMediaSource::setup() { // Push cached states to player role. They may have been set before setup() ran. this->player_role_->update_volume(std::roundf(this->cached_volume_ * 100.0f)); this->player_role_->update_muted(this->cached_muted_); - this->player_role_->set_static_delay_adjustable(this->static_delay_adjustable_); + this->player_role_->set_output_delay_adjustable(this->static_delay_adjustable_); } void SendspinMediaSource::dump_config() { @@ -35,7 +35,7 @@ void SendspinMediaSource::dump_config() { void SendspinMediaSource::set_static_delay_adjustable(bool adjustable) { this->static_delay_adjustable_ = adjustable; if (this->player_role_) { - this->player_role_->set_static_delay_adjustable(adjustable); + this->player_role_->set_output_delay_adjustable(adjustable); } } @@ -102,8 +102,8 @@ void SendspinMediaSource::handle_command(media_source::MediaSourceCommand comman if (!this->pending_start_) { // Ignore stop commands if we have a pending start, since the orchestrator may send a stop command before // play_uri - ESP_LOGD(TAG, "Received STOP command, updating Sendspin state to EXTERNAL_SOURCE"); - this->parent_->update_state(sendspin::SendspinClientState::EXTERNAL_SOURCE); + ESP_LOGD(TAG, "Received STOP command, leaving the Sendspin group"); + this->parent_->leave_group(); } break; } @@ -185,8 +185,6 @@ size_t SendspinMediaSource::on_audio_write(uint8_t *data, size_t length, uint32_ // THREAD CONTEXT: Main loop (PlayerRoleListener lifecycle callback) void SendspinMediaSource::on_stream_start() { - this->parent_->update_state(sendspin::SendspinClientState::SYNCHRONIZED); - if (!this->pending_start_) { // Dedup rapid on_stream_start() calls this->pending_start_ = true; @@ -211,4 +209,4 @@ void SendspinMediaSource::on_mute_changed(bool muted) { this->request_mute_(mute } // namespace esphome::sendspin_ -#endif // USE_ESP32 && USE_SENDSPIN_PLAYER && USE_SENDSPIN_CONTROLLER +#endif // USE_ESP_IDF && USE_SENDSPIN_PLAYER && USE_SENDSPIN_CONTROLLER diff --git a/esphome/components/sendspin/media_source/sendspin_media_source.h b/esphome/components/sendspin/media_source/sendspin_media_source.h index 1c5cb625bf4..f0800dbcb1c 100644 --- a/esphome/components/sendspin/media_source/sendspin_media_source.h +++ b/esphome/components/sendspin/media_source/sendspin_media_source.h @@ -2,7 +2,7 @@ #include "esphome/core/defines.h" -#if defined(USE_ESP32) && defined(USE_SENDSPIN_CONTROLLER) && defined(USE_SENDSPIN_PLAYER) +#if defined(USE_ESP_IDF) && defined(USE_SENDSPIN_CONTROLLER) && defined(USE_SENDSPIN_PLAYER) #include "esphome/components/sendspin/sendspin_hub.h" diff --git a/esphome/components/sendspin/sendspin_hub.cpp b/esphome/components/sendspin/sendspin_hub.cpp index 15d358d50fb..8b5eaeeed23 100644 --- a/esphome/components/sendspin/sendspin_hub.cpp +++ b/esphome/components/sendspin/sendspin_hub.cpp @@ -1,6 +1,6 @@ #include "sendspin_hub.h" -#ifdef USE_ESP32 +#ifdef USE_ESP_IDF #include "esphome/components/network/util.h" #ifdef USE_ETHERNET @@ -17,26 +17,122 @@ #include +#include +#include + namespace esphome::sendspin_ { -static const char *const TAG = "sendspin.hub"; +ESPHOME_LOG_TAG(TAG, "sendspin.hub"); + +namespace { + +// The reason passed to on_pairing_failed automations, in the protocol's own spelling. +StringRef pair_abort_reason_to_string(sendspin::SendspinPairAbortReason reason) { + using sendspin::SendspinPairAbortReason; + switch (reason) { + case SendspinPairAbortReason::ATTEMPT_TIMEOUT: + return StringRef::from_lit("attempt_timeout"); + case SendspinPairAbortReason::CONCURRENT_ATTEMPT: + return StringRef::from_lit("concurrent_attempt"); + case SendspinPairAbortReason::METHOD_NOT_SUPPORTED: + return StringRef::from_lit("method_not_supported"); + case SendspinPairAbortReason::PAIRING_CODE_MISMATCH: + return StringRef::from_lit("pairing_code_mismatch"); + case SendspinPairAbortReason::USER_CANCELLED: + return StringRef::from_lit("user_cancelled"); + case SendspinPairAbortReason::UNKNOWN: + break; + } + return StringRef::from_lit("unknown"); +} + +// Zeroes through a volatile pointer so dead-store elimination cannot drop it. +void secure_wipe(void *data, size_t len) { + volatile auto *p = static_cast(data); + for (size_t i = 0; i < len; ++i) { + p[i] = 0; + } +} + +// Call once per key, from setup(). +ESPPreferenceObject make_blob_pref(const std::string &name, size_t size) { + return global_preferences->make_preference(size, fnv1a_hash("sendspin_" + name)); +} + +// Loads straight into the returned buffer, which the library wipes after use. A failed load can +// leave part of a stored private key or PSK behind, so that path wipes it here. This only covers +// this buffer: the preference backend keeps its own copy of a pending write until it syncs. +std::optional> read_blob(ESPPreferenceObject &pref, size_t size) { + std::vector out(size); + if (!pref.load(out.data(), out.size())) { + secure_wipe(out.data(), out.size()); + return std::nullopt; + } + return out; +} + +// The library always writes a key's fixed size; any other length is misuse and is not stored. +bool write_blob(ESPPreferenceObject &pref, const char *key, size_t size, const uint8_t *data, size_t len) { + if (len != size) { + ESP_LOGW(TAG, "\"%s\" blob of %zu bytes does not match its size of %zu; rejecting write", key, len, size); + return false; + } + if (!pref.save(data, len)) { + ESP_LOGW(TAG, "Failed to persist \"%s\" blob (%zu bytes)", key, len); + return false; + } + return true; +} + +// "rec_" to n; nullopt for any other key or a slot with no storage here. +std::optional parse_record_slot(const std::string &key) { + const char *const prefix = sendspin::persistence_keys::RECORD_SLOT_PREFIX; + const size_t prefix_len = std::strlen(prefix); + if (key.size() <= prefix_len || key.compare(0, prefix_len, prefix) != 0) { + return std::nullopt; + } + size_t slot = 0; + for (size_t i = prefix_len; i < key.size(); i++) { + const char c = key[i]; + if (c < '0' || c > '9') { + return std::nullopt; + } + slot = slot * 10 + static_cast(c - '0'); + if (slot >= SENDSPIN_RECORD_SLOTS) { + return std::nullopt; + } + } + return slot; +} + +} // namespace #ifdef USE_SENDSPIN_ARTWORK // Indexed by the library enums, which start at zero and are contiguous. static const char *const IMAGE_SOURCE_NAMES[] = {"ALBUM", "ARTIST", "NONE"}; -static const char *const IMAGE_FORMAT_NAMES[] = {"JPEG", "PNG", "BMP"}; +static const char *const IMAGE_FORMAT_NAMES[] = {"JPEG", "PNG"}; #endif +SendspinHub *global_sendspin_hub = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +SendspinHub::SendspinHub() { global_sendspin_hub = this; } + void SendspinHub::setup() { auto config = this->build_client_config_(); this->client_ = std::make_unique(std::move(config)); - // Set up persistence (preferences must be initialized before providers are added to the client) - this->last_played_server_pref_ = - global_preferences->make_preference(fnv1a_hash("sendspin_last_played")); -#ifdef USE_SENDSPIN_PLAYER - this->static_delay_pref_ = global_preferences->make_preference(fnv1a_hash("sendspin_static_delay")); -#endif + // Set up persistence (preferences must be initialized before providers are added to the client). These key names + // are frozen; see PREFERENCE KEYS in sendspin_hub.h. + namespace keys = sendspin::persistence_keys; + this->keypair_pref_ = make_blob_pref("keypair", keys::KEYPAIR_SIZE); + this->pairing_psk_pref_ = make_blob_pref("pair_psk", keys::PAIRING_PSK_SIZE); + this->last_played_pref_ = make_blob_pref("last_played", keys::LAST_PLAYED_SIZE); + // Released firmware's name, so a calibrated delay survives. + this->output_delay_pref_ = make_blob_pref("static_delay", keys::OUTPUT_DELAY_SIZE); + for (size_t slot = 0; slot < SENDSPIN_RECORD_SLOTS; slot++) { + this->record_slot_prefs_[slot] = make_blob_pref(keys::record_slot_key(slot), keys::RECORD_SLOT_SIZE); + } + this->record_order_pref_ = make_blob_pref(keys::RECORD_ORDER, SENDSPIN_RECORD_SLOTS); // Wire providers and client listener this->client_->set_listener(this); @@ -62,14 +158,15 @@ void SendspinHub::setup() { this->client_->add_player(this->player_config_).set_listener(this->player_listener_); #endif -#ifndef USE_SENDSPIN_SWITCH - this->enabled_ = true; -#endif + // Set before setup() by codegen; an unpaired access switch sets it later, from its own setup(). + if (this->unpaired_access_.has_value()) { + this->client_->set_unpaired_access_enabled(*this->unpaired_access_); + } } void SendspinHub::loop() { - if (this->enabled_.has_value() && this->enabled_.value() != this->client_->is_started() && - !this->status_has_error()) { + if (this->enabled_.has_value() && this->unpaired_access_.has_value() && + this->enabled_.value() != this->client_->is_started() && !this->status_has_error()) { if (!this->enabled_.value()) { this->client_->stop(); } else if (!this->client_->start()) { @@ -83,17 +180,37 @@ void SendspinHub::loop() { #endif } +// Sends each server a goodbye and writes what the client still owed its provider. Wi-Fi sets up +// before this hub, so it shuts down after it and the goodbyes can still go out. +void SendspinHub::on_shutdown() { + // Keeps loop() from starting the client again. + this->enabled_ = false; + if (this->is_client_running()) { + this->client_->stop(); + } +} + void SendspinHub::dump_config() { + // client_id exists only once start() has run. + const char *client_id = "(unavailable)"; + if (this->client_ != nullptr && !this->client_->client_id().empty()) { + client_id = this->client_->client_id().c_str(); + } char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, "Sendspin Hub:\n" " Client ID: %s\n" + " MAC address: %s\n" " Manufacturer: %s\n" " Model: %s\n" " Firmware version: %s\n" - " Task stack in PSRAM: %s", - get_client_id_into_buffer(mac_buf), this->manufacturer_, this->get_product_name_(), - this->firmware_version_, YESNO(this->task_stack_in_psram_)); + " Task stack in PSRAM: %s\n" + " Unpaired access: %s\n" + " Pairing code method: %s", + client_id, get_mac_address_into_buffer(mac_buf), this->manufacturer_, this->get_product_name_(), + this->firmware_version_, YESNO(this->task_stack_in_psram_), + YESNO(this->client_ != nullptr && this->client_->is_unpaired_access_enabled()), + this->pairing_code_method_()); #ifdef USE_SENDSPIN_ARTWORK // Slot indices come from the order the image platform entries were declared, so the log is the @@ -108,7 +225,7 @@ void SendspinHub::dump_config() { #endif } -// THREAD CONTEXT: Main loop (invoked from Sendspin components) +// THREAD CONTEXT: Main loop (invoked from codegen before setup(), or from Sendspin components) void SendspinHub::set_enabled(bool enabled) { if (this->status_has_error()) { ESP_LOGE(TAG, "Cannot %s: Sendspin failed to start, reboot to retry", @@ -118,6 +235,23 @@ void SendspinHub::set_enabled(bool enabled) { this->enabled_ = enabled; } +// THREAD CONTEXT: Main loop +std::optional SendspinHub::get_pairing_token() const { + if (this->client_ == nullptr) { + return std::nullopt; + } + return this->client_->pairing_token(); +} + +// THREAD CONTEXT: Main loop (invoked from codegen before setup(), or from Sendspin components) +void SendspinHub::set_unpaired_access_enabled(bool enabled) { + this->unpaired_access_ = enabled; + // Before setup() there is no client yet; setup() applies the stored value. + if (this->client_ != nullptr) { + this->client_->set_unpaired_access_enabled(enabled); + } +} + #ifdef USE_MDNS_SUPPORTS_ENABLE_DISABLE // THREAD CONTEXT: Main loop void SendspinHub::update_mdns_service_() { @@ -154,22 +288,58 @@ void SendspinHub::disconnect_from_server(sendspin::SendspinGoodbyeReason reason) } // THREAD CONTEXT: Main loop (invoked from Sendspin components) -void SendspinHub::update_state(sendspin::SendspinClientState state) { - if (this->is_client_running()) { - this->client_->update_state(state); +void SendspinHub::leave_group() { + if (this->is_client_running() && + this->client_->get_group_state().playback_state == sendspin::SendspinPlaybackState::PLAYING) { + this->client_->leave(); } } -const char *SendspinHub::get_client_id_into_buffer(std::span buf) { - // The server matches client_id against the L2 source MAC of the device's multicast traffic. +// THREAD CONTEXT: Main loop (invoked from the sendspin.confirm_pairing_window action) +void SendspinHub::confirm_pairing_window() { + if (this->is_client_running()) { + this->client_->confirm_pairing_window(); + } +} + +// THREAD CONTEXT: Main loop (invoked from the sendspin.cancel_pairing_window action) +void SendspinHub::cancel_pairing_window() { + if (this->is_client_running()) { + this->client_->cancel_pairing_window(); + } +} + +const char *SendspinHub::get_mac_address_into_buffer(std::span buf) { + // The server matches this MAC against the L2 source MAC of the device's multicast traffic. // ESP-IDF derives the ethernet MAC as base+3 by default on ESP32-S3, so we cannot use the // eFuse base MAC when ethernet is the active interface. #ifdef USE_ETHERNET if (ethernet::global_eth_component != nullptr) { - return ethernet::global_eth_component->get_eth_mac_address_pretty_into_buffer(buf); + ethernet::global_eth_component->get_eth_mac_address_pretty_into_buffer(buf); + } else { + get_mac_address_pretty_into_buffer(buf); } +#else + get_mac_address_pretty_into_buffer(buf); #endif - return get_mac_address_pretty_into_buffer(buf); + // The pretty format is uppercase, but SendspinClientConfig::mac_address must be lowercase. + for (char &c : buf) { + if (c >= 'A' && c <= 'F') { + c += 'a' - 'A'; + } + } + return buf.data(); +} + +// Validation rejects a static code together with the dynamic code, so at most one is set. +const char *SendspinHub::pairing_code_method_() const { + if (this->pairing_code_display_supported_) { + return LOG_STR_LITERAL("dynamic"); + } + if (this->static_pairing_code_ != nullptr) { + return LOG_STR_LITERAL("static"); + } + return LOG_STR_LITERAL("none"); } const char *SendspinHub::get_product_name_() const { @@ -180,12 +350,26 @@ sendspin::SendspinClientConfig SendspinHub::build_client_config_() { sendspin::SendspinClientConfig config; char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; - config.client_id = SendspinHub::get_client_id_into_buffer(mac_buf); + config.mac_address = SendspinHub::get_mac_address_into_buffer(mac_buf); config.name = App.get_friendly_name(); config.product_name = this->get_product_name_(); config.manufacturer = this->manufacturer_; config.software_version = this->firmware_version_; config.httpd_psram_stack = this->task_stack_in_psram_; + config.protocol_task_psram_stack = this->task_stack_in_psram_; + config.max_pairing_records = SENDSPIN_RECORD_SLOTS; + + // The dynamic code needs a channel and a format. Only digits, since automations get the bare string and could not + // tell a QR code token apart. + if (this->pairing_code_display_supported_) { + config.pairing_code_out_channels = {sendspin::SendspinPairingCodeChannel::DISPLAY}; + config.pairing_code_formats = {sendspin::SendspinPairingCodeFormat::DIGITS}; + } + + if (this->static_pairing_code_ != nullptr) { + config.static_pairing_code_locations = {"operator"}; + config.static_pairing_code = this->static_pairing_code_; + } return config; } @@ -215,35 +399,77 @@ void SendspinHub::on_release_high_performance() { #endif } +void SendspinHub::on_open_pairing_window() { this->open_pairing_window_callbacks_.call(); } + +void SendspinHub::on_close_pairing_window() { this->close_pairing_window_callbacks_.call(); } + +// Only digits are offered, and the library refuses an activation in any other format. +void SendspinHub::on_display_pairing_code(const std::string &code, sendspin::SendspinPairingCodeFormat /*format*/) { + this->display_pairing_code_callbacks_.call(code); +} + +void SendspinHub::on_clear_pairing_code() { this->clear_pairing_code_callbacks_.call(); } + +void SendspinHub::on_pairing_succeeded(const std::string &server_id) { + this->pairing_succeeded_callbacks_.call(server_id); +} + +void SendspinHub::on_pairing_failed(const std::string &server_id, sendspin::SendspinPairAbortReason reason) { + this->pairing_failed_callbacks_.call(server_id, pair_abort_reason_to_string(reason)); +} + // --- SendspinNetworkProvider override --- -// THREAD CONTEXT: Main loop (polled by client_->loop()) +// THREAD CONTEXT: Main loop (polled by start() and client_->loop()) bool SendspinHub::is_network_ready() { return network::is_connected(); } // --- SendspinPersistenceProvider overrides --- +// THREAD CONTEXT: Main loop (the library makes every provider call there, including from start() and stop()) -// THREAD CONTEXT: Main loop (invoked by client_->loop() during lifecycle events) -bool SendspinHub::save_last_server_hash(uint32_t hash) { - LastPlayedServerPref pref{.server_id_hash = hash}; - bool ok = this->last_played_server_pref_.save(&pref); - if (ok) { - ESP_LOGD(TAG, "Persisted last played server hash: 0x%08" PRIX32, hash); - } else { - ESP_LOGW(TAG, "Failed to persist last played server hash"); +std::pair SendspinHub::pref_for_key_(const std::string &key) { + namespace keys = sendspin::persistence_keys; + if (auto slot = parse_record_slot(key); slot.has_value()) { + return {&this->record_slot_prefs_[*slot], keys::RECORD_SLOT_SIZE}; } - return ok; + if (key == keys::RECORD_ORDER) { + return {&this->record_order_pref_, SENDSPIN_RECORD_SLOTS}; + } + if (key == keys::KEYPAIR) { + return {&this->keypair_pref_, keys::KEYPAIR_SIZE}; + } + if (key == keys::PAIRING_PSK) { + return {&this->pairing_psk_pref_, keys::PAIRING_PSK_SIZE}; + } + if (key == keys::LAST_PLAYED) { + return {&this->last_played_pref_, keys::LAST_PLAYED_SIZE}; + } + if (key == keys::OUTPUT_DELAY) { + return {&this->output_delay_pref_, keys::OUTPUT_DELAY_SIZE}; + } + return {nullptr, 0}; } -// THREAD CONTEXT: Main loop (invoked by client_->loop() during lifecycle events) -std::optional SendspinHub::load_last_server_hash() { - LastPlayedServerPref pref{}; - if (this->last_played_server_pref_.load(&pref)) { - ESP_LOGI(TAG, "Loaded last played server hash: 0x%08" PRIX32, pref.server_id_hash); - return pref.server_id_hash; +std::optional> SendspinHub::load_blob(const std::string &key) { + auto [pref, size] = this->pref_for_key_(key); + if (pref == nullptr) { + ESP_LOGW(TAG, "load_blob: unknown key \"%s\"", key.c_str()); + return std::nullopt; } - return std::nullopt; + return read_blob(*pref, size); } +bool SendspinHub::save_blob(const std::string &key, const uint8_t *data, size_t len) { + auto [pref, size] = this->pref_for_key_(key); + if (pref == nullptr) { + ESP_LOGW(TAG, "save_blob: unknown key \"%s\"", key.c_str()); + return false; + } + return write_blob(*pref, key.c_str(), size, data, len); +} + +// Writes every component's queued preferences, so a failure may belong to another component. +bool SendspinHub::commit() { return global_preferences->sync(); } + // --- Sendspin role specific methods/overrides --- #ifdef USE_SENDSPIN_ARTWORK @@ -285,11 +511,7 @@ void SendspinHub::send_client_command(sendspin::SendspinControllerCommand comman } // THREAD CONTEXT: Main loop (invoked from the sendspin.switch action) -void SendspinHub::switch_client() { - // Clear any EXTERNAL_SOURCE state so the switch command is followed - this->update_state(sendspin::SendspinClientState::SYNCHRONIZED); - this->send_client_command(sendspin::SendspinControllerCommand::SWITCH); -} +void SendspinHub::switch_client() { this->send_client_command(sendspin::SendspinControllerCommand::SWITCH); } // THREAD CONTEXT: Main loop (ControllerRoleListener override, fired from client_->loop()) void SendspinHub::on_controller_state(const sendspin::ServerStateControllerObject &state) { @@ -332,31 +554,8 @@ sendspin::PlayerRole *SendspinHub::get_player_role() { } return nullptr; } - -// THREAD CONTEXT: Main loop (SendspinPersistenceProvider override) -bool SendspinHub::save_static_delay(uint16_t delay_ms) { - StaticDelayPref pref{.delay_ms = delay_ms}; - bool ok = this->static_delay_pref_.save(&pref); - if (ok) { - ESP_LOGD(TAG, "Persisted static delay: %u ms", delay_ms); - } else { - ESP_LOGW(TAG, "Failed to persist static delay"); - } - return ok; -} - -// THREAD CONTEXT: Main loop (SendspinPersistenceProvider override) -std::optional SendspinHub::load_static_delay() { - StaticDelayPref pref{}; - if (this->static_delay_pref_.load(&pref)) { - ESP_LOGI(TAG, "Loaded static delay: %u ms", pref.delay_ms); - return pref.delay_ms; - } - return std::nullopt; -} - #endif } // namespace esphome::sendspin_ -#endif // USE_ESP32 +#endif // USE_ESP_IDF diff --git a/esphome/components/sendspin/sendspin_hub.h b/esphome/components/sendspin/sendspin_hub.h index ea09dafe689..cd8d0d0afaf 100644 --- a/esphome/components/sendspin/sendspin_hub.h +++ b/esphome/components/sendspin/sendspin_hub.h @@ -2,12 +2,13 @@ #include "esphome/core/defines.h" -#ifdef USE_ESP32 +#ifdef USE_ESP_IDF #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" +#include "esphome/core/string_ref.h" #include "esphome/core/version.h" #ifdef USE_MDNS_SUPPORTS_ENABLE_DISABLE @@ -31,9 +32,15 @@ #include #endif +#include +#include +#include #include #include #include +#include +#include +#include namespace esphome::sendspin_ { @@ -44,22 +51,21 @@ namespace esphome::sendspin_ { /// one step later than hub so they can assume hub's setup() has already completed. namespace sendspin_priority { // AFTER_WIFI so the hub runs after the wifi/ethernet drivers are up and we can read the active -// interface's MAC for client_id. +// interface's MAC address for the device info. inline constexpr float HUB = esphome::setup_priority::AFTER_WIFI; inline constexpr float CHILD = HUB - 1.0f; } // namespace sendspin_priority -/// @brief Persistent storage structure for last played server hash. -struct LastPlayedServerPref { - uint32_t server_id_hash; -}; +// PREFERENCE KEYS: each library persistence key has its own preference, hashed from "sendspin_" + name. Renaming +// one erases it. -#ifdef USE_SENDSPIN_PLAYER -/// @brief Persistent storage structure for player static delay. -struct StaticDelayPref { - uint16_t delay_ms; +/// Pairing-record slots, handed to the client as max_pairing_records. +inline constexpr size_t SENDSPIN_RECORD_SLOTS = sendspin::SendspinClientConfig::DEFAULT_MAX_PAIRING_RECORDS; + +/// Feature flags reported to API clients in DeviceCapabilitiesResponse. +enum SendspinFeature : uint32_t { + SENDSPIN_FEATURE_PAIRING_TOKEN = 1 << 0, }; -#endif /// @brief Thin adapter over sendspin::SendspinClient. /// @@ -90,9 +96,12 @@ class SendspinHub final : public Component, public sendspin::SendspinNetworkProvider, public sendspin::SendspinPersistenceProvider { public: + SendspinHub(); + float get_setup_priority() const override { return sendspin_priority::HUB; } void setup() override; void loop() override; + void on_shutdown() override; void dump_config() override; /// @brief Connects the underlying client to the given Sendspin server. @@ -113,14 +122,22 @@ class SendspinHub final : public Component, /// - `USER_REQUEST`: user explicitly requested disconnect. void disconnect_from_server(sendspin::SendspinGoodbyeReason reason); - /// @brief Updates the client's reported playback state on the server. + /// @brief Leaves the current group so another source can use the speaker. + /// + /// Sends `client/leave` while the group is playing. A stopped group is left alone, so the device stays grouped. + /// No-op if the hub's client is not running. Must be called from the main loop thread. + void leave_group(); + + /// @brief Confirms a pairing attempt on the device. With no attempt waiting, it opens the pairing window for the + /// next one. /// /// No-op if the hub's client is not running. Must be called from the main loop thread. - /// @param state New client state: - /// - `SYNCHRONIZED`: client is synchronized and playing from the server. - /// - `ERROR`: client encountered a playback error. - /// - `EXTERNAL_SOURCE`: client is playing from a non-Sendspin source. - void update_state(sendspin::SendspinClientState state); + void confirm_pairing_window(); + + /// @brief Closes an open pairing window, so a waiting pairing attempt is not confirmed. + /// + /// No-op if the hub's client is not running. Must be called from the main loop thread. + void cancel_pairing_window(); // --- Configuration setters (called from codegen) --- @@ -128,19 +145,59 @@ class SendspinHub final : public Component, this->group_update_callbacks_.add(std::forward(callback)); } + template void add_on_open_pairing_window_callback(F &&callback) { + this->open_pairing_window_callbacks_.add(std::forward(callback)); + } + + template void add_on_close_pairing_window_callback(F &&callback) { + this->close_pairing_window_callbacks_.add(std::forward(callback)); + } + + template void add_on_display_pairing_code_callback(F &&callback) { + this->display_pairing_code_callbacks_.add(std::forward(callback)); + } + + template void add_on_clear_pairing_code_callback(F &&callback) { + this->clear_pairing_code_callbacks_.add(std::forward(callback)); + } + + template void add_on_pairing_succeeded_callback(F &&callback) { + this->pairing_succeeded_callbacks_.add(std::forward(callback)); + } + + template void add_on_pairing_failed_callback(F &&callback) { + this->pairing_failed_callbacks_.add(std::forward(callback)); + } + void set_task_stack_in_psram(bool task_stack_in_psram) { this->task_stack_in_psram_ = task_stack_in_psram; } /// @brief Requests the Sendspin client, including the server, the roles and the mDNS advertisement, to start or /// stop. /// /// Applied from the hub's loop(). Stopping blocks until the client is fully stopped; the roles' clear callbacks - /// fire from inside that call. With a sendspin switch configured the client stays stopped until the switch has - /// called this once. Must be called from the main loop thread. + /// fire from inside that call. The client stays stopped until this has been called once, by codegen or by the + /// enabled switch. Must be called from the main loop thread. void set_enabled(bool enabled); + /// Turns unpaired (Sentinel) access on or off. The client waits for this call, by codegen or by the unpaired + /// access switch, before its first start. May be called before setup(). Main loop only. + void set_unpaired_access_enabled(bool enabled); + /// @brief Returns whether the Sendspin client is running. bool is_client_running() const { return this->client_ != nullptr && this->client_->is_started(); } + /// @brief Returns whether the client has been turned off with set_enabled(). + bool is_disabled() const { return this->enabled_.has_value() && !this->enabled_.value(); } + + /// @brief Returns the SendspinFeature flags reported to API clients. + uint32_t get_feature_flags() const { return SENDSPIN_FEATURE_PAIRING_TOKEN; } + + /// @brief Returns the pairing token a Sendspin server uses to pair with this device. + /// + /// The token is a long-lived secret, so this component never logs it. Returns std::nullopt until the client has + /// started once; it stays available after the client stops. Main loop only. + std::optional get_pairing_token() const; + /// @brief Sets the device information reported to the server in the `client/hello` message. /// /// Each takes a pointer to a string literal emitted by codegen, so it must stay valid for the @@ -154,6 +211,13 @@ class SendspinHub final : public Component, void set_mdns(mdns::MDNSComponent *mdns) { this->mdns_ = mdns; } #endif + /// The static pairing code, used on every boot. + void set_static_pairing_code(const char *code) { this->static_pairing_code_ = code; } + + /// Set when on_display_pairing_code or a pairing_code text sensor is configured, so the device offers the dynamic + /// pairing code. + void set_pairing_code_display_supported(bool supported) { this->pairing_code_display_supported_ = supported; } + // --- Sendspin role specific methods --- #ifdef USE_SENDSPIN_ARTWORK @@ -219,12 +283,18 @@ class SendspinHub final : public Component, /// @brief Builds the SendspinClientConfig from ESPHome configuration and platform info. sendspin::SendspinClientConfig build_client_config_(); + /// The preference a library key is stored in and its blob size, or {nullptr, 0} for a key with + /// no storage here. + std::pair pref_for_key_(const std::string &key); + /// @brief Returns the product name reported to the server: the configured model, or the device name. const char *get_product_name_() const; - /// @brief Writes the active network interface's MAC into @p buf and returns its data pointer. + const char *pairing_code_method_() const; + + /// @brief Writes the active network interface's MAC, in lowercase, into @p buf and returns its data pointer. /// Uses the ethernet MAC if ethernet is configured, otherwise the base MAC (used by wifi). - static const char *get_client_id_into_buffer(std::span buf); + static const char *get_mac_address_into_buffer(std::span buf); #ifdef USE_MDNS_SUPPORTS_ENABLE_DISABLE /// @brief Keeps the `_sendspin` mDNS service advertised while the client is running. @@ -238,12 +308,26 @@ class SendspinHub final : public Component, void on_release_high_performance() override; + void on_open_pairing_window() override; + + void on_close_pairing_window() override; + + void on_display_pairing_code(const std::string &code, sendspin::SendspinPairingCodeFormat format) override; + + void on_clear_pairing_code() override; + + void on_pairing_succeeded(const std::string &server_id) override; + + void on_pairing_failed(const std::string &server_id, sendspin::SendspinPairAbortReason reason) override; + // --- SendspinNetworkProvider override --- bool is_network_ready() override; // --- SendspinPersistenceProvider overrides --- - bool save_last_server_hash(uint32_t hash) override; - std::optional load_last_server_hash() override; + // The library calls commit() after writing pairing secrets, so they reach flash right away. + std::optional> load_blob(const std::string &key) override; + bool save_blob(const std::string &key, const uint8_t *data, size_t len) override; + bool commit() override; // --- Sendspin role specific methods/overrides/member variables --- @@ -291,29 +375,41 @@ class SendspinHub final : public Component, #ifdef USE_SENDSPIN_PLAYER sendspin::PlayerRoleListener *player_listener_{nullptr}; sendspin::PlayerRoleConfig player_config_{}; - - // Part of SendspinPersistenceProvider overrides - ESPPreferenceObject static_delay_pref_; - std::optional load_static_delay() override; - bool save_static_delay(uint16_t delay_ms) override; #endif // --- Core member variables --- - ESPPreferenceObject last_played_server_pref_; + // Built once in setup(): make_preference() allocates a backend that is never freed. + ESPPreferenceObject keypair_pref_; + ESPPreferenceObject pairing_psk_pref_; + ESPPreferenceObject last_played_pref_; + ESPPreferenceObject output_delay_pref_; + std::array record_slot_prefs_; + ESPPreferenceObject record_order_pref_; std::unique_ptr client_; // Callback fan-out to child components CallbackManager group_update_callbacks_{}; + // Lazy: each pairing callback is fed by an optional YAML surface. + LazyCallbackManager open_pairing_window_callbacks_{}; + LazyCallbackManager close_pairing_window_callbacks_{}; + LazyCallbackManager display_pairing_code_callbacks_{}; + LazyCallbackManager clear_pairing_code_callbacks_{}; + LazyCallbackManager pairing_succeeded_callbacks_{}; + LazyCallbackManager pairing_failed_callbacks_{}; + + const char *static_pairing_code_{nullptr}; // Codegen string literal, or nullptr when not configured + bool pairing_code_display_supported_{false}; bool task_stack_in_psram_{false}; #ifdef USE_MDNS_SUPPORTS_ENABLE_DISABLE bool mdns_advertised_{false}; // Last state requested from mdns #endif - // Requested client state, applied from loop(). Empty until the switch restores its state. + // Requested client state, applied from loop(). The client does not start until both are set. std::optional enabled_; + std::optional unpaired_access_; // Device information sent in the `client/hello` message. Defaults apply when neither the // sendspin configuration nor the project information supplies a value. @@ -347,6 +443,8 @@ class SendspinPollingChild : public PollingComponent, public Parented @@ -8,7 +8,7 @@ namespace esphome::sendspin_ { -static const char *const TAG = "sendspin.sensor"; +ESPHOME_LOG_TAG(TAG, "sendspin.sensor"); // --- SendspinTrackProgressSensor --- diff --git a/esphome/components/sendspin/sensor/sendspin_sensor.h b/esphome/components/sendspin/sensor/sendspin_sensor.h index 5b29fff55f8..77bb8355986 100644 --- a/esphome/components/sendspin/sensor/sendspin_sensor.h +++ b/esphome/components/sendspin/sensor/sendspin_sensor.h @@ -2,7 +2,7 @@ #include "esphome/core/defines.h" -#if defined(USE_ESP32) && defined(USE_SENDSPIN_METADATA) && defined(USE_SENSOR) +#if defined(USE_ESP_IDF) && defined(USE_SENDSPIN_METADATA) && defined(USE_SENSOR) #include "esphome/components/sendspin/sendspin_hub.h" #include "esphome/components/sensor/sensor.h" diff --git a/esphome/components/sendspin/switch/__init__.py b/esphome/components/sendspin/switch/__init__.py index 63f5f7ad28b..63d30d0c033 100644 --- a/esphome/components/sendspin/switch/__init__.py +++ b/esphome/components/sendspin/switch/__init__.py @@ -1,29 +1,102 @@ +import logging + import esphome.codegen as cg from esphome.components import switch +from esphome.components.const import CONF_ENABLED +from esphome.components.switch import DOMAIN as SWITCH_DOMAIN import esphome.config_validation as cv -from esphome.const import ENTITY_CATEGORY_CONFIG +from esphome.const import CONF_PLATFORM, CONF_TYPE, ENTITY_CATEGORY_CONFIG +import esphome.final_validate as fv from esphome.types import ConfigType -from .. import CONF_SENDSPIN_ID, SendspinHub, sendspin_ns +from .. import ( + CONF_SENDSPIN_ID, + CONF_STATIC_PAIRING_CODE, + CONF_UNPAIRED_ACCESS, + DOMAIN, + SendspinHub, + _has_pairing_method, + request_switch, + sendspin_ns, +) + +_LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@kahrendt"] DEPENDENCIES = ["sendspin"] -SendspinSwitch = sendspin_ns.class_("SendspinSwitch", switch.Switch, cg.Component) +SendspinEnabledSwitch = sendspin_ns.class_( + "SendspinEnabledSwitch", switch.Switch, cg.Component +) +SendspinUnpairedAccessSwitch = sendspin_ns.class_( + "SendspinUnpairedAccessSwitch", switch.Switch, cg.Component +) + + +def _switch_schema(class_: cg.MockObjClass) -> cv.Schema: + return ( + switch.switch_schema( + class_, + block_inverted=True, + default_restore_mode="RESTORE_DEFAULT_ON", + entity_category=ENTITY_CATEGORY_CONFIG, + ) + .extend({cv.GenerateID(CONF_SENDSPIN_ID): cv.use_id(SendspinHub)}) + .extend(cv.COMPONENT_SCHEMA) + ) + + +def _request_switch(config: ConfigType) -> ConfigType: + """Tell the hub to wait for this switch before the client's first start.""" + request_switch(config[CONF_TYPE]) + return config + CONFIG_SCHEMA = cv.All( - switch.switch_schema( - SendspinSwitch, - block_inverted=True, - default_restore_mode="RESTORE_DEFAULT_ON", - entity_category=ENTITY_CATEGORY_CONFIG, - ) - .extend({cv.GenerateID(CONF_SENDSPIN_ID): cv.use_id(SendspinHub)}) - .extend(cv.COMPONENT_SCHEMA), + cv.typed_schema( + { + CONF_ENABLED: _switch_schema(SendspinEnabledSwitch), + CONF_UNPAIRED_ACCESS: _switch_schema(SendspinUnpairedAccessSwitch), + }, + key=CONF_TYPE, + ), cv.only_on_esp32, + _request_switch, ) +def _final_validate(config: ConfigType) -> ConfigType: + full_config = fv.full_config.get() + switch_type = config[CONF_TYPE] + same_type = [ + conf + for conf in full_config.get(SWITCH_DOMAIN, []) + if conf.get(CONF_PLATFORM) == DOMAIN and conf.get(CONF_TYPE) == switch_type + ] + # Two switches of one type would each drive the same hub setting. + if len(same_type) > 1: + raise cv.Invalid(f"Only one sendspin '{switch_type}' switch is allowed") + if switch_type != CONF_UNPAIRED_ACCESS: + return config + hub_config = full_config.get(DOMAIN, {}) + if CONF_UNPAIRED_ACCESS in hub_config: + raise cv.Invalid( + f"'{DOMAIN}: {CONF_UNPAIRED_ACCESS}' cannot be set together with an " + f"{CONF_UNPAIRED_ACCESS} switch; set the switch's restore_mode instead" + ) + if not _has_pairing_method(hub_config): + _LOGGER.warning( + "The %s switch has no pairing method (%s or a dynamic pairing code), so " + "while it is off no new server can pair with this device", + CONF_UNPAIRED_ACCESS, + CONF_STATIC_PAIRING_CODE, + ) + return config + + +FINAL_VALIDATE_SCHEMA = _final_validate + + async def to_code(config: ConfigType) -> None: var = await switch.new_switch(config) await cg.register_component(var, config) diff --git a/esphome/components/sendspin/switch/sendspin_switch.cpp b/esphome/components/sendspin/switch/sendspin_switch.cpp index 0bf029d4c77..51c151980a6 100644 --- a/esphome/components/sendspin/switch/sendspin_switch.cpp +++ b/esphome/components/sendspin/switch/sendspin_switch.cpp @@ -1,26 +1,49 @@ #include "sendspin_switch.h" -#ifdef USE_ESP32 +#if defined(USE_ESP_IDF) && defined(USE_SENDSPIN_SWITCH) #include "esphome/core/log.h" +#include "esphome/core/preferences.h" namespace esphome::sendspin_ { -static const char *const TAG = "sendspin.switch"; +ESPHOME_LOG_TAG(TAG, "sendspin.switch"); -void SendspinSwitch::setup() { +// --- SendspinEnabledSwitch --- + +void SendspinEnabledSwitch::setup() { // The hub waits for this request, so a restore mode without a state still has to answer. this->control(this->get_initial_state_with_restore_mode().value_or(true)); } -void SendspinSwitch::dump_config() { LOG_SWITCH("", "Sendspin Switch", this); } +void SendspinEnabledSwitch::dump_config() { LOG_SWITCH("", "Sendspin Enabled Switch", this); } // THREAD CONTEXT: Main loop -void SendspinSwitch::write_state(bool state) { +void SendspinEnabledSwitch::write_state(bool state) { this->parent_->set_enabled(state); this->publish_state(state); } +// --- SendspinUnpairedAccessSwitch --- + +void SendspinUnpairedAccessSwitch::setup() { + // The hub holds the client's first start for this, so a restore mode without a state still has to answer. + this->control(this->get_initial_state_with_restore_mode().value_or(true)); +} + +void SendspinUnpairedAccessSwitch::dump_config() { LOG_SWITCH("", "Sendspin Unpaired Access Switch", this); } + +// THREAD CONTEXT: Main loop +void SendspinUnpairedAccessSwitch::write_state(bool state) { + const bool turning_off = !state && this->has_state() && this->state; + this->parent_->set_unpaired_access_enabled(state); + this->publish_state(state); + // Write the change to flash now: losing it in a power cut would reopen unpaired access. + if (turning_off) { + global_preferences->sync(); + } +} + } // namespace esphome::sendspin_ -#endif // USE_ESP32 +#endif diff --git a/esphome/components/sendspin/switch/sendspin_switch.h b/esphome/components/sendspin/switch/sendspin_switch.h index 253d952b220..4b04c95857b 100644 --- a/esphome/components/sendspin/switch/sendspin_switch.h +++ b/esphome/components/sendspin/switch/sendspin_switch.h @@ -2,7 +2,7 @@ #include "esphome/core/defines.h" -#ifdef USE_ESP32 +#if defined(USE_ESP_IDF) && defined(USE_SENDSPIN_SWITCH) #include "esphome/components/sendspin/sendspin_hub.h" #include "esphome/components/switch/switch.h" @@ -10,7 +10,19 @@ namespace esphome::sendspin_ { /// @brief Switch that starts and stops the Sendspin client through the hub (see SendspinHub::set_enabled()). -class SendspinSwitch final : public switch_::Switch, public SendspinChild { +class SendspinEnabledSwitch final : public switch_::Switch, public SendspinChild { + public: + void setup() override; + void dump_config() override; + + protected: + void write_state(bool state) override; +}; + +/// @brief Switch that turns unpaired (Sentinel) access on and off through the hub (see +/// SendspinHub::set_unpaired_access_enabled()). The library does not persist the setting, so the switch's restore +/// mode does. +class SendspinUnpairedAccessSwitch final : public switch_::Switch, public SendspinChild { public: void setup() override; void dump_config() override; @@ -21,4 +33,4 @@ class SendspinSwitch final : public switch_::Switch, public SendspinChild { } // namespace esphome::sendspin_ -#endif // USE_ESP32 +#endif diff --git a/esphome/components/sendspin/text_sensor/__init__.py b/esphome/components/sendspin/text_sensor/__init__.py index 87f6c9b9362..24aa2d57346 100644 --- a/esphome/components/sendspin/text_sensor/__init__.py +++ b/esphome/components/sendspin/text_sensor/__init__.py @@ -4,16 +4,29 @@ import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_TYPE from esphome.types import ConfigType -from .. import CONF_SENDSPIN_ID, SendspinHub, request_metadata_support, sendspin_ns +from .. import ( + CONF_SENDSPIN_ID, + SendspinHub, + request_metadata_support, + request_pairing_code_display_support, + sendspin_ns, +) CODEOWNERS = ["@kahrendt"] DEPENDENCIES = ["sendspin"] +CONF_PAIRING_CODE = "pairing_code" + SendspinTextSensor = sendspin_ns.class_( "SendspinTextSensor", text_sensor.TextSensor, cg.Component, ) +SendspinPairingCodeTextSensor = sendspin_ns.class_( + "SendspinPairingCodeTextSensor", + text_sensor.TextSensor, + cg.Component, +) SendspinTextMetadataTypes = sendspin_ns.enum("SendspinTextMetadataTypes", is_class=True) SENDSPIN_TEXT_METADATA_TYPES = { @@ -26,18 +39,29 @@ SENDSPIN_TEXT_METADATA_TYPES = { def _request_roles(config: ConfigType) -> ConfigType: """Request the necessary Sendspin roles for the text sensor.""" - request_metadata_support() + if config[CONF_TYPE] == CONF_PAIRING_CODE: + # A display surface for the dynamic code, so advertise dynamic_pairing_code. + request_pairing_code_display_support() + else: + request_metadata_support() return config +_HUB_ID_SCHEMA = cv.Schema({cv.GenerateID(CONF_SENDSPIN_ID): cv.use_id(SendspinHub)}) +_METADATA_SCHEMA = text_sensor.text_sensor_schema(SendspinTextSensor).extend( + _HUB_ID_SCHEMA +) + CONFIG_SCHEMA = cv.All( - text_sensor.text_sensor_schema().extend( + cv.typed_schema( { - cv.GenerateID(): cv.declare_id(SendspinTextSensor), - cv.GenerateID(CONF_SENDSPIN_ID): cv.use_id(SendspinHub), - cv.Required(CONF_TYPE): cv.enum(SENDSPIN_TEXT_METADATA_TYPES), - } + **dict.fromkeys(SENDSPIN_TEXT_METADATA_TYPES, _METADATA_SCHEMA), + CONF_PAIRING_CODE: text_sensor.text_sensor_schema( + SendspinPairingCodeTextSensor + ).extend(_HUB_ID_SCHEMA), + }, + key=CONF_TYPE, ), cv.only_on_esp32, _request_roles, @@ -50,4 +74,7 @@ async def to_code(config: ConfigType) -> None: await cg.register_parented(var, config[CONF_SENDSPIN_ID]) await text_sensor.register_text_sensor(var, config) - cg.add(var.set_metadata_type(config[CONF_TYPE])) + if ( + metadata_type := SENDSPIN_TEXT_METADATA_TYPES.get(config[CONF_TYPE]) + ) is not None: + cg.add(var.set_metadata_type(metadata_type)) diff --git a/esphome/components/sendspin/text_sensor/sendspin_text_sensor.cpp b/esphome/components/sendspin/text_sensor/sendspin_text_sensor.cpp index 554e01cf880..629396a681e 100644 --- a/esphome/components/sendspin/text_sensor/sendspin_text_sensor.cpp +++ b/esphome/components/sendspin/text_sensor/sendspin_text_sensor.cpp @@ -1,15 +1,18 @@ #include "sendspin_text_sensor.h" -#if defined(USE_ESP32) && defined(USE_SENDSPIN_METADATA) && defined(USE_TEXT_SENSOR) +#if defined(USE_ESP_IDF) && defined(USE_TEXT_SENSOR) +#ifdef USE_SENDSPIN_METADATA #include +#endif #include namespace esphome::sendspin_ { -static const char *const TAG = "sendspin.text_sensor"; +ESPHOME_LOG_TAG(TAG, "sendspin.text_sensor"); +#ifdef USE_SENDSPIN_METADATA void SendspinTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Sendspin", this); } // A field is nullopt when the server has not provided it or has explicitly cleared it. Both mean there is nothing to @@ -50,6 +53,16 @@ void SendspinTextSensor::publish_if_changed_(const char *value) { this->publish_state(value); } } +#endif // USE_SENDSPIN_METADATA + +void SendspinPairingCodeTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Pairing Code", this); } + +// THREAD CONTEXT: Main loop. The registered callbacks also fire on the main loop (SendspinHub dispatches them from +// its loop()). +void SendspinPairingCodeTextSensor::setup() { + this->parent_->add_on_display_pairing_code_callback([this](const std::string &code) { this->publish_state(code); }); + this->parent_->add_on_clear_pairing_code_callback([this]() { this->publish_state(""); }); +} } // namespace esphome::sendspin_ diff --git a/esphome/components/sendspin/text_sensor/sendspin_text_sensor.h b/esphome/components/sendspin/text_sensor/sendspin_text_sensor.h index d38f360d94d..cc25ab93c7c 100644 --- a/esphome/components/sendspin/text_sensor/sendspin_text_sensor.h +++ b/esphome/components/sendspin/text_sensor/sendspin_text_sensor.h @@ -2,15 +2,19 @@ #include "esphome/core/defines.h" -#if defined(USE_ESP32) && defined(USE_SENDSPIN_METADATA) && defined(USE_TEXT_SENSOR) +#if defined(USE_ESP_IDF) && defined(USE_TEXT_SENSOR) #include "esphome/components/sendspin/sendspin_hub.h" #include "esphome/components/text_sensor/text_sensor.h" +#ifdef USE_SENDSPIN_METADATA #include +#endif namespace esphome::sendspin_ { +#ifdef USE_SENDSPIN_METADATA + enum class SendspinTextMetadataTypes { TITLE, ARTIST, @@ -31,6 +35,13 @@ class SendspinTextSensor final : public SendspinChild, public text_sensor::TextS SendspinTextMetadataTypes metadata_type_; }; +#endif // USE_SENDSPIN_METADATA + +class SendspinPairingCodeTextSensor final : public SendspinChild, public text_sensor::TextSensor { + public: + void dump_config() override; + void setup() override; +}; } // namespace esphome::sendspin_ #endif diff --git a/esphome/components/senseair/__init__.py b/esphome/components/senseair/__init__.py index e69de29bb2d..5973c85b6a7 100644 --- a/esphome/components/senseair/__init__.py +++ b/esphome/components/senseair/__init__.py @@ -0,0 +1 @@ +DOMAIN = "senseair" diff --git a/esphome/components/senseair/senseair.cpp b/esphome/components/senseair/senseair.cpp index f5017cff75b..858510f4277 100644 --- a/esphome/components/senseair/senseair.cpp +++ b/esphome/components/senseair/senseair.cpp @@ -4,7 +4,7 @@ namespace esphome::senseair { -static const char *const TAG = "senseair"; +ESPHOME_LOG_TAG(TAG, "senseair"); static const uint8_t SENSEAIR_REQUEST_LENGTH = 8; static const uint8_t SENSEAIR_PPM_STATUS_RESPONSE_LENGTH = 13; static const uint8_t SENSEAIR_ABC_PERIOD_RESPONSE_LENGTH = 7; diff --git a/esphome/components/sensirion_common/__init__.py b/esphome/components/sensirion_common/__init__.py index 5d9424d1e7c..e1fc793d6db 100644 --- a/esphome/components/sensirion_common/__init__.py +++ b/esphome/components/sensirion_common/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import i2c CODEOWNERS = ["@martgras"] +DOMAIN = "sensirion_common" sensirion_common_ns = cg.esphome_ns.namespace("sensirion_common") diff --git a/esphome/components/sensirion_common/i2c_sensirion.cpp b/esphome/components/sensirion_common/i2c_sensirion.cpp index 3325c277722..19172e68d2c 100644 --- a/esphome/components/sensirion_common/i2c_sensirion.cpp +++ b/esphome/components/sensirion_common/i2c_sensirion.cpp @@ -6,7 +6,7 @@ namespace esphome::sensirion_common { -static const char *const TAG = "sensirion_i2c"; +ESPHOME_LOG_TAG(TAG, "sensirion_i2c"); // To avoid memory allocations for small writes a stack buffer is used static const size_t BUFFER_STACK_SIZE = 16; diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index f3293d4ed56..2b404442a4d 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -1,5 +1,6 @@ import logging import math +from typing import Any from esphome import automation import esphome.codegen as cg @@ -43,6 +44,7 @@ from esphome.const import ( CONF_TIMEOUT, CONF_TO, CONF_TRIGGER_ID, + CONF_TYPE_ID, CONF_UNIT_OF_MEASUREMENT, CONF_VALUE, CONF_WEB_SERVER, @@ -127,6 +129,7 @@ from esphome.types import ConfigType from esphome.util import Registry CODEOWNERS = ["@esphome/core"] +DOMAIN = "sensor" DEVICE_CLASSES = [ DEVICE_CLASS_ABSOLUTE_HUMIDITY, @@ -221,6 +224,13 @@ def validate_send_first_at(value): FILTER_REGISTRY = Registry() validate_filters = cv.validate_registry("filter", FILTER_REGISTRY) +# Default for streamed readings (radar sensors): hold the last value for a second so a dropped +# frame doesn't read as absence, then rate limit. Codegen turns the pair into one TimeoutThrottleFilter. +TIMEOUT_THROTTLE_FILTERS = [ + {"timeout": {"timeout": cv.TimePeriod(milliseconds=1000), "value": "last"}}, + {"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}, +] + def validate_datapoint(value): if isinstance(value, dict): @@ -292,6 +302,7 @@ ThrottleWithPriorityNanFilter = sensor_ns.class_( TimeoutFilterBase = sensor_ns.class_("TimeoutFilterBase", Filter, cg.Component) TimeoutFilterLast = sensor_ns.class_("TimeoutFilterLast", TimeoutFilterBase) TimeoutFilterConfigured = sensor_ns.class_("TimeoutFilterConfigured", TimeoutFilterBase) +TimeoutThrottleFilter = sensor_ns.class_("TimeoutThrottleFilter", TimeoutFilterLast) DebounceFilter = sensor_ns.class_("DebounceFilter", Filter) HeartbeatFilter = sensor_ns.class_("HeartbeatFilter", Filter) DeltaFilter = sensor_ns.class_("DeltaFilter", Filter) @@ -682,6 +693,12 @@ THROTTLE_WITH_PRIORITY_SCHEMA = cv.maybe_simple_value( ) +def _is_nan_only(values: Any) -> bool: + if not isinstance(values, list): + values = [values] + return bool(values) and all(isinstance(v, float) and math.isnan(v) for v in values) + + @FILTER_REGISTRY.register( "throttle_with_priority", ThrottleWithPriorityFilter, @@ -695,7 +712,7 @@ async def throttle_with_priority_filter_to_code(config, filter_id): # omits `value:`) to avoid the TemplatableFn array + NaN lambda the # generic ValueListFilter path requires. Behavior is identical: NaN sensor # readings always bypass the throttle. - if values and all(isinstance(v, float) and math.isnan(v) for v in values): + if _is_nan_only(values): filter_id = filter_id.copy() filter_id.type = ThrottleWithPriorityNanFilter return cg.new_Pvariable(filter_id, config[CONF_TIMEOUT]) @@ -940,6 +957,43 @@ async def build_filters(config): return await cg.build_registry_list(FILTER_REGISTRY, config) +def _timeout_throttle_period( + first: ConfigType, second: ConfigType +) -> cv.TimePeriod | None: + """Period of a `timeout` (value `last`) directly followed by a NaN only `throttle_with_priority`.""" + timeout = first.get("timeout") + throttle = second.get("throttle_with_priority") + if not isinstance(timeout, dict) or not isinstance(throttle, dict): + return None + if timeout[CONF_VALUE] != "last" or timeout[CONF_TIMEOUT] != throttle[CONF_TIMEOUT]: + return None + if not _is_nan_only(throttle[CONF_VALUE]): + return None + return timeout[CONF_TIMEOUT] + + +async def _build_chain_filters(config: list[ConfigType]) -> list: + """Like build_filters, but merges the radar `timeout` + `throttle_with_priority` pair into one filter.""" + filters = [] + i = 0 + while i < len(config): + if ( + i + 1 < len(config) + and (period := _timeout_throttle_period(config[i], config[i + 1])) + is not None + ): + filter_id = config[i][CONF_TYPE_ID].copy() + filter_id.type = TimeoutThrottleFilter + var = cg.new_Pvariable(filter_id, period) + await cg.register_component(var, {}) + filters.append(var) + i += 2 + continue + filters.append(await cg.build_registry_entry(FILTER_REGISTRY, config[i])) + i += 1 + return filters + + _CALLBACK_AUTOMATIONS = ( automation.CallbackAutomation( CONF_ON_VALUE, "add_on_state_callback", [(float, "x")] @@ -978,7 +1032,7 @@ async def setup_sensor_core_(var, config): cg.add(var.set_force_update(True)) if config.get(CONF_FILTERS): # must exist and not be empty cg.add_define("USE_SENSOR_FILTER") - filters = await build_filters(config[CONF_FILTERS]) + filters = await _build_chain_filters(config[CONF_FILTERS]) cg.add(var.set_filters(filters)) CORE.add_job(_build_sensor_automations, var, config) diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index 9b9d7c033ae..732db5b0b4f 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -11,7 +11,18 @@ namespace esphome::sensor { -static const char *const TAG = "sensor.filter"; +ESPHOME_LOG_TAG(TAG, "sensor.filter"); + +/// Shared pass check for throttle_with_priority (NaN only): passes and stamps `last_input` when the +/// period has elapsed, on the first value, or for NaN. +static bool throttle_nan_passes(uint32_t &last_input, uint32_t period, float value) { + const uint32_t now = App.get_loop_component_start_time(); + if (last_input == 0 || now - last_input >= period || std::isnan(value)) { + last_input = now; + return true; + } + return false; +} // Filter void Filter::input(float value) { @@ -266,11 +277,8 @@ optional throttle_with_priority_new_value(Sensor *parent, float value, co ThrottleWithPriorityNanFilter::ThrottleWithPriorityNanFilter(uint32_t min_time_between_inputs) : min_time_between_inputs_(min_time_between_inputs) {} optional ThrottleWithPriorityNanFilter::new_value(float value) { - const uint32_t now = App.get_loop_component_start_time(); - if (this->last_input_ == 0 || now - this->last_input_ >= this->min_time_between_inputs_ || std::isnan(value)) { - this->last_input_ = now; + if (throttle_nan_passes(this->last_input_, this->min_time_between_inputs_, value)) return value; - } return {}; } @@ -355,6 +363,23 @@ optional TimeoutFilterConfigured::new_value(float value) { return value; } +// TimeoutThrottleFilter +optional TimeoutThrottleFilter::new_value(float value) { + TimeoutFilterLast::new_value(value); + if (throttle_nan_passes(this->last_input_, this->time_period_, value)) + return value; + return {}; +} + +void TimeoutThrottleFilter::loop() { + const uint32_t now = App.get_loop_component_start_time(); + if (now - this->timeout_start_time_ >= this->time_period_) { + if (throttle_nan_passes(this->last_input_, this->time_period_, this->pending_value_)) + this->output(this->pending_value_); + this->disable_loop(); + } +} + // DebounceFilter optional DebounceFilter::new_value(float value) { App.scheduler.set_timeout(this, this->time_period_, [this, value]() { this->output(value); }); diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index bc086e3805b..510ee94c6a7 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -479,6 +479,19 @@ class TimeoutFilterConfigured : public TimeoutFilterBase { // Total: 8 (base) + 4 = 12 bytes + vtable ptr + Component overhead }; +/// Codegen use only: `timeout` (value `last`) followed by `throttle_with_priority` (NaN only) with the +/// same period, as one object. Output matches the two filter chain exactly. +class TimeoutThrottleFilter : public TimeoutFilterLast { + public: + explicit TimeoutThrottleFilter(uint32_t time_period) : TimeoutFilterLast(time_period) {} + + optional new_value(float value) override; + void loop() override; + + protected: + uint32_t last_input_{0}; // When the throttle stage last let a value through +}; + class DebounceFilter : public Filter { public: explicit DebounceFilter(uint32_t time_period); diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index bee5d7c6d33..df73e2c481a 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -6,7 +6,7 @@ namespace esphome::sensor { -static const char *const TAG = "sensor"; +ESPHOME_LOG_TAG(TAG, "sensor"); // Function implementation of LOG_SENSOR macro to reduce code size void log_sensor(const char *tag, const char *prefix, const char *type, Sensor *obj) { diff --git a/esphome/components/serial_proxy/__init__.py b/esphome/components/serial_proxy/__init__.py index b6e780fabdc..a8767280504 100644 --- a/esphome/components/serial_proxy/__init__.py +++ b/esphome/components/serial_proxy/__init__.py @@ -17,10 +17,12 @@ from dataclasses import dataclass from esphome import pins import esphome.codegen as cg from esphome.components import uart +from esphome.components.usb_uart import is_usb_uart_channel import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_NAME +from esphome.const import CONF_ID, CONF_NAME, CONF_UART_ID from esphome.core import CORE, coroutine_with_priority from esphome.coroutine import CoroPriority +import esphome.final_validate as fv from esphome.types import ConfigType CODEOWNERS = ["@kbx81"] @@ -38,6 +40,7 @@ SERIAL_PROXY_PORT_TYPES = { "TTL": SerialProxyPortType.SERIAL_PROXY_PORT_TYPE_TTL, "RS232": SerialProxyPortType.SERIAL_PROXY_PORT_TYPE_RS232, "RS485": SerialProxyPortType.SERIAL_PROXY_PORT_TYPE_RS485, + "USB_SERIAL": SerialProxyPortType.SERIAL_PROXY_PORT_TYPE_USB_SERIAL, } CONF_DTR_PIN = "dtr_pin" @@ -73,6 +76,18 @@ CONFIG_SCHEMA = ( ) +def _final_validate(config: ConfigType) -> ConfigType: + is_usb = is_usb_uart_channel(config[CONF_UART_ID], fv.full_config.get()) + if config[CONF_PORT_TYPE] == "USB_SERIAL" and not is_usb: + raise cv.Invalid( + f"{CONF_PORT_TYPE} USB_SERIAL requires {CONF_UART_ID} to be a usb_uart channel" + ) + return config + + +FINAL_VALIDATE_SCHEMA = _final_validate + + @coroutine_with_priority(CoroPriority.FINAL) async def _add_serial_proxy_count_define() -> None: """Emit the SERIAL_PROXY_COUNT define once with the final instance count.""" @@ -88,6 +103,12 @@ async def to_code(config: ConfigType) -> None: cg.add(cg.App.register_serial_proxy(var)) cg.add(var.set_name(config[CONF_NAME])) cg.add(var.set_port_type(config[CONF_PORT_TYPE])) + # port_type names the electrical interface (a USB RS485 adapter is RS485), so every + # usb_uart channel reports USB identity whatever port type it declares + if is_usb_uart_channel(config[CONF_UART_ID], CORE.config): + channel = await cg.get_variable(config[CONF_UART_ID]) + cg.add(var.set_usb_channel(channel)) + cg.add_define("USE_SERIAL_PROXY_USB_IDENTITY") cg.add_define("USE_SERIAL_PROXY") # Track instance count for the FINAL priority define diff --git a/esphome/components/serial_proxy/serial_proxy.cpp b/esphome/components/serial_proxy/serial_proxy.cpp index 729ec40256a..d512362fa01 100644 --- a/esphome/components/serial_proxy/serial_proxy.cpp +++ b/esphome/components/serial_proxy/serial_proxy.cpp @@ -14,9 +14,13 @@ #include "esphome/components/api/api_server.h" #endif +#ifdef USE_SERIAL_PROXY_USB_IDENTITY +#include "esphome/components/usb_uart/usb_uart.h" +#endif + namespace esphome::serial_proxy { -static const char *const TAG = "serial_proxy"; +ESPHOME_LOG_TAG(TAG, "serial_proxy"); uint32_t SerialProxy::stall_loop_time = 0; uint32_t SerialProxy::stall_spent_ms = 0; @@ -35,6 +39,12 @@ void SerialProxy::setup() { // instance_index_ is fixed at registration time; pre-set it so loop() only needs to update data this->outgoing_msg_.instance = this->instance_index_; #endif +#ifdef USE_SERIAL_PROXY_USB_IDENTITY + if (this->usb_channel_ != nullptr) { + this->usb_channel_->get_parent()->add_on_connection_callback( + [this](bool connected) { this->on_usb_connection_changed_(connected); }); + } +#endif #ifdef USE_SERIAL_PROXY_TAP // A tap sets itself up before this runs (its setup priority is higher), so it may // already be waiting on the port -- a boot-time handshake with the device, say. Leaving @@ -161,9 +171,10 @@ void SerialProxy::dump_config() { " RTS Pin: %s\n" " DTR Pin: %s", this->instance_index_, this->name_ != nullptr ? this->name_ : "", - this->port_type_ == api::enums::SERIAL_PROXY_PORT_TYPE_RS485 ? LOG_STR_LITERAL("RS485") - : this->port_type_ == api::enums::SERIAL_PROXY_PORT_TYPE_RS232 ? LOG_STR_LITERAL("RS232") - : LOG_STR_LITERAL("TTL"), + this->port_type_ == api::enums::SERIAL_PROXY_PORT_TYPE_RS485 ? LOG_STR_LITERAL("RS485") + : this->port_type_ == api::enums::SERIAL_PROXY_PORT_TYPE_RS232 ? LOG_STR_LITERAL("RS232") + : this->port_type_ == api::enums::SERIAL_PROXY_PORT_TYPE_USB_SERIAL ? LOG_STR_LITERAL("USB_SERIAL") + : LOG_STR_LITERAL("TTL"), this->rts_pin_ != nullptr ? LOG_STR_LITERAL("configured") : LOG_STR_LITERAL("not configured"), this->dtr_pin_ != nullptr ? LOG_STR_LITERAL("configured") : LOG_STR_LITERAL("not configured")); } @@ -373,6 +384,60 @@ SerialProxyResult SerialProxy::set_modem_pins(api::APIConnection *api_connection return SerialProxyResult::SERIAL_PROXY_RESULT_OK; } +#ifdef USE_API +void SerialProxy::send_identity(api::APIConnection *api_connection) { + IdentityScratch scratch; + api::SerialProxyIdentity msg{}; + this->fill_identity_(scratch, msg); + api_connection->send_serial_proxy_identity(msg); +} + +void SerialProxy::fill_identity_([[maybe_unused]] IdentityScratch &scratch, api::SerialProxyIdentity &msg) const { + msg.instance = this->instance_index_; +#ifdef USE_SERIAL_PROXY_USB_IDENTITY + // The define is global, so a hardware UART port in the same config also gets here + if (this->usb_channel_ != nullptr) { + msg.source = api::enums::SERIAL_PROXY_IDENTITY_SOURCE_USB; + // Covers a removed device, a channel the device has no CDC function for and a failed channel setup + if (!this->usb_channel_->is_connected()) { + return; + } + auto *client = this->usb_channel_->get_parent(); + msg.flags = api::enums::SERIAL_PROXY_IDENTITY_FLAG_CONNECTED; + if (!client->get_device_info(scratch)) { + msg.flags |= api::enums::SERIAL_PROXY_IDENTITY_FLAG_ERROR; + return; + } + msg.usb.vendor_id = scratch.vendor_id; + msg.usb.product_id = scratch.product_id; + msg.usb.bcd_device = scratch.bcd_device; + msg.usb.interface_number = this->usb_channel_->get_interface_number(); + msg.manufacturer = StringRef(scratch.manufacturer); + msg.product = StringRef(scratch.product); + msg.serial_number = StringRef(scratch.serial_number); + return; + } +#endif + // Zero-initialized message: source NONE, no flags +} +#endif + +#ifdef USE_SERIAL_PROXY_USB_IDENTITY +void SerialProxy::on_usb_connection_changed_(bool connected) { + ESP_LOGD(TAG, "USB device %s serial proxy [%" PRIu32 "]", + connected ? LOG_STR_LITERAL("attached to") : LOG_STR_LITERAL("removed from"), this->instance_index_); +#ifdef USE_API + if (api::global_api_server == nullptr) { + return; + } + IdentityScratch scratch; + api::SerialProxyIdentity msg{}; + this->fill_identity_(scratch, msg); + api::global_api_server->send_serial_proxy_identity(msg); +#endif +} +#endif + uint32_t SerialProxy::get_modem_pins() const { return (this->rts_state_ ? static_cast(SERIAL_PROXY_LINE_STATE_FLAG_RTS) : 0u) | (this->dtr_state_ ? static_cast(SERIAL_PROXY_LINE_STATE_FLAG_DTR) : 0u); diff --git a/esphome/components/serial_proxy/serial_proxy.h b/esphome/components/serial_proxy/serial_proxy.h index df775d4f630..1a406bc0891 100644 --- a/esphome/components/serial_proxy/serial_proxy.h +++ b/esphome/components/serial_proxy/serial_proxy.h @@ -20,6 +20,15 @@ #include "esphome/components/api/api_pb2.h" #endif +#ifdef USE_SERIAL_PROXY_USB_IDENTITY +namespace esphome::usb_uart { +class USBUartChannel; +} // namespace esphome::usb_uart +namespace esphome::usb_host { +struct UsbDeviceInfo; +} // namespace esphome::usb_host +#endif + // Forward-declare types needed outside the USE_API guard. namespace esphome::api { class APIConnection; @@ -160,6 +169,16 @@ class SerialProxy final : public uart::UARTDevice, public Component { /// Set the DTR GPIO pin (from YAML configuration) void set_dtr_pin(GPIOPin *pin) { this->dtr_pin_ = pin; } +#ifdef USE_SERIAL_PROXY_USB_IDENTITY + /// Attach the USB UART channel behind this port (from code generation) + void set_usb_channel(usb_uart::USBUartChannel *channel) { this->usb_channel_ = channel; } +#endif + +#ifdef USE_API + /// Send this port's identity to one client + void send_identity(api::APIConnection *api_connection); +#endif + #ifdef USE_SERIAL_PROXY_TAP /// Attach a traffic observer. At most one, set once at setup time. void set_tap(SerialProxyTap *tap) { this->tap_ = tap; } @@ -226,6 +245,23 @@ class SerialProxy final : public uart::UARTDevice, public Component { bool tap_observing_() const; #endif +#ifdef USE_API +#ifdef USE_SERIAL_PROXY_USB_IDENTITY + using IdentityScratch = usb_host::UsbDeviceInfo; +#else + struct IdentityScratch {}; +#endif + /// Fill an identity message for this port. The message's strings are views into scratch, + /// so it must outlive the send. + void fill_identity_(IdentityScratch &scratch, api::SerialProxyIdentity &msg) const; +#endif + +#ifdef USE_SERIAL_PROXY_USB_IDENTITY + /// The USB device behind this port was attached or removed; report the port's new + /// identity to every subscribed API client + void on_usb_connection_changed_(bool connected); +#endif + /// Instance index for identifying this proxy in API messages uint32_t instance_index_{0}; @@ -268,6 +304,11 @@ class SerialProxy final : public uart::UARTDevice, public Component { #ifdef USE_SERIAL_PROXY_TAP SerialProxyTap *tap_{nullptr}; #endif + +#ifdef USE_SERIAL_PROXY_USB_IDENTITY + /// The USB UART channel behind this port; nullptr on non-USB ports + usb_uart::USBUartChannel *usb_channel_{nullptr}; +#endif }; } // namespace esphome::serial_proxy diff --git a/esphome/components/servo/__init__.py b/esphome/components/servo/__init__.py index 74acaa34962..44ec294765b 100644 --- a/esphome/components/servo/__init__.py +++ b/esphome/components/servo/__init__.py @@ -19,6 +19,7 @@ servo_ns = cg.esphome_ns.namespace("servo") Servo = servo_ns.class_("Servo", cg.Component) CONF_AUTO_DETACH_TIME = "auto_detach_time" +DOMAIN = "servo" MULTI_CONF = True CONFIG_SCHEMA = cv.Schema( { diff --git a/esphome/components/servo/servo.cpp b/esphome/components/servo/servo.cpp index 8d5344cf440..a415fdb8f77 100644 --- a/esphome/components/servo/servo.cpp +++ b/esphome/components/servo/servo.cpp @@ -5,7 +5,7 @@ namespace esphome::servo { -static const char *const TAG = "servo"; +ESPHOME_LOG_TAG(TAG, "servo"); uint32_t global_servo_id = 1911044085ULL; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/sfa30/__init__.py b/esphome/components/sfa30/__init__.py index 28b665906f5..d608a07f79e 100644 --- a/esphome/components/sfa30/__init__.py +++ b/esphome/components/sfa30/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@ghsensdev"] +DOMAIN = "sfa30" diff --git a/esphome/components/sfa30/sfa30.cpp b/esphome/components/sfa30/sfa30.cpp index 960806e98b0..efa3d8dc61f 100644 --- a/esphome/components/sfa30/sfa30.cpp +++ b/esphome/components/sfa30/sfa30.cpp @@ -3,7 +3,7 @@ namespace esphome::sfa30 { -static const char *const TAG = "sfa30"; +ESPHOME_LOG_TAG(TAG, "sfa30"); static const uint16_t SFA30_CMD_GET_DEVICE_MARKING = 0xD060; static const uint16_t SFA30_CMD_START_CONTINUOUS_MEASUREMENTS = 0x0006; diff --git a/esphome/components/sfa40/__init__.py b/esphome/components/sfa40/__init__.py index 79568d458a3..077f0d2e9f9 100644 --- a/esphome/components/sfa40/__init__.py +++ b/esphome/components/sfa40/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@NoQuarrel"] +DOMAIN = "sfa40" diff --git a/esphome/components/sfa40/sfa40.cpp b/esphome/components/sfa40/sfa40.cpp index 0d6bee3f9a8..31a25a4f384 100644 --- a/esphome/components/sfa40/sfa40.cpp +++ b/esphome/components/sfa40/sfa40.cpp @@ -4,7 +4,7 @@ namespace esphome::sfa40 { -static const char *const TAG = "sfa40"; +ESPHOME_LOG_TAG(TAG, "sfa40"); // SFA40 Datasheet: https://sensirion.com/media/documents/5B06EDD9/69F84BD8/Sensirion_Datasheet_SFA40.pdf diff --git a/esphome/components/sgp30/__init__.py b/esphome/components/sgp30/__init__.py index e69de29bb2d..4754f35d59f 100644 --- a/esphome/components/sgp30/__init__.py +++ b/esphome/components/sgp30/__init__.py @@ -0,0 +1 @@ +DOMAIN = "sgp30" diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index fd007c92cc8..72827ad51b5 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -8,7 +8,7 @@ namespace esphome::sgp30 { -static const char *const TAG = "sgp30"; +ESPHOME_LOG_TAG(TAG, "sgp30"); static const uint16_t SGP30_CMD_GET_SERIAL_ID = 0x3682; static const uint16_t SGP30_CMD_GET_FEATURESET = 0x202f; diff --git a/esphome/components/sgp40/__init__.py b/esphome/components/sgp40/__init__.py index e69de29bb2d..f18cc0d930c 100644 --- a/esphome/components/sgp40/__init__.py +++ b/esphome/components/sgp40/__init__.py @@ -0,0 +1 @@ +DOMAIN = "sgp40" diff --git a/esphome/components/sgp4x/__init__.py b/esphome/components/sgp4x/__init__.py index e69de29bb2d..f8030fdae05 100644 --- a/esphome/components/sgp4x/__init__.py +++ b/esphome/components/sgp4x/__init__.py @@ -0,0 +1 @@ +DOMAIN = "sgp4x" diff --git a/esphome/components/sgp4x/sgp4x.cpp b/esphome/components/sgp4x/sgp4x.cpp index 0cf5c314830..854dddba059 100644 --- a/esphome/components/sgp4x/sgp4x.cpp +++ b/esphome/components/sgp4x/sgp4x.cpp @@ -7,7 +7,7 @@ namespace esphome::sgp4x { -static const char *const TAG = "sgp4x"; +ESPHOME_LOG_TAG(TAG, "sgp4x"); void SGP4xComponent::setup() { // Serial Number identification diff --git a/esphome/components/sha256/__init__.py b/esphome/components/sha256/__init__.py index 5db0e77b769..5856d230a2f 100644 --- a/esphome/components/sha256/__init__.py +++ b/esphome/components/sha256/__init__.py @@ -5,6 +5,7 @@ from esphome.helpers import IS_MACOS from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] +DOMAIN = "sha256" sha256_ns = cg.esphome_ns.namespace("sha256") diff --git a/esphome/components/shelly_dimmer/__init__.py b/esphome/components/shelly_dimmer/__init__.py index accefbbc34b..72586d16371 100644 --- a/esphome/components/shelly_dimmer/__init__.py +++ b/esphome/components/shelly_dimmer/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@rnauber", "@edge90"] +DOMAIN = "shelly_dimmer" diff --git a/esphome/components/shelly_dimmer/shelly_dimmer.cpp b/esphome/components/shelly_dimmer/shelly_dimmer.cpp index b69e4175912..eedf7ff7ce2 100644 --- a/esphome/components/shelly_dimmer/shelly_dimmer.cpp +++ b/esphome/components/shelly_dimmer/shelly_dimmer.cpp @@ -19,7 +19,7 @@ namespace { -constexpr char TAG[] = "shelly_dimmer"; +ESPHOME_LOG_TAG(TAG, "shelly_dimmer"); constexpr uint8_t SHELLY_DIMMER_ACK_TIMEOUT = 200; // ms constexpr uint8_t SHELLY_DIMMER_MAX_RETRIES = 3; diff --git a/esphome/components/shelly_dimmer/stm32flash.cpp b/esphome/components/shelly_dimmer/stm32flash.cpp index beb015851ea..c33876e4943 100644 --- a/esphome/components/shelly_dimmer/stm32flash.cpp +++ b/esphome/components/shelly_dimmer/stm32flash.cpp @@ -108,7 +108,7 @@ constexpr uint8_t STM_OBL_LAUNCH_CODE[] = { constexpr uint32_t STM_OBL_LAUNCH_CODE_SIZE = sizeof(STM_OBL_LAUNCH_CODE); -constexpr char TAG[] = "stm32flash"; +ESPHOME_LOG_TAG(TAG, "stm32flash"); } // Anonymous namespace diff --git a/esphome/components/sht3xd/__init__.py b/esphome/components/sht3xd/__init__.py index e69de29bb2d..f58b3fb1503 100644 --- a/esphome/components/sht3xd/__init__.py +++ b/esphome/components/sht3xd/__init__.py @@ -0,0 +1 @@ +DOMAIN = "sht3xd" diff --git a/esphome/components/sht3xd/sht3xd.cpp b/esphome/components/sht3xd/sht3xd.cpp index eba6be65d58..672d119aed9 100644 --- a/esphome/components/sht3xd/sht3xd.cpp +++ b/esphome/components/sht3xd/sht3xd.cpp @@ -3,7 +3,7 @@ namespace esphome::sht3xd { -static const char *const TAG = "sht3xd"; +ESPHOME_LOG_TAG(TAG, "sht3xd"); // https://sensirion.com/media/documents/E5762713/63D103C2/Sensirion_electronic_identification_code_SHT3x.pdf // indicates two possible read serial number registers either with clock stretching enabled or disabled. diff --git a/esphome/components/sht4x/__init__.py b/esphome/components/sht4x/__init__.py index e69de29bb2d..2e9124bdab7 100644 --- a/esphome/components/sht4x/__init__.py +++ b/esphome/components/sht4x/__init__.py @@ -0,0 +1 @@ +DOMAIN = "sht4x" diff --git a/esphome/components/sht4x/sht4x.cpp b/esphome/components/sht4x/sht4x.cpp index 4a3df5a91f2..45de59fe0dc 100644 --- a/esphome/components/sht4x/sht4x.cpp +++ b/esphome/components/sht4x/sht4x.cpp @@ -4,7 +4,7 @@ namespace esphome::sht4x { -static const char *const TAG = "sht4x"; +ESPHOME_LOG_TAG(TAG, "sht4x"); static const uint8_t MEASURECOMMANDS[] = {0xFD, 0xF6, 0xE0}; static const uint8_t SERIAL_NUMBER_COMMAND = 0x89; diff --git a/esphome/components/shtcx/__init__.py b/esphome/components/shtcx/__init__.py index e69de29bb2d..30c06bdaecf 100644 --- a/esphome/components/shtcx/__init__.py +++ b/esphome/components/shtcx/__init__.py @@ -0,0 +1 @@ +DOMAIN = "shtcx" diff --git a/esphome/components/shtcx/shtcx.cpp b/esphome/components/shtcx/shtcx.cpp index 9ec0a2cdb7b..68f2c4ea21e 100644 --- a/esphome/components/shtcx/shtcx.cpp +++ b/esphome/components/shtcx/shtcx.cpp @@ -4,7 +4,7 @@ namespace esphome::shtcx { -static const char *const TAG = "shtcx"; +ESPHOME_LOG_TAG(TAG, "shtcx"); static constexpr uint16_t SHTCX_COMMAND_SLEEP = 0xB098; static constexpr uint16_t SHTCX_COMMAND_WAKEUP = 0x3517; diff --git a/esphome/components/shutdown/__init__.py b/esphome/components/shutdown/__init__.py index 480a6f3e31c..949ea69bb9d 100644 --- a/esphome/components/shutdown/__init__.py +++ b/esphome/components/shutdown/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@esphome/core", "@jsuanet"] +DOMAIN = "shutdown" diff --git a/esphome/components/shutdown/button/shutdown_button.cpp b/esphome/components/shutdown/button/shutdown_button.cpp index 6394c6c14e6..7a1380469bb 100644 --- a/esphome/components/shutdown/button/shutdown_button.cpp +++ b/esphome/components/shutdown/button/shutdown_button.cpp @@ -12,7 +12,7 @@ namespace esphome::shutdown { -static const char *const TAG = "shutdown.button"; +ESPHOME_LOG_TAG(TAG, "shutdown.button"); void ShutdownButton::dump_config() { LOG_BUTTON("", "Shutdown Button", this); } void ShutdownButton::press_action() { diff --git a/esphome/components/shutdown/switch/shutdown_switch.cpp b/esphome/components/shutdown/switch/shutdown_switch.cpp index a44a572aa1c..6e0a70ff82d 100644 --- a/esphome/components/shutdown/switch/shutdown_switch.cpp +++ b/esphome/components/shutdown/switch/shutdown_switch.cpp @@ -12,7 +12,7 @@ namespace esphome::shutdown { -static const char *const TAG = "shutdown.switch"; +ESPHOME_LOG_TAG(TAG, "shutdown.switch"); void ShutdownSwitch::dump_config() { LOG_SWITCH("", "Shutdown Switch", this); } void ShutdownSwitch::write_state(bool state) { diff --git a/esphome/components/sigma_delta_output/__init__.py b/esphome/components/sigma_delta_output/__init__.py index 3356e61bb25..2af211995bd 100644 --- a/esphome/components/sigma_delta_output/__init__.py +++ b/esphome/components/sigma_delta_output/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@Cat-Ion"] +DOMAIN = "sigma_delta_output" diff --git a/esphome/components/sigma_delta_output/sigma_delta_output.cpp b/esphome/components/sigma_delta_output/sigma_delta_output.cpp index 6cc131c68ff..37412c5d9b1 100644 --- a/esphome/components/sigma_delta_output/sigma_delta_output.cpp +++ b/esphome/components/sigma_delta_output/sigma_delta_output.cpp @@ -3,7 +3,7 @@ namespace esphome::sigma_delta_output { -static const char *const TAG = "output.sigma_delta"; +ESPHOME_LOG_TAG(TAG, "output.sigma_delta"); void SigmaDeltaOutput::setup() { if (this->pin_) diff --git a/esphome/components/sim800l/__init__.py b/esphome/components/sim800l/__init__.py index c472e01f83a..e20c091b75c 100644 --- a/esphome/components/sim800l/__init__.py +++ b/esphome/components/sim800l/__init__.py @@ -6,6 +6,7 @@ from esphome.const import CONF_ID, CONF_MESSAGE DEPENDENCIES = ["uart"] CODEOWNERS = ["@glmnet"] +DOMAIN = "sim800l" MULTI_CONF = True sim800l_ns = cg.esphome_ns.namespace("sim800l") diff --git a/esphome/components/sim800l/sim800l.cpp b/esphome/components/sim800l/sim800l.cpp index 13b9888e052..e75e5e95dd3 100644 --- a/esphome/components/sim800l/sim800l.cpp +++ b/esphome/components/sim800l/sim800l.cpp @@ -5,7 +5,7 @@ namespace esphome::sim800l { -static const char *const TAG = "sim800l"; +ESPHOME_LOG_TAG(TAG, "sim800l"); const char ASCII_CR = 0x0D; const char ASCII_LF = 0x0A; diff --git a/esphome/components/slow_pwm/__init__.py b/esphome/components/slow_pwm/__init__.py index e69de29bb2d..c46da34e38c 100644 --- a/esphome/components/slow_pwm/__init__.py +++ b/esphome/components/slow_pwm/__init__.py @@ -0,0 +1 @@ +DOMAIN = "slow_pwm" diff --git a/esphome/components/slow_pwm/slow_pwm_output.cpp b/esphome/components/slow_pwm/slow_pwm_output.cpp index e695ab95403..f41455bc805 100644 --- a/esphome/components/slow_pwm/slow_pwm_output.cpp +++ b/esphome/components/slow_pwm/slow_pwm_output.cpp @@ -5,7 +5,7 @@ namespace esphome::slow_pwm { -static const char *const TAG = "output.slow_pwm"; +ESPHOME_LOG_TAG(TAG, "output.slow_pwm"); void SlowPWMOutput::setup() { if (this->pin_) diff --git a/esphome/components/sm10bit_base/__init__.py b/esphome/components/sm10bit_base/__init__.py index 2b555a59df5..05ba98562d7 100644 --- a/esphome/components/sm10bit_base/__init__.py +++ b/esphome/components/sm10bit_base/__init__.py @@ -6,6 +6,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@Cossid"] +DOMAIN = "sm10bit_base" MULTI_CONF = True CONF_MAX_POWER_COLOR_CHANNELS = "max_power_color_channels" diff --git a/esphome/components/sm10bit_base/sm10bit_base.cpp b/esphome/components/sm10bit_base/sm10bit_base.cpp index 45de3c457d2..d01ec5dbdba 100644 --- a/esphome/components/sm10bit_base/sm10bit_base.cpp +++ b/esphome/components/sm10bit_base/sm10bit_base.cpp @@ -3,7 +3,7 @@ namespace esphome::sm10bit_base { -static const char *const TAG = "sm10bit_base"; +ESPHOME_LOG_TAG(TAG, "sm10bit_base"); static const uint8_t SM10BIT_ADDR_STANDBY = 0x0; static const uint8_t SM10BIT_ADDR_START_3CH = 0x8; diff --git a/esphome/components/sm16716/__init__.py b/esphome/components/sm16716/__init__.py index 3fb9ac3f06b..ae3c54ab7c7 100644 --- a/esphome/components/sm16716/__init__.py +++ b/esphome/components/sm16716/__init__.py @@ -11,6 +11,7 @@ from esphome.const import ( from esphome.types import ConfigType AUTO_LOAD = ["output"] +DOMAIN = "sm16716" sm16716_ns = cg.esphome_ns.namespace("sm16716") SM16716 = sm16716_ns.class_("SM16716", cg.Component) diff --git a/esphome/components/sm16716/sm16716.cpp b/esphome/components/sm16716/sm16716.cpp index 59e9f1b7120..647a40dc47c 100644 --- a/esphome/components/sm16716/sm16716.cpp +++ b/esphome/components/sm16716/sm16716.cpp @@ -3,7 +3,7 @@ namespace esphome::sm16716 { -static const char *const TAG = "sm16716"; +ESPHOME_LOG_TAG(TAG, "sm16716"); void SM16716::setup() { this->data_pin_->setup(); diff --git a/esphome/components/sm2135/__init__.py b/esphome/components/sm2135/__init__.py index 537db5bbe49..c42ce4c8b3f 100644 --- a/esphome/components/sm2135/__init__.py +++ b/esphome/components/sm2135/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["output"] CODEOWNERS = ["@BoukeHaarsma23", "@matika77", "@dd32"] +DOMAIN = "sm2135" sm2135_ns = cg.esphome_ns.namespace("sm2135") SM2135 = sm2135_ns.class_("SM2135", cg.Component) diff --git a/esphome/components/sm2135/sm2135.cpp b/esphome/components/sm2135/sm2135.cpp index 0086a63878b..c8a4287b65a 100644 --- a/esphome/components/sm2135/sm2135.cpp +++ b/esphome/components/sm2135/sm2135.cpp @@ -5,7 +5,7 @@ namespace esphome::sm2135 { -static const char *const TAG = "sm2135"; +ESPHOME_LOG_TAG(TAG, "sm2135"); static const uint8_t SM2135_ADDR_MC = 0xC0; // Max current register static const uint8_t SM2135_ADDR_CH = 0xC1; // RGB or CW channel select register diff --git a/esphome/components/sm2235/__init__.py b/esphome/components/sm2235/__init__.py index 95037fd2756..803cd54be59 100644 --- a/esphome/components/sm2235/__init__.py +++ b/esphome/components/sm2235/__init__.py @@ -5,6 +5,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["sm10bit_base", "output"] CODEOWNERS = ["@Cossid"] +DOMAIN = "sm2235" MULTI_CONF = True sm2235_ns = cg.esphome_ns.namespace("sm2235") diff --git a/esphome/components/sm2235/sm2235.cpp b/esphome/components/sm2235/sm2235.cpp index e981bd0f71f..3bd302047bc 100644 --- a/esphome/components/sm2235/sm2235.cpp +++ b/esphome/components/sm2235/sm2235.cpp @@ -3,7 +3,7 @@ namespace esphome::sm2235 { -static const char *const TAG = "sm2235"; +ESPHOME_LOG_TAG(TAG, "sm2235"); void SM2235::setup() { this->data_pin_->setup(); diff --git a/esphome/components/sm2335/__init__.py b/esphome/components/sm2335/__init__.py index 145c5a0117d..a9fdfcef391 100644 --- a/esphome/components/sm2335/__init__.py +++ b/esphome/components/sm2335/__init__.py @@ -5,6 +5,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["sm10bit_base", "output"] CODEOWNERS = ["@Cossid"] +DOMAIN = "sm2335" MULTI_CONF = True sm2335_ns = cg.esphome_ns.namespace("sm2335") diff --git a/esphome/components/sm2335/sm2335.cpp b/esphome/components/sm2335/sm2335.cpp index 93f1096800f..6e0d7b35887 100644 --- a/esphome/components/sm2335/sm2335.cpp +++ b/esphome/components/sm2335/sm2335.cpp @@ -3,7 +3,7 @@ namespace esphome::sm2335 { -static const char *const TAG = "sm2335"; +ESPHOME_LOG_TAG(TAG, "sm2335"); void SM2335::setup() { this->data_pin_->setup(); diff --git a/esphome/components/sm300d2/__init__.py b/esphome/components/sm300d2/__init__.py index e69de29bb2d..da0a0637ed4 100644 --- a/esphome/components/sm300d2/__init__.py +++ b/esphome/components/sm300d2/__init__.py @@ -0,0 +1 @@ +DOMAIN = "sm300d2" diff --git a/esphome/components/sm300d2/sm300d2.cpp b/esphome/components/sm300d2/sm300d2.cpp index 882959a4542..0660e0f20e4 100644 --- a/esphome/components/sm300d2/sm300d2.cpp +++ b/esphome/components/sm300d2/sm300d2.cpp @@ -3,7 +3,7 @@ namespace esphome::sm300d2 { -static const char *const TAG = "sm300d2"; +ESPHOME_LOG_TAG(TAG, "sm300d2"); static const uint8_t SM300D2_RESPONSE_LENGTH = 17; void SM300D2Sensor::update() { diff --git a/esphome/components/sml/__init__.py b/esphome/components/sml/__init__.py index 07ca5bf4447..35ca231f023 100644 --- a/esphome/components/sml/__init__.py +++ b/esphome/components/sml/__init__.py @@ -9,6 +9,7 @@ from esphome.const import CONF_ID, CONF_ON_DATA from esphome.types import ConfigType CODEOWNERS = ["@alengwenus"] +DOMAIN = "sml" DEPENDENCIES = ["uart"] diff --git a/esphome/components/sml/sensor/sml_sensor.cpp b/esphome/components/sml/sensor/sml_sensor.cpp index 047a13e88d8..9861edcf8f7 100644 --- a/esphome/components/sml/sensor/sml_sensor.cpp +++ b/esphome/components/sml/sensor/sml_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::sml { -static const char *const TAG = "sml_sensor"; +ESPHOME_LOG_TAG(TAG, "sml_sensor"); SmlSensor::SmlSensor(std::string server_id, std::string obis_code) : SmlListener(std::move(server_id), std::move(obis_code)) {} diff --git a/esphome/components/sml/sml.cpp b/esphome/components/sml/sml.cpp index bacfbcccef0..a4bc4373d7c 100644 --- a/esphome/components/sml/sml.cpp +++ b/esphome/components/sml/sml.cpp @@ -5,7 +5,7 @@ namespace esphome::sml { -static const char *const TAG = "sml"; +ESPHOME_LOG_TAG(TAG, "sml"); const char START_BYTES_DETECTED = 1; const char END_BYTES_DETECTED = 2; diff --git a/esphome/components/sml/text_sensor/sml_text_sensor.cpp b/esphome/components/sml/text_sensor/sml_text_sensor.cpp index d0965e3dcaa..9bd31923ecd 100644 --- a/esphome/components/sml/text_sensor/sml_text_sensor.cpp +++ b/esphome/components/sml/text_sensor/sml_text_sensor.cpp @@ -6,7 +6,7 @@ namespace esphome::sml { -static const char *const TAG = "sml_text_sensor"; +ESPHOME_LOG_TAG(TAG, "sml_text_sensor"); SmlTextSensor::SmlTextSensor(std::string server_id, std::string obis_code, SmlType format) : SmlListener(std::move(server_id), std::move(obis_code)), format_(format) {} diff --git a/esphome/components/smt100/__init__.py b/esphome/components/smt100/__init__.py index 44ed591400a..572df68d99b 100644 --- a/esphome/components/smt100/__init__.py +++ b/esphome/components/smt100/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@piechade"] +DOMAIN = "smt100" diff --git a/esphome/components/smt100/smt100.cpp b/esphome/components/smt100/smt100.cpp index 2889a9fb4d9..7785bbca2d7 100644 --- a/esphome/components/smt100/smt100.cpp +++ b/esphome/components/smt100/smt100.cpp @@ -3,7 +3,7 @@ namespace esphome::smt100 { -static const char *const TAG = "smt100"; +ESPHOME_LOG_TAG(TAG, "smt100"); void SMT100Component::update() { ESP_LOGV(TAG, "Sending measurement request"); diff --git a/esphome/components/sn74hc165/__init__.py b/esphome/components/sn74hc165/__init__.py index 4f21312fec8..af566cebda9 100644 --- a/esphome/components/sn74hc165/__init__.py +++ b/esphome/components/sn74hc165/__init__.py @@ -15,6 +15,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@jesserockz"] DEPENDENCIES = [] +DOMAIN = "sn74hc165" MULTI_CONF = True sn74hc165_ns = cg.esphome_ns.namespace("sn74hc165") diff --git a/esphome/components/sn74hc165/sn74hc165.cpp b/esphome/components/sn74hc165/sn74hc165.cpp index fde789e90c7..318390e8e22 100644 --- a/esphome/components/sn74hc165/sn74hc165.cpp +++ b/esphome/components/sn74hc165/sn74hc165.cpp @@ -3,7 +3,7 @@ namespace esphome::sn74hc165 { -static const char *const TAG = "sn74hc165"; +ESPHOME_LOG_TAG(TAG, "sn74hc165"); void SN74HC165Component::setup() { // initialize pins diff --git a/esphome/components/sn74hc595/__init__.py b/esphome/components/sn74hc595/__init__.py index 367b65176ba..78ed703ed9e 100644 --- a/esphome/components/sn74hc595/__init__.py +++ b/esphome/components/sn74hc595/__init__.py @@ -15,6 +15,7 @@ from esphome.const import ( from esphome.cpp_generator import MockObj from esphome.types import ConfigType +DOMAIN = "sn74hc595" MULTI_CONF = True sn74hc595_ns = cg.esphome_ns.namespace("sn74hc595") diff --git a/esphome/components/sn74hc595/sn74hc595.cpp b/esphome/components/sn74hc595/sn74hc595.cpp index 710e51ad128..4691ffcf6d7 100644 --- a/esphome/components/sn74hc595/sn74hc595.cpp +++ b/esphome/components/sn74hc595/sn74hc595.cpp @@ -4,7 +4,7 @@ namespace esphome::sn74hc595 { -static const char *const TAG = "sn74hc595"; +ESPHOME_LOG_TAG(TAG, "sn74hc595"); void SN74HC595Component::pre_setup_() { if (this->have_oe_pin_) { // disable output diff --git a/esphome/components/snapshot/display/snapshot_display.cpp b/esphome/components/snapshot/display/snapshot_display.cpp index 6297e3e18f7..6618d9ae74a 100644 --- a/esphome/components/snapshot/display/snapshot_display.cpp +++ b/esphome/components/snapshot/display/snapshot_display.cpp @@ -7,7 +7,7 @@ namespace esphome::snapshot { -static const char *const TAG = "snapshot.display"; +ESPHOME_LOG_TAG(TAG, "snapshot.display"); namespace { diff --git a/esphome/components/snapshot/snapshot.cpp b/esphome/components/snapshot/snapshot.cpp index 096ec48eb3b..58406f17ffd 100644 --- a/esphome/components/snapshot/snapshot.cpp +++ b/esphome/components/snapshot/snapshot.cpp @@ -22,7 +22,7 @@ namespace esphome::snapshot { namespace { -constexpr const char *const TAG = "snapshot"; +ESPHOME_LOG_TAG(TAG, "snapshot"); // Longest name we will build a path from. NAME_MAX is 255 and we may append a collision suffix. constexpr size_t MAX_NAME_LENGTH = 200; diff --git a/esphome/components/sntp/__init__.py b/esphome/components/sntp/__init__.py index e69de29bb2d..481183e0208 100644 --- a/esphome/components/sntp/__init__.py +++ b/esphome/components/sntp/__init__.py @@ -0,0 +1 @@ +DOMAIN = "sntp" diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp index f8d48b40981..de951483bba 100644 --- a/esphome/components/sntp/sntp_component.cpp +++ b/esphome/components/sntp/sntp_component.cpp @@ -11,7 +11,7 @@ namespace esphome::sntp { -static const char *const TAG = "sntp"; +ESPHOME_LOG_TAG(TAG, "sntp"); #if defined(USE_ESP32) SNTPComponent *SNTPComponent::instance = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/sntp/time.py b/esphome/components/sntp/time.py index 9d849f9fe9b..9365339473e 100644 --- a/esphome/components/sntp/time.py +++ b/esphome/components/sntp/time.py @@ -2,13 +2,13 @@ import logging import esphome.codegen as cg from esphome.components import time as time_ +from esphome.components.time import DOMAIN as TIME_DOMAIN from esphome.config_helpers import merge_config import esphome.config_validation as cv from esphome.const import ( CONF_ID, CONF_PLATFORM, CONF_SERVERS, - CONF_TIME, PLATFORM_BK72XX, PLATFORM_ESP32, PLATFORM_ESP8266, @@ -20,11 +20,12 @@ from esphome.core import CORE import esphome.final_validate as fv from esphome.types import ConfigType +from . import DOMAIN + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ["network"] -CONF_SNTP = "sntp" sntp_ns = cg.esphome_ns.namespace("sntp") SNTPComponent = sntp_ns.class_("SNTPComponent", time_.RealTimeClock) @@ -35,13 +36,13 @@ DEFAULT_SERVERS = ["0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org"] def _sntp_final_validate(config: ConfigType) -> None: """Merge multiple SNTP instances into one, similar to OTA merging behavior.""" full_conf = fv.full_config.get() - time_confs = full_conf.get(CONF_TIME, []) + time_confs = full_conf.get(TIME_DOMAIN, []) sntp_configs: list[ConfigType] = [] other_time_configs: list[ConfigType] = [] for time_conf in time_confs: - if time_conf.get(CONF_PLATFORM) == CONF_SNTP: + if time_conf.get(CONF_PLATFORM) == DOMAIN: sntp_configs.append(time_conf) else: other_time_configs.append(time_conf) @@ -81,7 +82,7 @@ def _sntp_final_validate(config: ConfigType) -> None: # Replace time configs with merged SNTP + other time platforms other_time_configs.append(merged) - full_conf[CONF_TIME] = other_time_configs + full_conf[TIME_DOMAIN] = other_time_configs fv.full_config.set(full_conf) diff --git a/esphome/components/socket/__init__.py b/esphome/components/socket/__init__.py index 269791f5e7a..cc5f672ad55 100644 --- a/esphome/components/socket/__init__.py +++ b/esphome/components/socket/__init__.py @@ -14,6 +14,7 @@ from esphome.types import ConfigType _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@esphome/core"] +DOMAIN = "socket" socket_ns = cg.esphome_ns.namespace("socket") Ipv4AllowEntry = socket_ns.struct("Ipv4AllowEntry") @@ -149,6 +150,21 @@ def require_wake_loop_threadsafe() -> None: IPV4_ALLOW_SCHEMA = cv.All(cv.ensure_list(cv.ipv4network), cv.Length(max=255)) +_HOST = cv.Any(cv.domain, cv.hostname) + + +def ipv4_host(value: object) -> str: + """Validate an IPv4 address or a hostname; the resolver behind it is IPv4 only.""" + value = cv.string(value) + try: + cv.ipv6address(value) + except cv.Invalid: + return _HOST(value) + raise cv.Invalid( + "IPv6 addresses are not supported, use an IPv4 address or a hostname" + ) + + def _network_order(addr: IPv4Address) -> int: """The s_addr value for addr on the little endian targets.""" return int.from_bytes(addr.packed, "little") diff --git a/esphome/components/socket/ipv4_resolve.cpp b/esphome/components/socket/ipv4_resolve.cpp index d2316cd064b..d5da8745b97 100644 --- a/esphome/components/socket/ipv4_resolve.cpp +++ b/esphome/components/socket/ipv4_resolve.cpp @@ -16,7 +16,7 @@ namespace esphome::socket { -static const char *const TAG = "socket"; +ESPHOME_LOG_TAG(TAG, "socket"); bool Ipv4Resolve::consume_failure() { #if defined(IPV4_RESOLVE_ATOMIC_STATE) diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index b411445b98a..2b21291c30b 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -43,7 +43,7 @@ namespace esphome::socket { // (Ethernet). On ESP8266, it's a no-op. #define LWIP_LOCK() esphome::LwIPLock lwip_lock_guard // NOLINT -static const char *const TAG = "socket"; +ESPHOME_LOG_TAG(TAG, "socket"); #ifdef USE_ESP8266 // optimistic_yield() rate limit in microseconds of CONT time; cheap when hot. diff --git a/esphome/components/socket/tcp_client_link.cpp b/esphome/components/socket/tcp_client_link.cpp index 141695a4bb6..9812f1835e1 100644 --- a/esphome/components/socket/tcp_client_link.cpp +++ b/esphome/components/socket/tcp_client_link.cpp @@ -17,19 +17,23 @@ static constexpr uint32_t CONNECT_TIMEOUT_MS = 10000; // Non-blocking options and TCP keepalive for a bridged stream socket. // Keepalive is best-effort: the raw lwIP implementation (ESP8266, RP2040) // rejects it, so a half-open link there is only detected by a failed write. -static void set_stream_options(Socket *sock) { +static void set_stream_options(Socket *sock, const char *tag) { int yes = 1; + // Fails only on an invalid descriptor, or on raw lwIP after a peer reset that the next read() reports. sock->setblocking(false); - sock->setsockopt(IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)); - sock->setsockopt(SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)); + int err = sock->setsockopt(IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)); + err |= sock->setsockopt(SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)); #ifdef TCP_KEEPIDLE int idle = 30; int interval = 10; int count = 3; - sock->setsockopt(IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)); - sock->setsockopt(IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval)); - sock->setsockopt(IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count)); + err |= sock->setsockopt(IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)); + err |= sock->setsockopt(IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval)); + err |= sock->setsockopt(IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count)); #endif + if (err != 0) { + ESP_LOGV(tag, "Nodelay/keepalive not fully applied"); + } } void TcpClientLink::begin(const char *tag) { @@ -84,7 +88,7 @@ void TcpClientLink::try_connect_() { this->drop_(LOG_STR("Connect failed"), errno); return; } - set_stream_options(this->sock_.get()); + set_stream_options(this->sock_.get(), this->tag_); // Starts the pending-connect clock that poll() times out against. this->note_attempt(); // An immediate success is reported by the next poll(); poll_connect() sees it writable. @@ -95,7 +99,7 @@ void TcpClientLink::try_connect_() { void TcpClientLink::adopt(std::unique_ptr sock) { this->close(); - set_stream_options(sock.get()); + set_stream_options(sock.get(), this->tag_); this->sock_ = std::move(sock); this->connected_ = true; } diff --git a/esphome/components/sonoff_d1/__init__.py b/esphome/components/sonoff_d1/__init__.py index 18b4d30d183..40a6c103db6 100644 --- a/esphome/components/sonoff_d1/__init__.py +++ b/esphome/components/sonoff_d1/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@anatoly-savchenkov"] +DOMAIN = "sonoff_d1" diff --git a/esphome/components/sonoff_d1/sonoff_d1.cpp b/esphome/components/sonoff_d1/sonoff_d1.cpp index 3e5af2b51ff..02282fef9ca 100644 --- a/esphome/components/sonoff_d1/sonoff_d1.cpp +++ b/esphome/components/sonoff_d1/sonoff_d1.cpp @@ -46,7 +46,7 @@ namespace esphome::sonoff_d1 { -static const char *const TAG = "sonoff_d1"; +ESPHOME_LOG_TAG(TAG, "sonoff_d1"); // Protocol constants static constexpr size_t SONOFF_D1_ACK_SIZE = 7; diff --git a/esphome/components/sound_level/__init__.py b/esphome/components/sound_level/__init__.py index e69de29bb2d..93f0f4b2e38 100644 --- a/esphome/components/sound_level/__init__.py +++ b/esphome/components/sound_level/__init__.py @@ -0,0 +1 @@ +DOMAIN = "sound_level" diff --git a/esphome/components/sound_level/sound_level.cpp b/esphome/components/sound_level/sound_level.cpp index 99ab7932d68..1e51e45b7b3 100644 --- a/esphome/components/sound_level/sound_level.cpp +++ b/esphome/components/sound_level/sound_level.cpp @@ -9,7 +9,7 @@ namespace esphome::sound_level { -static const char *const TAG = "sound_level"; +ESPHOME_LOG_TAG(TAG, "sound_level"); static const uint32_t MAX_FILL_DURATION_MS = 30; static const uint32_t RING_BUFFER_DURATION_MS = 120; @@ -171,7 +171,7 @@ bool SoundLevelComponent::start_() { (stream_info.ms_to_bytes(RING_BUFFER_DURATION_MS) / bytes_per_frame) * bytes_per_frame; std::shared_ptr temp_ring_buffer = ring_buffer::RingBuffer::create(ring_buffer_size); if (temp_ring_buffer == nullptr) { - this->status_momentary_error("ring_buffer", 15000); + this->status_momentary_error(15000); return false; } @@ -180,7 +180,7 @@ bool SoundLevelComponent::start_() { this->audio_source_ = audio::RingBufferAudioSource::create( temp_ring_buffer, stream_info.ms_to_bytes(MAX_FILL_DURATION_MS), static_cast(bytes_per_frame)); if (this->audio_source_ == nullptr) { - this->status_momentary_error("audio_source", 15000); + this->status_momentary_error(15000); return false; } this->ring_buffer_ = temp_ring_buffer; diff --git a/esphome/components/spa06_base/__init__.py b/esphome/components/spa06_base/__init__.py index e084020a141..c08d92f9d8b 100644 --- a/esphome/components/spa06_base/__init__.py +++ b/esphome/components/spa06_base/__init__.py @@ -19,6 +19,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@danielkent-net"] +DOMAIN = "spa06_base" spa06_ns = cg.esphome_ns.namespace("spa06_base") diff --git a/esphome/components/spa06_base/spa06_base.cpp b/esphome/components/spa06_base/spa06_base.cpp index d3de5168e48..02defd07735 100644 --- a/esphome/components/spa06_base/spa06_base.cpp +++ b/esphome/components/spa06_base/spa06_base.cpp @@ -6,7 +6,9 @@ namespace esphome::spa06_base { -static const char *const TAG = "spa06"; +ESPHOME_LOG_TAG(TAG, "spa06"); + +static constexpr uint32_t MEASUREMENT_TIMEOUT_ID = 0; // Sign extension function for <=16 bit types inline int16_t decode16(uint8_t msb, uint8_t lsb, size_t bits, size_t head = 0) { @@ -243,7 +245,7 @@ void SPA06Component::update() { } // Queue a background task for retrieving the measurement - this->set_timeout("measurement", this->conversion_time_, [this]() { + this->set_timeout(MEASUREMENT_TIMEOUT_ID, this->conversion_time_, [this]() { float raw_temperature; float temperature = 0.0; float pressure = 0.0; diff --git a/esphome/components/spa06_i2c/__init__.py b/esphome/components/spa06_i2c/__init__.py index e69de29bb2d..928c6c157b5 100644 --- a/esphome/components/spa06_i2c/__init__.py +++ b/esphome/components/spa06_i2c/__init__.py @@ -0,0 +1 @@ +DOMAIN = "spa06_i2c" diff --git a/esphome/components/spa06_i2c/spa06_i2c.cpp b/esphome/components/spa06_i2c/spa06_i2c.cpp index 4970b0822d7..cdb0818d39e 100644 --- a/esphome/components/spa06_i2c/spa06_i2c.cpp +++ b/esphome/components/spa06_i2c/spa06_i2c.cpp @@ -4,7 +4,7 @@ namespace esphome::spa06_i2c { -static const char *const TAG = "spa06_i2c"; +ESPHOME_LOG_TAG(TAG, "spa06_i2c"); void SPA06I2CComponent::dump_config() { LOG_I2C_DEVICE(this); diff --git a/esphome/components/spa06_spi/__init__.py b/esphome/components/spa06_spi/__init__.py index e69de29bb2d..b4739219991 100644 --- a/esphome/components/spa06_spi/__init__.py +++ b/esphome/components/spa06_spi/__init__.py @@ -0,0 +1 @@ +DOMAIN = "spa06_spi" diff --git a/esphome/components/spa06_spi/spa06_spi.cpp b/esphome/components/spa06_spi/spa06_spi.cpp index 9f3683d2197..08d725d4f8d 100644 --- a/esphome/components/spa06_spi/spa06_spi.cpp +++ b/esphome/components/spa06_spi/spa06_spi.cpp @@ -13,7 +13,7 @@ inline constexpr uint8_t SPA06_SPI_WRITE = 0x7F; namespace esphome::spa06_spi { -static const char *const TAG = "spa06_spi"; +ESPHOME_LOG_TAG(TAG, "spa06_spi"); void SPA06SPIComponent::dump_config() { SPA06Component::dump_config(); diff --git a/esphome/components/spd2010/__init__.py b/esphome/components/spd2010/__init__.py new file mode 100644 index 00000000000..67a78e5f287 --- /dev/null +++ b/esphome/components/spd2010/__init__.py @@ -0,0 +1,3 @@ +CODEOWNERS = ["@grischard"] +DEPENDENCIES = ["i2c"] +DOMAIN = "spd2010" diff --git a/esphome/components/spd2010/spd2010_touchscreen.cpp b/esphome/components/spd2010/spd2010_touchscreen.cpp new file mode 100644 index 00000000000..94fad87d7d4 --- /dev/null +++ b/esphome/components/spd2010/spd2010_touchscreen.cpp @@ -0,0 +1,211 @@ +// SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +// SPDX-License-Identifier: Apache-2.0 +// Protocol adapted from esp_lcd_touch_spd2010 2.0.1 for ESPHome. +// https://github.com/espressif/esp-bsp/tree/master/components/lcd_touch/esp_lcd_touch_spd2010 + +#include "spd2010_touchscreen.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome::spd2010 { + +ESPHOME_LOG_TAG(TAG, "spd2010.touchscreen"); + +static constexpr uint16_t REG_CLEAR_INTERRUPT = 0x0002; +static constexpr uint16_t REG_CPU_START = 0x0004; +static constexpr uint16_t REG_STATUS = 0x0020; +static constexpr uint16_t REG_VERSION = 0x0026; +static constexpr uint16_t REG_TOUCH_START = 0x0046; +static constexpr uint16_t REG_POINT_MODE = 0x0050; +static constexpr uint16_t REG_HDP_STATUS = 0x02FC; +static constexpr uint16_t REG_HDP = 0x0300; + +// Low status byte +static constexpr uint8_t STATUS_POINT = 0x01; +static constexpr uint8_t STATUS_GESTURE = 0x02; +static constexpr uint8_t STATUS_AUX = 0x08; +// High status byte +static constexpr uint8_t STATUS_BIOS = 0x40; +static constexpr uint8_t STATUS_CPU = 0x20; +static constexpr uint8_t STATUS_RUNNING = 0x08; + +static constexpr uint8_t HDP_STATUS_DONE = 0x82; +static constexpr uint8_t HDP_STATUS_MORE = 0x00; + +static constexpr uint8_t MAX_CONTACT_ID = 0x0A; +static constexpr uint8_t GESTURE_REPORT_ID = 0xF6; +static constexpr size_t REPORT_HEADER_LENGTH = 4; +static constexpr size_t CONTACT_LENGTH = 6; +static constexpr size_t MAX_REPORT_LENGTH = REPORT_HEADER_LENGTH + 10 * CONTACT_LENGTH; +static constexpr uint8_t MAX_DRAIN_READS = 8; + +static constexpr uint32_t RESET_PULSE_WIDTH = + 20; // longer than the datasheet says, but the controller seems to need it +static constexpr uint32_t RESET_DELAY = 20; + +bool SPD2010Touchscreen::read_register_(uint16_t reg, uint8_t *data, size_t length) const { + const uint8_t command[] = {static_cast(reg), static_cast(reg >> 8u)}; + if (this->write(command, sizeof(command)) != i2c::ERROR_OK) + return false; + // The controller needs a STOP and a delay before a commandless receive. + delay_microseconds_safe(200); + return this->read(data, length) == i2c::ERROR_OK; +} + +bool SPD2010Touchscreen::write_command_(uint16_t reg, uint16_t value) const { + const uint8_t command[]{ + static_cast(reg), + static_cast(reg >> 8u), + static_cast(value), + static_cast(value >> 8u), + }; + return this->write(command, sizeof(command)) == i2c::ERROR_OK; +} + +bool SPD2010Touchscreen::clear_interrupt_() const { return this->write_command_(REG_CLEAR_INTERRUPT, 1); } + +void SPD2010Touchscreen::setup() { + if (this->x_raw_max_ == this->x_raw_min_) + this->x_raw_max_ = this->display_->get_native_width() - 1; + if (this->y_raw_max_ == this->y_raw_min_) + this->y_raw_max_ = this->display_->get_native_height() - 1; + if (this->reset_pin_ != nullptr) { + this->reset_pin_->setup(); + this->reset_pin_->digital_write(false); + this->set_timeout(RESET_PULSE_WIDTH, [this] { + this->reset_pin_->digital_write(true); + this->set_timeout(RESET_DELAY, [this] { this->initialize_(); }); + }); + } else { + this->initialize_(); + } +} + +void SPD2010Touchscreen::initialize_() { + uint8_t version[18]; + if (!this->read_register_(REG_VERSION, version, sizeof(version))) { + this->mark_failed(LOG_STR("Failed to read firmware version")); + return; + } + ESP_LOGD(TAG, "Firmware version: 0x%04X", encode_uint16(version[5], version[4])); + if (this->interrupt_pin_ != nullptr) { + this->interrupt_pin_->setup(); + this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE); + } + this->ready_ = true; +} + +bool SPD2010Touchscreen::finish_report_() { + // A corrupt status must not cause an unbounded drain loop or buffer overflow. + for (uint8_t attempt = 0; attempt != MAX_DRAIN_READS; attempt++) { + uint8_t status[8]; + if (!this->read_register_(REG_HDP_STATUS, status, sizeof(status))) + return false; + if (status[5] == HDP_STATUS_DONE) + return this->clear_interrupt_(); + const uint16_t length = encode_uint16(status[3], status[2]); + if (status[5] != HDP_STATUS_MORE || length == 0 || length > MAX_REPORT_LENGTH) + break; + uint8_t remaining[MAX_REPORT_LENGTH]; + if (!this->read_register_(REG_HDP, remaining, length)) + return false; + } + ESP_LOGW(TAG, "Invalid or unfinished report"); + (void) this->clear_interrupt_(); + return false; +} + +// Returns true only when touch data was read correctly and should be published. +bool SPD2010Touchscreen::read_data_() { + uint8_t status[4]; + if (!this->read_register_(REG_STATUS, status, sizeof(status))) { + this->status_set_warning(LOG_STR("Failed to read status")); + return false; + } + const uint8_t status_low = status[0]; + const uint8_t status_high = status[1]; + const uint16_t length = encode_uint16(status[3], status[2]); + + if (status_high & STATUS_BIOS) { + this->status_set_warning(LOG_STR("BIOS status; start CPU")); + (void) (this->clear_interrupt_() && this->write_command_(REG_CPU_START, 1)); + return false; + } + if (status_high & STATUS_CPU) { + this->status_set_warning(LOG_STR("CPU status; stop point mode, start touch")); + (void) (this->write_command_(REG_POINT_MODE, 0) && this->write_command_(REG_TOUCH_START, 0) && + this->clear_interrupt_()); + return false; + } + if ((status_high & STATUS_RUNNING) && length == 0) { + if (!this->clear_interrupt_()) { + this->status_set_warning(LOG_STR("Failed to clear interrupt")); + return false; + } + this->status_clear_warning(); + return true; + } + if (!(status_low & (STATUS_POINT | STATUS_GESTURE))) { + if ((status_high & STATUS_RUNNING) && (status_low & STATUS_AUX) && !this->clear_interrupt_()) { + this->status_set_warning(LOG_STR("Failed to clear interrupt")); + return false; + } + this->status_clear_warning(); + return false; + } + if (length < REPORT_HEADER_LENGTH || length > MAX_REPORT_LENGTH || + (length - REPORT_HEADER_LENGTH) % CONTACT_LENGTH != 0) { + ESP_LOGW(TAG, "Invalid report length: %u", length); + this->status_set_warning(LOG_STR("Invalid report length")); + (void) this->clear_interrupt_(); + return false; + } + uint8_t report[MAX_REPORT_LENGTH]; + if (!this->read_register_(REG_HDP, report, length) || !this->finish_report_()) { + this->status_set_warning(LOG_STR("Failed to read report")); + return false; + } + // Gesture reports are not used. + if (!(status_low & STATUS_POINT) || + (length > REPORT_HEADER_LENGTH && report[REPORT_HEADER_LENGTH] == GESTURE_REPORT_ID)) { + this->status_clear_warning(); + return false; + } + // Check every ID before adding any point, so a bad report leaves the stored touches unchanged. + for (size_t offset = REPORT_HEADER_LENGTH; offset < length; offset += CONTACT_LENGTH) { + if (report[offset] > MAX_CONTACT_ID) { + this->status_set_warning(LOG_STR("Invalid contact ID")); + return false; + } + } + for (size_t offset = REPORT_HEADER_LENGTH; offset < length; offset += CONTACT_LENGTH) { + const uint8_t *contact = report + offset; + // Zero strength marks a released contact. + if (contact[4] == 0) + continue; + const uint16_t x = ((contact[3] & 0xF0u) << 4u) | contact[1]; + const uint16_t y = ((contact[3] & 0x0Fu) << 8u) | contact[2]; + this->add_raw_touch_position_(contact[0], x, y, contact[4]); + } + this->status_clear_warning(); + return true; +} + +void SPD2010Touchscreen::update_touches() { + if (!this->ready_ || !this->read_data_()) + this->skip_update_ = true; +} + +void SPD2010Touchscreen::dump_config() { + ESP_LOGCONFIG(TAG, + "SPD2010 Touchscreen:\n" + " Raw X range: %d-%d\n" + " Raw Y range: %d-%d", + this->x_raw_min_, this->x_raw_max_, this->y_raw_min_, this->y_raw_max_); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); +} + +} // namespace esphome::spd2010 diff --git a/esphome/components/spd2010/spd2010_touchscreen.h b/esphome/components/spd2010/spd2010_touchscreen.h new file mode 100644 index 00000000000..88173968721 --- /dev/null +++ b/esphome/components/spd2010/spd2010_touchscreen.h @@ -0,0 +1,30 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/touchscreen/touchscreen.h" +#include "esphome/core/hal.h" + +namespace esphome::spd2010 { + +class SPD2010Touchscreen final : public touchscreen::Touchscreen, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } + void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; } + + protected: + void initialize_(); + void update_touches() override; + bool read_data_(); + bool read_register_(uint16_t reg, uint8_t *data, size_t length) const; + bool write_command_(uint16_t reg, uint16_t value) const; + bool clear_interrupt_() const; + bool finish_report_(); + + InternalGPIOPin *interrupt_pin_{nullptr}; + GPIOPin *reset_pin_{nullptr}; + bool ready_{false}; +}; + +} // namespace esphome::spd2010 diff --git a/esphome/components/spd2010/touchscreen.py b/esphome/components/spd2010/touchscreen.py new file mode 100644 index 00000000000..7ead6a34184 --- /dev/null +++ b/esphome/components/spd2010/touchscreen.py @@ -0,0 +1,33 @@ +from esphome import pins +import esphome.codegen as cg +from esphome.components import i2c, touchscreen +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN +from esphome.types import ConfigType + +spd2010_ns = cg.esphome_ns.namespace("spd2010") +SPD2010Touchscreen = spd2010_ns.class_( + "SPD2010Touchscreen", touchscreen.Touchscreen, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + touchscreen.touchscreen_schema("50ms") + .extend( + { + cv.GenerateID(): cv.declare_id(SPD2010Touchscreen), + cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + } + ) + .extend(i2c.i2c_device_schema(0x53)) +) + + +async def to_code(config: ConfigType) -> None: + var = cg.new_Pvariable(config[CONF_ID]) + await touchscreen.register_touchscreen(var, config) + await i2c.register_i2c_device(var, config) + if interrupt_pin := config.get(CONF_INTERRUPT_PIN): + cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin))) + if reset_pin := config.get(CONF_RESET_PIN): + cg.add(var.set_reset_pin(await cg.gpio_pin_expression(reset_pin))) diff --git a/esphome/components/speaker/__init__.py b/esphome/components/speaker/__init__.py index d0a743543e5..d34cfc32c0f 100644 --- a/esphome/components/speaker/__init__.py +++ b/esphome/components/speaker/__init__.py @@ -8,6 +8,7 @@ from esphome.coroutine import CoroPriority, coroutine_with_priority AUTO_LOAD = ["audio"] CODEOWNERS = ["@jesserockz", "@kahrendt"] +DOMAIN = "speaker" IS_PLATFORM_COMPONENT = True diff --git a/esphome/components/speaker/media_player/__init__.py b/esphome/components/speaker/media_player/__init__.py index 78892bff226..159af158086 100644 --- a/esphome/components/speaker/media_player/__init__.py +++ b/esphome/components/speaker/media_player/__init__.py @@ -251,7 +251,7 @@ automation.register_apply_action( # so a trigger arg with the same name cannot shadow it. ( CONF_MEDIA_FILE, - audio.AudioFile.operator("ptr"), + audio.AudioFile.operator("const").operator("ptr"), lambda _, value: f"::{value}", ), (CONF_ANNOUNCEMENT, cg.bool_), diff --git a/esphome/components/speaker/media_player/audio_pipeline.cpp b/esphome/components/speaker/media_player/audio_pipeline.cpp index 509984cfa29..a39a1ecbbef 100644 --- a/esphome/components/speaker/media_player/audio_pipeline.cpp +++ b/esphome/components/speaker/media_player/audio_pipeline.cpp @@ -21,7 +21,7 @@ static const uint32_t DECODE_TASK_STACK_SIZE = 3 * 1024; static const uint32_t INFO_ERROR_QUEUE_COUNT = 5; -static const char *const TAG = "speaker_media_player.pipeline"; +ESPHOME_LOG_TAG(TAG, "speaker_media_player.pipeline"); enum EventGroupBits : uint32_t { // MESSAGE_* bits are only set by their respective tasks @@ -66,7 +66,7 @@ void AudioPipeline::start_url(const std::string &uri) { this->pending_url_ = true; } -void AudioPipeline::start_file(audio::AudioFile *audio_file) { +void AudioPipeline::start_file(const audio::AudioFile *audio_file) { if (this->is_playing_) { xEventGroupSetBits(this->event_group_, PIPELINE_COMMAND_STOP); } diff --git a/esphome/components/speaker/media_player/audio_pipeline.h b/esphome/components/speaker/media_player/audio_pipeline.h index 02dad15de97..b114a7b7a51 100644 --- a/esphome/components/speaker/media_player/audio_pipeline.h +++ b/esphome/components/speaker/media_player/audio_pipeline.h @@ -74,7 +74,7 @@ class AudioPipeline final { /// @brief Starts an audio pipeline given a AudioFile pointer /// @param audio_file pointer to an AudioFile object /// @return ESP_OK if successful or an appropriate error if not - void start_file(audio::AudioFile *audio_file); + void start_file(const audio::AudioFile *audio_file); /// @brief Stops the pipeline. Sends a stop signal to each task (if running) and clears the ring buffers. /// @return ESP_OK if successful or ESP_ERR_TIMEOUT if the tasks did not indicate they stopped @@ -121,7 +121,7 @@ class AudioPipeline final { speaker::Speaker *speaker_{nullptr}; std::string current_uri_{}; - audio::AudioFile *current_audio_file_{nullptr}; + const audio::AudioFile *current_audio_file_{nullptr}; audio::AudioFileType current_audio_file_type_; audio::AudioStreamInfo current_audio_stream_info_; diff --git a/esphome/components/speaker/media_player/speaker_media_player.cpp b/esphome/components/speaker/media_player/speaker_media_player.cpp index cac2f6649c8..8ab4128e927 100644 --- a/esphome/components/speaker/media_player/speaker_media_player.cpp +++ b/esphome/components/speaker/media_player/speaker_media_player.cpp @@ -50,7 +50,12 @@ static const uint32_t MEDIA_CONTROLS_QUEUE_LENGTH = 20; static const UBaseType_t MEDIA_PIPELINE_TASK_PRIORITY = 1; static const UBaseType_t ANNOUNCEMENT_PIPELINE_TASK_PRIORITY = 1; -static const char *const TAG = "speaker_media_player"; +ESPHOME_LOG_TAG(TAG, "speaker_media_player"); + +static constexpr uint32_t UNPAUSE_MEDIA_INTERVAL_ID = 0; +static constexpr uint32_t NEXT_ANNOUNCEMENT_TIMEOUT_ID = 1; +static constexpr uint32_t NEXT_MEDIA_TIMEOUT_ID = 2; +static constexpr uint32_t UNPAUSE_ANNOUNCEMENT_INTERVAL_ID = 3; void SpeakerMediaPlayer::setup() { #ifdef USE_SPEAKER_MEDIA_PLAYER_ON_OFF @@ -112,13 +117,13 @@ void SpeakerMediaPlayer::set_playlist_delay_ms(AudioPipelineType pipeline_type, void SpeakerMediaPlayer::stop_and_unpause_media_() { this->media_pipeline_->stop(); this->unpause_media_remaining_ = 3; - this->set_interval("unpause_med", 50, [this]() { + this->set_interval(UNPAUSE_MEDIA_INTERVAL_ID, 50, [this]() { if (this->media_pipeline_state_ == AudioPipelineState::STOPPED) { - this->cancel_interval("unpause_med"); + this->cancel_interval(UNPAUSE_MEDIA_INTERVAL_ID); this->media_pipeline_->set_pause_state(false); this->is_paused_ = false; } else if (--this->unpause_media_remaining_ == 0) { - this->cancel_interval("unpause_med"); + this->cancel_interval(UNPAUSE_MEDIA_INTERVAL_ID); } }); } @@ -152,7 +157,7 @@ void SpeakerMediaPlayer::watch_media_commands_() { if (this->single_pipeline_() || (media_command.announce.has_value() && media_command.announce.value())) { if (!enqueue) { // Ensure the loaded next item doesn't start playing, clear the queue, start the file, and unpause - this->cancel_timeout("next_ann"); + this->cancel_timeout(NEXT_ANNOUNCEMENT_TIMEOUT_ID); this->announcement_playlist_.clear(); this->announcement_item_failed_ = false; if (media_command.file.has_value()) { @@ -166,7 +171,7 @@ void SpeakerMediaPlayer::watch_media_commands_() { } else { if (!enqueue) { // Ensure the loaded next item doesn't start playing, clear the queue, start the file, and unpause - this->cancel_timeout("next_media"); + this->cancel_timeout(NEXT_MEDIA_TIMEOUT_ID); this->media_playlist_.clear(); this->media_item_failed_ = false; if (this->is_paused_) { @@ -236,23 +241,23 @@ void SpeakerMediaPlayer::watch_media_commands_() { if (this->single_pipeline_() || (media_command.announce.has_value() && media_command.announce.value())) { #endif if (this->announcement_pipeline_ != nullptr) { - this->cancel_timeout("next_ann"); + this->cancel_timeout(NEXT_ANNOUNCEMENT_TIMEOUT_ID); this->announcement_playlist_.clear(); this->announcement_item_failed_ = false; this->announcement_pipeline_->stop(); this->unpause_announcement_remaining_ = 3; - this->set_interval("unpause_ann", 50, [this]() { + this->set_interval(UNPAUSE_ANNOUNCEMENT_INTERVAL_ID, 50, [this]() { if (this->announcement_pipeline_state_ == AudioPipelineState::STOPPED) { - this->cancel_interval("unpause_ann"); + this->cancel_interval(UNPAUSE_ANNOUNCEMENT_INTERVAL_ID); this->announcement_pipeline_->set_pause_state(false); } else if (--this->unpause_announcement_remaining_ == 0) { - this->cancel_interval("unpause_ann"); + this->cancel_interval(UNPAUSE_ANNOUNCEMENT_INTERVAL_ID); } }); } } else { if (this->media_pipeline_ != nullptr) { - this->cancel_timeout("next_media"); + this->cancel_timeout(NEXT_MEDIA_TIMEOUT_ID); this->media_playlist_.clear(); this->media_item_failed_ = false; this->stop_and_unpause_media_(); @@ -408,7 +413,8 @@ void SpeakerMediaPlayer::loop() { this->announcement_pipeline_->set_pause_state(true); // Internally unpause the pipeline after the delay between playlist items. Announcements do not follow the // media player's pause state. - this->set_timeout("next_ann", timeout_ms, [this]() { this->announcement_pipeline_->set_pause_state(false); }); + this->set_timeout(NEXT_ANNOUNCEMENT_TIMEOUT_ID, timeout_ms, + [this]() { this->announcement_pipeline_->set_pause_state(false); }); } } } else { @@ -454,7 +460,7 @@ void SpeakerMediaPlayer::loop() { this->media_pipeline_->set_pause_state(true); // Internally unpause the pipeline after the delay between playlist items, if the media player state is // not paused. - this->set_timeout("next_media", timeout_ms, + this->set_timeout(NEXT_MEDIA_TIMEOUT_ID, timeout_ms, [this]() { this->media_pipeline_->set_pause_state(this->is_paused_); }); } } @@ -493,7 +499,7 @@ void SpeakerMediaPlayer::loop() { #endif } -void SpeakerMediaPlayer::play_file(audio::AudioFile *media_file, bool announcement, bool enqueue) { +void SpeakerMediaPlayer::play_file(const audio::AudioFile *media_file, bool announcement, bool enqueue) { if (!this->is_ready()) { // Ignore any commands sent before the media player is setup return; diff --git a/esphome/components/speaker/media_player/speaker_media_player.h b/esphome/components/speaker/media_player/speaker_media_player.h index 889aedbc9ac..e98196785c8 100644 --- a/esphome/components/speaker/media_player/speaker_media_player.h +++ b/esphome/components/speaker/media_player/speaker_media_player.h @@ -28,13 +28,13 @@ struct MediaCallCommand { optional volume; optional announce; optional url; // Must be manually deleted after receiving this struct from a queue - optional file; + optional file; optional enqueue; }; struct PlaylistItem { optional url; - optional file; + optional file; }; struct VolumeRestoreState { @@ -87,7 +87,7 @@ class SpeakerMediaPlayer final : public Component, Trigger<> *get_unmute_trigger() { return &this->unmute_trigger_; } Trigger *get_volume_trigger() { return &this->volume_trigger_; } - void play_file(audio::AudioFile *media_file, bool announcement, bool enqueue); + void play_file(const audio::AudioFile *media_file, bool announcement, bool enqueue); void set_playlist_delay_ms(AudioPipelineType pipeline_type, uint32_t delay_ms); diff --git a/esphome/components/speaker_source/__init__.py b/esphome/components/speaker_source/__init__.py index e69de29bb2d..ebe19a4ead5 100644 --- a/esphome/components/speaker_source/__init__.py +++ b/esphome/components/speaker_source/__init__.py @@ -0,0 +1 @@ +DOMAIN = "speaker_source" diff --git a/esphome/components/speaker_source/speaker_source_media_player.cpp b/esphome/components/speaker_source/speaker_source_media_player.cpp index 6a000b8f215..657c26af80a 100644 --- a/esphome/components/speaker_source/speaker_source_media_player.cpp +++ b/esphome/components/speaker_source/speaker_source_media_player.cpp @@ -11,7 +11,7 @@ namespace esphome::speaker_source { static constexpr uint32_t MEDIA_CONTROLS_QUEUE_LENGTH = 20; -static const char *const TAG = "speaker_source_media_player"; +ESPHOME_LOG_TAG(TAG, "speaker_source_media_player"); // SourceBinding method implementations (defined here because SpeakerSourceMediaPlayer is forward-declared in the // header) diff --git a/esphome/components/speed/__init__.py b/esphome/components/speed/__init__.py index b75374863be..9f048ef37d2 100644 --- a/esphome/components/speed/__init__.py +++ b/esphome/components/speed/__init__.py @@ -1,3 +1,5 @@ import esphome.codegen as cg +DOMAIN = "speed" + speed_ns = cg.esphome_ns.namespace("speed") diff --git a/esphome/components/speed/fan/speed_fan.cpp b/esphome/components/speed/fan/speed_fan.cpp index af46ef52e1c..322c638d4ba 100644 --- a/esphome/components/speed/fan/speed_fan.cpp +++ b/esphome/components/speed/fan/speed_fan.cpp @@ -3,7 +3,7 @@ namespace esphome::speed { -static const char *const TAG = "speed.fan"; +ESPHOME_LOG_TAG(TAG, "speed.fan"); void SpeedFan::setup() { // Construct traits before restore so preset modes can be looked up by index diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index dd2dce01c5d..d6949cc5909 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -18,6 +18,7 @@ from esphome.components.esp32 import ( VARIANT_ESP32S31, only_on_variant, ) +from esphome.components.psram import DOMAIN as PSRAM_DOMAIN from esphome.config_helpers import filter_source_files_from_platform import esphome.config_validation as cv from esphome.const import ( @@ -44,6 +45,7 @@ import esphome.final_validate as fv from esphome.types import ConfigType CODEOWNERS = ["@esphome/core", "@clydebarrow"] +DOMAIN = "spi" spi_ns = cg.esphome_ns.namespace("spi") SPIComponent = spi_ns.class_("SPIComponent", cg.Component) QuadSPIComponent = spi_ns.class_("QuadSPIComponent", cg.Component) @@ -157,7 +159,7 @@ def _validate_psram_dma(value: Any) -> bool: msg_prefix="PSRAM DMA", ), cv.require_framework_version(esp_idf=cv.Version(5, 5, 3)), - cv.requires_component("psram"), + cv.requires_component(PSRAM_DOMAIN), )(value) diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index dfdc9fa6245..343fcd5488a 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -4,7 +4,7 @@ namespace esphome::spi { -const char *const TAG = "spi"; +ESPHOME_LOG_TAG(TAG, "spi"); SPIDelegate *const SPIDelegate::NULL_DELEGATE = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) new SPIDelegateDummy(); diff --git a/esphome/components/spi/spi_arduino.cpp b/esphome/components/spi/spi_arduino.cpp index ae2d2906edf..8ad868ad2e3 100644 --- a/esphome/components/spi/spi_arduino.cpp +++ b/esphome/components/spi/spi_arduino.cpp @@ -4,7 +4,7 @@ namespace esphome::spi { #if defined(USE_ARDUINO) && !defined(USE_ESP32) -static const char *const TAG = "spi"; +ESPHOME_LOG_TAG(TAG, "spi"); class SPIDelegateHw : public SPIDelegate { public: SPIDelegateHw(SPIInterface channel, uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) diff --git a/esphome/components/spi/spi_esp_idf.cpp b/esphome/components/spi/spi_esp_idf.cpp index 95b5e4f14bf..7b51bfaa698 100644 --- a/esphome/components/spi/spi_esp_idf.cpp +++ b/esphome/components/spi/spi_esp_idf.cpp @@ -8,7 +8,7 @@ namespace esphome::spi { #ifdef USE_ESP32 -static const char *const TAG = "spi"; +ESPHOME_LOG_TAG(TAG, "spi"); static const size_t MAX_TRANSFER_SIZE = 4092; // dictated by ESP-IDF API. #ifdef USE_SPI_PSRAM_DMA diff --git a/esphome/components/spi_device/__init__.py b/esphome/components/spi_device/__init__.py index bd2bfe64524..3f64dfc3d16 100644 --- a/esphome/components/spi_device/__init__.py +++ b/esphome/components/spi_device/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType DEPENDENCIES = ["spi"] CODEOWNERS = ["@clydebarrow"] +DOMAIN = "spi_device" MULTI_CONF = True spi_device_ns = cg.esphome_ns.namespace("spi_device") diff --git a/esphome/components/spi_device/spi_device.cpp b/esphome/components/spi_device/spi_device.cpp index bdf5978bc7d..f89b9f624f2 100644 --- a/esphome/components/spi_device/spi_device.cpp +++ b/esphome/components/spi_device/spi_device.cpp @@ -5,7 +5,7 @@ namespace esphome::spi_device { -static const char *const TAG = "spi_device"; +ESPHOME_LOG_TAG(TAG, "spi_device"); void SPIDeviceComponent::setup() { this->spi_setup(); } diff --git a/esphome/components/spi_led_strip/__init__.py b/esphome/components/spi_led_strip/__init__.py index 850a1f6e027..2d856e1b106 100644 --- a/esphome/components/spi_led_strip/__init__.py +++ b/esphome/components/spi_led_strip/__init__.py @@ -1,2 +1,3 @@ CODEOWNERS = ["@clydebarrow"] DEPENDENCIES = ["spi"] +DOMAIN = "spi_led_strip" diff --git a/esphome/components/split_buffer/__init__.py b/esphome/components/split_buffer/__init__.py index be7472936f3..e66c0c6efae 100644 --- a/esphome/components/split_buffer/__init__.py +++ b/esphome/components/split_buffer/__init__.py @@ -1,4 +1,5 @@ CODEOWNERS = ["@jesserockz"] +DOMAIN = "split_buffer" # Allows split_buffer to be configured in yaml, to allow use of the C++ api. diff --git a/esphome/components/split_buffer/split_buffer.cpp b/esphome/components/split_buffer/split_buffer.cpp index 63a0f0d9195..56b1d326a38 100644 --- a/esphome/components/split_buffer/split_buffer.cpp +++ b/esphome/components/split_buffer/split_buffer.cpp @@ -7,7 +7,7 @@ #include "esphome/core/log.h" namespace esphome::split_buffer { -static constexpr const char *const TAG = "split_buffer"; +ESPHOME_LOG_TAG(TAG, "split_buffer"); SplitBuffer::~SplitBuffer() { this->free(); } diff --git a/esphome/components/sprinkler/__init__.py b/esphome/components/sprinkler/__init__.py index 00144adacd7..fe096514d07 100644 --- a/esphome/components/sprinkler/__init__.py +++ b/esphome/components/sprinkler/__init__.py @@ -23,6 +23,7 @@ from esphome.helpers import docs_url AUTO_LOAD = ["number", "switch"] CODEOWNERS = ["@kbx81"] +DOMAIN = "sprinkler" CONF_AUTO_ADVANCE_SWITCH = "auto_advance_switch" CONF_DIVIDER = "divider" diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index cdec1581266..eb9e61958f5 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -10,7 +10,7 @@ namespace esphome::sprinkler { -static const char *const TAG = "sprinkler"; +ESPHOME_LOG_TAG(TAG, "sprinkler"); void SprinklerControllerNumber::setup() { float value; @@ -322,13 +322,11 @@ optional SprinklerValveRunRequest::valve_as_opt() { SprinklerValveOperator *SprinklerValveRunRequest::valve_operator() { return this->valve_op_; } Sprinkler::Sprinkler() : Sprinkler("") {} -Sprinkler::Sprinkler(const char *name) : name_(name) { - // The `name` is stored for dump_config logging - this->timer_.init(2); - // Timer names only need to be unique within this component instance - this->timer_.push_back({"sm", false, 0, 0, [this]() { this->sm_timer_callback_(); }}); - this->timer_.push_back({"vs", false, 0, 0, [this]() { this->valve_selection_callback_(); }}); -} +// The `name` is stored for dump_config logging; the scheduler keys each timer by its SprinklerTimerIndex +Sprinkler::Sprinkler(const char *name) + : name_(name), + timer_{{{false, 0, 0, [this]() { this->sm_timer_callback_(); }}, + {false, 0, 0, [this]() { this->valve_selection_callback_(); }}}} {} void Sprinkler::setup() { this->all_valves_off_(true); @@ -1510,7 +1508,7 @@ const LogString *Sprinkler::state_as_str_(SprinklerState state) { void Sprinkler::start_timer_(const SprinklerTimerIndex timer_index) { if (this->timer_duration_(timer_index) > 0) { - this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index), + this->set_timeout(static_cast(timer_index), this->timer_duration_(timer_index), this->timer_cbf_(timer_index)); this->timer_[timer_index].start_time = millis(); this->timer_[timer_index].active = true; @@ -1521,7 +1519,7 @@ void Sprinkler::start_timer_(const SprinklerTimerIndex timer_index) { bool Sprinkler::cancel_timer_(const SprinklerTimerIndex timer_index) { this->timer_[timer_index].active = false; - return this->cancel_timeout(this->timer_[timer_index].name); + return this->cancel_timeout(static_cast(timer_index)); } bool Sprinkler::timer_active_(const SprinklerTimerIndex timer_index) { return this->timer_[timer_index].active; } diff --git a/esphome/components/sprinkler/sprinkler.h b/esphome/components/sprinkler/sprinkler.h index 2499a0a5915..c51824d8833 100644 --- a/esphome/components/sprinkler/sprinkler.h +++ b/esphome/components/sprinkler/sprinkler.h @@ -7,6 +7,7 @@ #include "esphome/components/number/number.h" #include "esphome/components/switch/switch.h" +#include #include namespace esphome::sprinkler { @@ -49,7 +50,6 @@ struct SprinklerQueueItem { }; struct SprinklerTimer { - const char *name; bool active; uint32_t time; uint32_t start_time; @@ -564,8 +564,8 @@ class Sprinkler final : public Component { /// Sprinkler valve operator objects std::vector valve_op_{2}; - /// Valve control timers - FixedVector enforces that this can never grow beyond init() size - FixedVector timer_; + /// Valve control timers, indexed by SprinklerTimerIndex (also used as the scheduler id) + std::array timer_; /// Other Sprinkler instances we should be aware of (used to check if pumps are in use) std::vector other_controllers_; diff --git a/esphome/components/sps30/__init__.py b/esphome/components/sps30/__init__.py index e69de29bb2d..4b06a323ab8 100644 --- a/esphome/components/sps30/__init__.py +++ b/esphome/components/sps30/__init__.py @@ -0,0 +1 @@ +DOMAIN = "sps30" diff --git a/esphome/components/sps30/sps30.cpp b/esphome/components/sps30/sps30.cpp index 73fa4ef4633..3a2ebb14c39 100644 --- a/esphome/components/sps30/sps30.cpp +++ b/esphome/components/sps30/sps30.cpp @@ -6,7 +6,7 @@ namespace esphome::sps30 { -static const char *const TAG = "sps30"; +ESPHOME_LOG_TAG(TAG, "sps30"); static const uint16_t SPS30_CMD_GET_ARTICLE_CODE = 0xD025; static const uint16_t SPS30_CMD_GET_SERIAL_NUMBER = 0xD033; diff --git a/esphome/components/ssd1306_base/__init__.py b/esphome/components/ssd1306_base/__init__.py index 26eda3b4eaa..9577f9d4f7d 100644 --- a/esphome/components/ssd1306_base/__init__.py +++ b/esphome/components/ssd1306_base/__init__.py @@ -18,6 +18,8 @@ from esphome.const import ( from esphome.cpp_generator import MockObj from esphome.types import ConfigType +DOMAIN = "ssd1306_base" + ssd1306_base_ns = cg.esphome_ns.namespace("ssd1306_base") SSD1306 = ssd1306_base_ns.class_("SSD1306", cg.PollingComponent, display.DisplayBuffer) SSD1306Model = ssd1306_base_ns.enum("SSD1306Model") diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index 5f1c6fca8f4..ef7fa46e73a 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -5,7 +5,7 @@ namespace esphome::ssd1306_base { -static const char *const TAG = "ssd1306"; +ESPHOME_LOG_TAG(TAG, "ssd1306"); static const uint8_t SSD1306_MAX_CONTRAST = 255; static const uint8_t SSD1305_MAX_BRIGHTNESS = 255; diff --git a/esphome/components/ssd1306_i2c/__init__.py b/esphome/components/ssd1306_i2c/__init__.py index e69de29bb2d..9389c74a447 100644 --- a/esphome/components/ssd1306_i2c/__init__.py +++ b/esphome/components/ssd1306_i2c/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ssd1306_i2c" diff --git a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp index 00c864a217d..1cdfed9f502 100644 --- a/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp +++ b/esphome/components/ssd1306_i2c/ssd1306_i2c.cpp @@ -3,7 +3,7 @@ namespace esphome::ssd1306_i2c { -static const char *const TAG = "ssd1306_i2c"; +ESPHOME_LOG_TAG(TAG, "ssd1306_i2c"); void I2CSSD1306::setup() { this->init_reset_(); diff --git a/esphome/components/ssd1306_spi/__init__.py b/esphome/components/ssd1306_spi/__init__.py index e69de29bb2d..fc62e74a436 100644 --- a/esphome/components/ssd1306_spi/__init__.py +++ b/esphome/components/ssd1306_spi/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ssd1306_spi" diff --git a/esphome/components/ssd1306_spi/ssd1306_spi.cpp b/esphome/components/ssd1306_spi/ssd1306_spi.cpp index 0534deeb03b..8e2dcebc0ba 100644 --- a/esphome/components/ssd1306_spi/ssd1306_spi.cpp +++ b/esphome/components/ssd1306_spi/ssd1306_spi.cpp @@ -4,7 +4,7 @@ namespace esphome::ssd1306_spi { -static const char *const TAG = "ssd1306_spi"; +ESPHOME_LOG_TAG(TAG, "ssd1306_spi"); void SPISSD1306::setup() { this->spi_setup(); diff --git a/esphome/components/ssd1322_base/__init__.py b/esphome/components/ssd1322_base/__init__.py index daa03458572..d66ae8d5c97 100644 --- a/esphome/components/ssd1322_base/__init__.py +++ b/esphome/components/ssd1322_base/__init__.py @@ -13,6 +13,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@kbx81"] +DOMAIN = "ssd1322_base" ssd1322_base_ns = cg.esphome_ns.namespace("ssd1322_base") SSD1322 = ssd1322_base_ns.class_("SSD1322", cg.PollingComponent, display.DisplayBuffer) diff --git a/esphome/components/ssd1322_base/ssd1322_base.cpp b/esphome/components/ssd1322_base/ssd1322_base.cpp index a8a39b9ab01..30ca4109efb 100644 --- a/esphome/components/ssd1322_base/ssd1322_base.cpp +++ b/esphome/components/ssd1322_base/ssd1322_base.cpp @@ -4,7 +4,7 @@ namespace esphome::ssd1322_base { -static const char *const TAG = "ssd1322"; +ESPHOME_LOG_TAG(TAG, "ssd1322"); static const uint8_t SSD1322_MAX_CONTRAST = 255; static const uint8_t SSD1322_COLORMASK = 0x0f; diff --git a/esphome/components/ssd1322_spi/__init__.py b/esphome/components/ssd1322_spi/__init__.py index e69de29bb2d..52b9de59b26 100644 --- a/esphome/components/ssd1322_spi/__init__.py +++ b/esphome/components/ssd1322_spi/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ssd1322_spi" diff --git a/esphome/components/ssd1322_spi/ssd1322_spi.cpp b/esphome/components/ssd1322_spi/ssd1322_spi.cpp index 79f15508c34..38426b9eafc 100644 --- a/esphome/components/ssd1322_spi/ssd1322_spi.cpp +++ b/esphome/components/ssd1322_spi/ssd1322_spi.cpp @@ -4,7 +4,7 @@ namespace esphome::ssd1322_spi { -static const char *const TAG = "ssd1322_spi"; +ESPHOME_LOG_TAG(TAG, "ssd1322_spi"); void SPISSD1322::setup() { this->spi_setup(); diff --git a/esphome/components/ssd1325_base/__init__.py b/esphome/components/ssd1325_base/__init__.py index e48a8218254..fbbf329d53b 100644 --- a/esphome/components/ssd1325_base/__init__.py +++ b/esphome/components/ssd1325_base/__init__.py @@ -13,6 +13,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@kbx81"] +DOMAIN = "ssd1325_base" ssd1325_base_ns = cg.esphome_ns.namespace("ssd1325_base") SSD1325 = ssd1325_base_ns.class_("SSD1325", cg.PollingComponent, display.DisplayBuffer) diff --git a/esphome/components/ssd1325_base/ssd1325_base.cpp b/esphome/components/ssd1325_base/ssd1325_base.cpp index b8d8f999995..17175447d92 100644 --- a/esphome/components/ssd1325_base/ssd1325_base.cpp +++ b/esphome/components/ssd1325_base/ssd1325_base.cpp @@ -4,7 +4,7 @@ namespace esphome::ssd1325_base { -static const char *const TAG = "ssd1325"; +ESPHOME_LOG_TAG(TAG, "ssd1325"); static const uint8_t SSD1325_MAX_CONTRAST = 127; static const uint8_t SSD1325_COLORMASK = 0x0f; diff --git a/esphome/components/ssd1325_spi/__init__.py b/esphome/components/ssd1325_spi/__init__.py index e69de29bb2d..218baca080a 100644 --- a/esphome/components/ssd1325_spi/__init__.py +++ b/esphome/components/ssd1325_spi/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ssd1325_spi" diff --git a/esphome/components/ssd1325_spi/ssd1325_spi.cpp b/esphome/components/ssd1325_spi/ssd1325_spi.cpp index af6a01c7f39..cb21eb09fe4 100644 --- a/esphome/components/ssd1325_spi/ssd1325_spi.cpp +++ b/esphome/components/ssd1325_spi/ssd1325_spi.cpp @@ -4,7 +4,7 @@ namespace esphome::ssd1325_spi { -static const char *const TAG = "ssd1325_spi"; +ESPHOME_LOG_TAG(TAG, "ssd1325_spi"); void SPISSD1325::setup() { this->spi_setup(); diff --git a/esphome/components/ssd1327_base/__init__.py b/esphome/components/ssd1327_base/__init__.py index 462f197696d..2c9c0faf3b2 100644 --- a/esphome/components/ssd1327_base/__init__.py +++ b/esphome/components/ssd1327_base/__init__.py @@ -7,6 +7,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@kbx81"] +DOMAIN = "ssd1327_base" ssd1327_base_ns = cg.esphome_ns.namespace("ssd1327_base") SSD1327 = ssd1327_base_ns.class_("SSD1327", cg.PollingComponent, display.DisplayBuffer) diff --git a/esphome/components/ssd1327_base/ssd1327_base.cpp b/esphome/components/ssd1327_base/ssd1327_base.cpp index ae02b836bf4..7c8b490f610 100644 --- a/esphome/components/ssd1327_base/ssd1327_base.cpp +++ b/esphome/components/ssd1327_base/ssd1327_base.cpp @@ -4,7 +4,7 @@ namespace esphome::ssd1327_base { -static const char *const TAG = "ssd1327"; +ESPHOME_LOG_TAG(TAG, "ssd1327"); static const uint8_t SSD1327_MAX_CONTRAST = 127; static const uint8_t SSD1327_COLORMASK = 0x0f; diff --git a/esphome/components/ssd1327_i2c/__init__.py b/esphome/components/ssd1327_i2c/__init__.py index e69de29bb2d..b093087fa14 100644 --- a/esphome/components/ssd1327_i2c/__init__.py +++ b/esphome/components/ssd1327_i2c/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ssd1327_i2c" diff --git a/esphome/components/ssd1327_i2c/ssd1327_i2c.cpp b/esphome/components/ssd1327_i2c/ssd1327_i2c.cpp index 90fbcbd05cf..98328c800c1 100644 --- a/esphome/components/ssd1327_i2c/ssd1327_i2c.cpp +++ b/esphome/components/ssd1327_i2c/ssd1327_i2c.cpp @@ -3,7 +3,7 @@ namespace esphome::ssd1327_i2c { -static const char *const TAG = "ssd1327_i2c"; +ESPHOME_LOG_TAG(TAG, "ssd1327_i2c"); void I2CSSD1327::setup() { this->init_reset_(); diff --git a/esphome/components/ssd1327_spi/__init__.py b/esphome/components/ssd1327_spi/__init__.py index e69de29bb2d..f95fe105798 100644 --- a/esphome/components/ssd1327_spi/__init__.py +++ b/esphome/components/ssd1327_spi/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ssd1327_spi" diff --git a/esphome/components/ssd1327_spi/ssd1327_spi.cpp b/esphome/components/ssd1327_spi/ssd1327_spi.cpp index c6a64227fc4..8816aadf6d0 100644 --- a/esphome/components/ssd1327_spi/ssd1327_spi.cpp +++ b/esphome/components/ssd1327_spi/ssd1327_spi.cpp @@ -4,7 +4,7 @@ namespace esphome::ssd1327_spi { -static const char *const TAG = "ssd1327_spi"; +ESPHOME_LOG_TAG(TAG, "ssd1327_spi"); void SPISSD1327::setup() { this->spi_setup(); diff --git a/esphome/components/ssd1331_base/__init__.py b/esphome/components/ssd1331_base/__init__.py index d1c0b585f83..2345acdf650 100644 --- a/esphome/components/ssd1331_base/__init__.py +++ b/esphome/components/ssd1331_base/__init__.py @@ -7,6 +7,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@kbx81"] +DOMAIN = "ssd1331_base" ssd1331_base_ns = cg.esphome_ns.namespace("ssd1331_base") SSD1331 = ssd1331_base_ns.class_("SSD1331", cg.PollingComponent, display.DisplayBuffer) diff --git a/esphome/components/ssd1331_base/ssd1331_base.cpp b/esphome/components/ssd1331_base/ssd1331_base.cpp index 7755e5a2a03..8d5ebb4dbbe 100644 --- a/esphome/components/ssd1331_base/ssd1331_base.cpp +++ b/esphome/components/ssd1331_base/ssd1331_base.cpp @@ -4,7 +4,7 @@ namespace esphome::ssd1331_base { -static const char *const TAG = "ssd1331"; +ESPHOME_LOG_TAG(TAG, "ssd1331"); static const uint16_t SSD1331_COLORMASK = 0xffff; static const uint8_t SSD1331_MAX_CONTRASTA = 0x91; diff --git a/esphome/components/ssd1331_spi/__init__.py b/esphome/components/ssd1331_spi/__init__.py index e69de29bb2d..4e37ffcabac 100644 --- a/esphome/components/ssd1331_spi/__init__.py +++ b/esphome/components/ssd1331_spi/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ssd1331_spi" diff --git a/esphome/components/ssd1331_spi/ssd1331_spi.cpp b/esphome/components/ssd1331_spi/ssd1331_spi.cpp index a5118cdfbef..d36f9b75af2 100644 --- a/esphome/components/ssd1331_spi/ssd1331_spi.cpp +++ b/esphome/components/ssd1331_spi/ssd1331_spi.cpp @@ -4,7 +4,7 @@ namespace esphome::ssd1331_spi { -static const char *const TAG = "ssd1331_spi"; +ESPHOME_LOG_TAG(TAG, "ssd1331_spi"); void SPISSD1331::setup() { this->spi_setup(); diff --git a/esphome/components/ssd1351_base/__init__.py b/esphome/components/ssd1351_base/__init__.py index a516b4d9516..f693004d633 100644 --- a/esphome/components/ssd1351_base/__init__.py +++ b/esphome/components/ssd1351_base/__init__.py @@ -7,6 +7,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@kbx81"] +DOMAIN = "ssd1351_base" ssd1351_base_ns = cg.esphome_ns.namespace("ssd1351_base") SSD1351 = ssd1351_base_ns.class_("SSD1351", cg.PollingComponent, display.DisplayBuffer) diff --git a/esphome/components/ssd1351_base/ssd1351_base.cpp b/esphome/components/ssd1351_base/ssd1351_base.cpp index 3df575499d9..7ab3a64ea84 100644 --- a/esphome/components/ssd1351_base/ssd1351_base.cpp +++ b/esphome/components/ssd1351_base/ssd1351_base.cpp @@ -4,7 +4,7 @@ namespace esphome::ssd1351_base { -static const char *const TAG = "ssd1351"; +ESPHOME_LOG_TAG(TAG, "ssd1351"); static const uint16_t SSD1351_COLORMASK = 0xffff; static const uint8_t SSD1351_MAX_CONTRAST = 15; diff --git a/esphome/components/ssd1351_spi/__init__.py b/esphome/components/ssd1351_spi/__init__.py index e69de29bb2d..0cb07c8b5ee 100644 --- a/esphome/components/ssd1351_spi/__init__.py +++ b/esphome/components/ssd1351_spi/__init__.py @@ -0,0 +1 @@ +DOMAIN = "ssd1351_spi" diff --git a/esphome/components/ssd1351_spi/ssd1351_spi.cpp b/esphome/components/ssd1351_spi/ssd1351_spi.cpp index f6d2323e9cc..06d95e74a4d 100644 --- a/esphome/components/ssd1351_spi/ssd1351_spi.cpp +++ b/esphome/components/ssd1351_spi/ssd1351_spi.cpp @@ -4,7 +4,7 @@ namespace esphome::ssd1351_spi { -static const char *const TAG = "ssd1351_spi"; +ESPHOME_LOG_TAG(TAG, "ssd1351_spi"); void SPISSD1351::setup() { this->spi_setup(); diff --git a/esphome/components/st7123/__init__.py b/esphome/components/st7123/__init__.py index 335bc238be8..6e3a763eb44 100644 --- a/esphome/components/st7123/__init__.py +++ b/esphome/components/st7123/__init__.py @@ -2,5 +2,6 @@ import esphome.codegen as cg CODEOWNERS = ["@miniskipper"] DEPENDENCIES = ["i2c"] +DOMAIN = "st7123" st7123_ns = cg.esphome_ns.namespace("st7123") diff --git a/esphome/components/st7123/touchscreen/st7123_touchscreen.cpp b/esphome/components/st7123/touchscreen/st7123_touchscreen.cpp index 117f9752645..5cd986f6976 100644 --- a/esphome/components/st7123/touchscreen/st7123_touchscreen.cpp +++ b/esphome/components/st7123/touchscreen/st7123_touchscreen.cpp @@ -5,7 +5,7 @@ namespace esphome::st7123 { -static const char *const TAG = "st7123.touchscreen"; +ESPHOME_LOG_TAG(TAG, "st7123.touchscreen"); void ST7123Touchscreen::setup() { if (this->reset_pin_ != nullptr) { diff --git a/esphome/components/st7567_base/__init__.py b/esphome/components/st7567_base/__init__.py index 51225c50236..8fdc0a7f7af 100644 --- a/esphome/components/st7567_base/__init__.py +++ b/esphome/components/st7567_base/__init__.py @@ -14,6 +14,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@latonita"] +DOMAIN = "st7567_base" st7567_base_ns = cg.esphome_ns.namespace("st7567_base") ST7567 = st7567_base_ns.class_("ST7567", cg.PollingComponent, display.DisplayBuffer) diff --git a/esphome/components/st7567_base/st7567_base.cpp b/esphome/components/st7567_base/st7567_base.cpp index fff3aa7e039..aa83de6a8c2 100644 --- a/esphome/components/st7567_base/st7567_base.cpp +++ b/esphome/components/st7567_base/st7567_base.cpp @@ -4,7 +4,7 @@ namespace esphome::st7567_base { -static const char *const TAG = "st7567"; +ESPHOME_LOG_TAG(TAG, "st7567"); void ST7567::setup() { this->init_internal_(this->get_buffer_length_()); diff --git a/esphome/components/st7567_i2c/__init__.py b/esphome/components/st7567_i2c/__init__.py index dd06cfffea0..3c54c70741b 100644 --- a/esphome/components/st7567_i2c/__init__.py +++ b/esphome/components/st7567_i2c/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@latonita"] +DOMAIN = "st7567_i2c" diff --git a/esphome/components/st7567_i2c/st7567_i2c.cpp b/esphome/components/st7567_i2c/st7567_i2c.cpp index c99815ecd69..657a60f9549 100644 --- a/esphome/components/st7567_i2c/st7567_i2c.cpp +++ b/esphome/components/st7567_i2c/st7567_i2c.cpp @@ -3,7 +3,7 @@ namespace esphome::st7567_i2c { -static const char *const TAG = "st7567_i2c"; +ESPHOME_LOG_TAG(TAG, "st7567_i2c"); void I2CST7567::setup() { this->init_reset_(); diff --git a/esphome/components/st7567_spi/__init__.py b/esphome/components/st7567_spi/__init__.py index dd06cfffea0..ed601048578 100644 --- a/esphome/components/st7567_spi/__init__.py +++ b/esphome/components/st7567_spi/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@latonita"] +DOMAIN = "st7567_spi" diff --git a/esphome/components/st7567_spi/st7567_spi.cpp b/esphome/components/st7567_spi/st7567_spi.cpp index fae8c313814..fa9fb792dd3 100644 --- a/esphome/components/st7567_spi/st7567_spi.cpp +++ b/esphome/components/st7567_spi/st7567_spi.cpp @@ -3,7 +3,7 @@ namespace esphome::st7567_spi { -static const char *const TAG = "st7567_spi"; +ESPHOME_LOG_TAG(TAG, "st7567_spi"); void SPIST7567::setup() { this->spi_setup(); diff --git a/esphome/components/st7701s/__init__.py b/esphome/components/st7701s/__init__.py index c58ce8a01e8..812c6caa8f4 100644 --- a/esphome/components/st7701s/__init__.py +++ b/esphome/components/st7701s/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@clydebarrow"] +DOMAIN = "st7701s" diff --git a/esphome/components/st7735/__init__.py b/esphome/components/st7735/__init__.py index 62dfa6f4136..176eb233e17 100644 --- a/esphome/components/st7735/__init__.py +++ b/esphome/components/st7735/__init__.py @@ -1,5 +1,7 @@ import esphome.codegen as cg +DOMAIN = "st7735" + st7735_ns = cg.esphome_ns.namespace("st7735") DEPRECATED_COMPONENT = """ diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index 7eb70c5c235..94b94d17a98 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -218,7 +218,7 @@ static constexpr uint8_t PROGMEM 100 }; // 100 ms delay // clang-format on -static const char *const TAG = "st7735"; +ESPHOME_LOG_TAG(TAG, "st7735"); ST7735::ST7735(ST7735Model model, int width, int height, int colstart, int rowstart, bool eightbitcolor, bool usebgr, bool invert_colors) diff --git a/esphome/components/st7789v/__init__.py b/esphome/components/st7789v/__init__.py index 7915cf119c9..e67f4809128 100644 --- a/esphome/components/st7789v/__init__.py +++ b/esphome/components/st7789v/__init__.py @@ -1,5 +1,7 @@ import esphome.codegen as cg +DOMAIN = "st7789v" + st7789v_ns = cg.esphome_ns.namespace("st7789v") DEPRECATED_COMPONENT = """ diff --git a/esphome/components/st7789v/st7789v.cpp b/esphome/components/st7789v/st7789v.cpp index 2e07e24522c..343d196b507 100644 --- a/esphome/components/st7789v/st7789v.cpp +++ b/esphome/components/st7789v/st7789v.cpp @@ -4,7 +4,7 @@ namespace esphome::st7789v { -static const char *const TAG = "st7789v"; +ESPHOME_LOG_TAG(TAG, "st7789v"); #ifdef USE_ESP32 static constexpr size_t TEMP_BUFFER_SIZE = 1024; #else diff --git a/esphome/components/st7920/__init__.py b/esphome/components/st7920/__init__.py index e69de29bb2d..a1512e4e575 100644 --- a/esphome/components/st7920/__init__.py +++ b/esphome/components/st7920/__init__.py @@ -0,0 +1 @@ +DOMAIN = "st7920" diff --git a/esphome/components/st7920/st7920.cpp b/esphome/components/st7920/st7920.cpp index 429ed9e57be..19a96aad602 100644 --- a/esphome/components/st7920/st7920.cpp +++ b/esphome/components/st7920/st7920.cpp @@ -5,7 +5,7 @@ namespace esphome::st7920 { -static const char *const TAG = "st7920"; +ESPHOME_LOG_TAG(TAG, "st7920"); // ST7920 COMMANDS static const uint8_t LCD_DATA = 0xFA; diff --git a/esphome/components/statsd/__init__.py b/esphome/components/statsd/__init__.py index 58d4be97fae..16df864082d 100644 --- a/esphome/components/statsd/__init__.py +++ b/esphome/components/statsd/__init__.py @@ -14,6 +14,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["socket"] CODEOWNERS = ["@Links2004"] DEPENDENCIES = ["network"] +DOMAIN = "statsd" CONF_PREFIX = "prefix" diff --git a/esphome/components/statsd/statsd.cpp b/esphome/components/statsd/statsd.cpp index 2a56551255b..12af8345f13 100644 --- a/esphome/components/statsd/statsd.cpp +++ b/esphome/components/statsd/statsd.cpp @@ -10,7 +10,7 @@ namespace esphome::statsd { // this is needed since statsD does not support fragmented UDP packets static const uint16_t SEND_THRESHOLD = 1024; -static const char *const TAG = "statsD"; +ESPHOME_LOG_TAG(TAG, "statsD"); void StatsdComponent::setup() { #ifndef USE_ESP8266 diff --git a/esphome/components/status/__init__.py b/esphome/components/status/__init__.py index e69de29bb2d..dee924f6cf8 100644 --- a/esphome/components/status/__init__.py +++ b/esphome/components/status/__init__.py @@ -0,0 +1 @@ +DOMAIN = "status" diff --git a/esphome/components/status/status_binary_sensor.cpp b/esphome/components/status/status_binary_sensor.cpp index 2c95be85690..192c03219fc 100644 --- a/esphome/components/status/status_binary_sensor.cpp +++ b/esphome/components/status/status_binary_sensor.cpp @@ -12,7 +12,7 @@ namespace esphome::status { -static const char *const TAG = "status"; +ESPHOME_LOG_TAG(TAG, "status"); void StatusBinarySensor::update() { bool status = network::is_connected(); diff --git a/esphome/components/status_led/__init__.py b/esphome/components/status_led/__init__.py index 700296c0dfa..8bd98c0ef8d 100644 --- a/esphome/components/status_led/__init__.py +++ b/esphome/components/status_led/__init__.py @@ -5,6 +5,8 @@ from esphome.const import CONF_ID, CONF_PIN from esphome.core import CoroPriority, coroutine_with_priority from esphome.types import ConfigType +DOMAIN = "status_led" + status_led_ns = cg.esphome_ns.namespace("status_led") StatusLED = status_led_ns.class_("StatusLED", cg.Component) diff --git a/esphome/components/status_led/light/status_led_light.cpp b/esphome/components/status_led/light/status_led_light.cpp index 341d3bfce53..6eeb9732a47 100644 --- a/esphome/components/status_led/light/status_led_light.cpp +++ b/esphome/components/status_led/light/status_led_light.cpp @@ -5,7 +5,7 @@ namespace esphome::status_led { -static const char *const TAG = "status_led"; +ESPHOME_LOG_TAG(TAG, "status_led"); void StatusLEDLightOutput::loop() { uint8_t new_state = App.get_app_state() & STATUS_LED_MASK; diff --git a/esphome/components/status_led/status_led.cpp b/esphome/components/status_led/status_led.cpp index ae37b3fae78..81b3b9e3af7 100644 --- a/esphome/components/status_led/status_led.cpp +++ b/esphome/components/status_led/status_led.cpp @@ -4,7 +4,7 @@ namespace esphome::status_led { -static const char *const TAG = "status_led"; +ESPHOME_LOG_TAG(TAG, "status_led"); static constexpr uint32_t ERROR_PERIOD_MS = 250; static constexpr uint32_t ERROR_ON_MS = 150; diff --git a/esphome/components/stcc4/__init__.py b/esphome/components/stcc4/__init__.py index e69de29bb2d..d0f16805b80 100644 --- a/esphome/components/stcc4/__init__.py +++ b/esphome/components/stcc4/__init__.py @@ -0,0 +1 @@ +DOMAIN = "stcc4" diff --git a/esphome/components/stcc4/stcc4.cpp b/esphome/components/stcc4/stcc4.cpp index efc7a2b8ee7..ac7a998e5d6 100644 --- a/esphome/components/stcc4/stcc4.cpp +++ b/esphome/components/stcc4/stcc4.cpp @@ -6,7 +6,7 @@ namespace esphome::stcc4 { -static const char *const TAG = "stcc4"; +ESPHOME_LOG_TAG(TAG, "stcc4"); // I2C Commands static constexpr uint16_t STCC4_CMD_START_CONTINUOUS_MEASUREMENT = 0x218b; diff --git a/esphome/components/stepper/__init__.py b/esphome/components/stepper/__init__.py index 017fd95cd89..f5c1a304335 100644 --- a/esphome/components/stepper/__init__.py +++ b/esphome/components/stepper/__init__.py @@ -16,6 +16,7 @@ from esphome.const import ( from esphome.core import CORE, CoroPriority, coroutine_with_priority from esphome.types import SafeExpType +DOMAIN = "stepper" IS_PLATFORM_COMPONENT = True stepper_ns = cg.esphome_ns.namespace("stepper") diff --git a/esphome/components/stepper/stepper.cpp b/esphome/components/stepper/stepper.cpp index 54df83782ec..dd3040d8987 100644 --- a/esphome/components/stepper/stepper.cpp +++ b/esphome/components/stepper/stepper.cpp @@ -4,7 +4,7 @@ namespace esphome::stepper { -static const char *const TAG = "stepper"; +ESPHOME_LOG_TAG(TAG, "stepper"); void Stepper::calculate_speed_(uint32_t now) { // delta t since last calculation in seconds diff --git a/esphome/components/sts3x/__init__.py b/esphome/components/sts3x/__init__.py index e69de29bb2d..d7d6491925e 100644 --- a/esphome/components/sts3x/__init__.py +++ b/esphome/components/sts3x/__init__.py @@ -0,0 +1 @@ +DOMAIN = "sts3x" diff --git a/esphome/components/sts3x/sts3x.cpp b/esphome/components/sts3x/sts3x.cpp index ff2a7748bf0..8b97b2f4997 100644 --- a/esphome/components/sts3x/sts3x.cpp +++ b/esphome/components/sts3x/sts3x.cpp @@ -3,7 +3,7 @@ namespace esphome::sts3x { -static const char *const TAG = "sts3x"; +ESPHOME_LOG_TAG(TAG, "sts3x"); static const uint16_t STS3X_COMMAND_READ_SERIAL_NUMBER = 0x3780; static const uint16_t STS3X_COMMAND_READ_STATUS = 0xF32D; diff --git a/esphome/components/stts22h/__init__.py b/esphome/components/stts22h/__init__.py index a33c0b554b7..15a864ef0c7 100644 --- a/esphome/components/stts22h/__init__.py +++ b/esphome/components/stts22h/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@B48D81EFCC"] +DOMAIN = "stts22h" diff --git a/esphome/components/stts22h/stts22h.cpp b/esphome/components/stts22h/stts22h.cpp index 2b2559c8436..7d66dd0a183 100644 --- a/esphome/components/stts22h/stts22h.cpp +++ b/esphome/components/stts22h/stts22h.cpp @@ -3,7 +3,7 @@ namespace esphome::stts22h { -static const char *const TAG = "stts22h"; +ESPHOME_LOG_TAG(TAG, "stts22h"); static const uint8_t WHOAMI_REG = 0x01; static const uint8_t CTRL_REG = 0x04; diff --git a/esphome/components/substitutions/__init__.py b/esphome/components/substitutions/__init__.py index d59f39b9c22..93cf588e02a 100644 --- a/esphome/components/substitutions/__init__.py +++ b/esphome/components/substitutions/__init__.py @@ -25,6 +25,7 @@ from esphome.yaml_util import ( from .jinja import Jinja, JinjaError, Missing, Resolver, UndefinedError, has_jinja CODEOWNERS = ["@esphome/core"] +DOMAIN = "substitutions" _LOGGER = logging.getLogger(__name__) ContextVars = ChainMap[str, Any] @@ -517,7 +518,7 @@ def resolve_substitutions_block( """Resolve a deferred ``substitutions: !include file.yaml`` and validate the shape. The caller is responsible for wrapping the call in - ``cv.prepend_path(CONF_SUBSTITUTIONS)`` for error reporting. + ``cv.prepend_path(DOMAIN)`` for error reporting. ``command_line_substitutions`` seeds the filename context so ``substitutions: !include ${var}.yaml`` can reference CLI-provided vars. """ @@ -552,7 +553,7 @@ def do_substitution_pass( # Extract substitutions from config, overriding with substitutions coming from command line: # Use merge_dicts_ordered to preserve OrderedDict type for move_to_end() substitutions = config.pop(CONF_SUBSTITUTIONS, {}) - with cv.prepend_path(CONF_SUBSTITUTIONS): + with cv.prepend_path(DOMAIN): substitutions = resolve_substitutions_block( substitutions, command_line_substitutions ) diff --git a/esphome/components/sun/__init__.py b/esphome/components/sun/__init__.py index 73ddefe9729..71441e97823 100644 --- a/esphome/components/sun/__init__.py +++ b/esphome/components/sun/__init__.py @@ -16,6 +16,7 @@ from esphome.const import ( from esphome.types import ConfigType CODEOWNERS = ["@OttoWinter"] +DOMAIN = "sun" sun_ns = cg.esphome_ns.namespace("sun") Sun = sun_ns.class_("Sun") diff --git a/esphome/components/sun/sensor/sun_sensor.cpp b/esphome/components/sun/sensor/sun_sensor.cpp index d788e19ea41..b3cdacddaed 100644 --- a/esphome/components/sun/sensor/sun_sensor.cpp +++ b/esphome/components/sun/sensor/sun_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::sun { -static const char *const TAG = "sun.sensor"; +ESPHOME_LOG_TAG(TAG, "sun.sensor"); void SunSensor::dump_config() { LOG_SENSOR("", "Sun Sensor", this); } diff --git a/esphome/components/sun/sun.cpp b/esphome/components/sun/sun.cpp index d03ff07981e..f4c9d830b4d 100644 --- a/esphome/components/sun/sun.cpp +++ b/esphome/components/sun/sun.cpp @@ -16,7 +16,7 @@ namespace esphome::sun { using namespace esphome::sun::internal; -static const char *const TAG = "sun"; +ESPHOME_LOG_TAG(TAG, "sun"); #undef degrees #undef radians diff --git a/esphome/components/sun/text_sensor/sun_text_sensor.cpp b/esphome/components/sun/text_sensor/sun_text_sensor.cpp index 21aa4b86e0f..15a3fc21be5 100644 --- a/esphome/components/sun/text_sensor/sun_text_sensor.cpp +++ b/esphome/components/sun/text_sensor/sun_text_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::sun { -static const char *const TAG = "sun.text_sensor"; +ESPHOME_LOG_TAG(TAG, "sun.text_sensor"); void SunTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Sun Text Sensor", this); } diff --git a/esphome/components/sun_gtil2/__init__.py b/esphome/components/sun_gtil2/__init__.py index 0f5ae277532..61643cf4883 100644 --- a/esphome/components/sun_gtil2/__init__.py +++ b/esphome/components/sun_gtil2/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_ID from esphome.types import ConfigType CODEOWNERS = ["@Mat931"] +DOMAIN = "sun_gtil2" MULTI_CONF = True DEPENDENCIES = ["uart"] diff --git a/esphome/components/sun_gtil2/sun_gtil2.cpp b/esphome/components/sun_gtil2/sun_gtil2.cpp index 78e398d0868..f89797cc106 100644 --- a/esphome/components/sun_gtil2/sun_gtil2.cpp +++ b/esphome/components/sun_gtil2/sun_gtil2.cpp @@ -3,7 +3,7 @@ namespace esphome::sun_gtil2 { -static const char *const TAG = "sun_gtil2"; +ESPHOME_LOG_TAG(TAG, "sun_gtil2"); static const double NTC_A = 0.0011591051055979914; static const double NTC_B = 0.00022878183547845582; diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index c34a73a36d1..e965b552f48 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -32,6 +32,7 @@ from esphome.cpp_generator import MockObj, MockObjClass from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] +DOMAIN = "switch" IS_PLATFORM_COMPONENT = True DEVICE_CLASSES = [ DEVICE_CLASS_EMPTY, diff --git a/esphome/components/switch/binary_sensor/switch_binary_sensor.cpp b/esphome/components/switch/binary_sensor/switch_binary_sensor.cpp index 19995fb1ae3..c5b93e1dbb2 100644 --- a/esphome/components/switch/binary_sensor/switch_binary_sensor.cpp +++ b/esphome/components/switch/binary_sensor/switch_binary_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::switch_ { -static const char *const TAG = "switch.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "switch.binary_sensor"); void SwitchBinarySensor::setup() { source_->add_on_state_callback([this](bool value) { this->publish_state(value); }); diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index 2cc8ad782d9..179ef2ce35d 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -5,7 +5,7 @@ namespace esphome::switch_ { -static const char *const TAG = "switch"; +ESPHOME_LOG_TAG(TAG, "switch"); Switch::Switch() : state(false) {} diff --git a/esphome/components/sx126x/__init__.py b/esphome/components/sx126x/__init__.py index ce47a570501..c8b920d6cb7 100644 --- a/esphome/components/sx126x/__init__.py +++ b/esphome/components/sx126x/__init__.py @@ -10,6 +10,7 @@ from esphome.core import ID, TimePeriod from esphome.cpp_generator import MockObj from esphome.types import ConfigType, TemplateArgsType +DOMAIN = "sx126x" MULTI_CONF = True CODEOWNERS = ["@swoboda1337"] DEPENDENCIES = ["spi"] @@ -146,6 +147,11 @@ def validate_raw_data(value: Any) -> bytes | list[int]: ) +MAX_PACKET_SIZE = 255 +# The radio sends packets of 1 to MAX_PACKET_SIZE bytes. +validate_packet_data = cv.All(validate_raw_data, cv.Length(min=1, max=MAX_PACKET_SIZE)) + + def validate_config(config: ConfigType) -> ConfigType: lora_bws = [ "7_8kHz", @@ -205,7 +211,9 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True), cv.Optional(CONF_PA_POWER, default=17): cv.int_range(min=-3, max=22), cv.Optional(CONF_PA_RAMP, default="40us"): cv.enum(RAMP), - cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range(min=0, max=255), + cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range( + min=0, max=MAX_PACKET_SIZE + ), cv.Optional(CONF_PREAMBLE_DETECT, default=2): cv.int_range(min=0, max=4), cv.Optional(CONF_PREAMBLE_SIZE, default=8): cv.int_range(min=1, max=65535), cv.Required(CONF_RST_PIN): pins.gpio_output_pin_schema, @@ -314,7 +322,7 @@ automation.register_apply_action( SEND_PACKET_ACTION_SCHEMA = cv.maybe_simple_value( { cv.GenerateID(): cv.use_id(SX126x), - cv.Required(CONF_DATA): cv.templatable(validate_raw_data), + cv.Required(CONF_DATA): cv.templatable(validate_packet_data), }, key=CONF_DATA, ) @@ -334,15 +342,11 @@ async def send_packet_action_to_code( ) -> MockObj: var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - data = config[CONF_DATA] - if isinstance(data, bytes): - data = list(data) - if cg.is_template(data): - templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) - cg.add(var.set_data_template(templ)) - else: - # Generate static array in flash to avoid RAM copy - arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8) - arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data)) - cg.add(var.set_data_static(arr, len(data))) + await automation.templatable_bytes( + config[CONF_DATA], + args, + var.set_data_template, + var.set_data_static, + "sx126x_data", + ) return var diff --git a/esphome/components/sx126x/automation.h b/esphome/components/sx126x/automation.h index 411de123413..0dbf3cb42c4 100644 --- a/esphome/components/sx126x/automation.h +++ b/esphome/components/sx126x/automation.h @@ -7,35 +7,12 @@ namespace esphome::sx126x { template class SendPacketAction final : public Action, public Parented { - public: - void set_data_template(std::vector (*func)(Ts...)) { - this->data_.func = func; - this->len_ = -1; // Sentinel value indicates template mode - } - - void set_data_static(const uint8_t *data, size_t len) { - this->data_.data = data; - this->len_ = len; // Length >= 0 indicates static mode - } + TEMPLATABLE_BYTES(data) void play(const Ts &...x) override { - std::vector data; - if (this->len_ >= 0) { - // Static mode: copy from flash to vector - data.assign(this->data_.data, this->data_.data + this->len_); - } else { - // Template mode: call function - data = this->data_.func(x...); - } - this->parent_->transmit_packet(data); + this->data_.template visit( + [this](const uint8_t *data, size_t len) { this->parent_->transmit_packet(data, len); }, x...); } - - protected: - ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length - union Data { - std::vector (*func)(Ts...); // Function pointer (stateless lambdas) - const uint8_t *data; // Pointer to static data in flash - } data_; }; } // namespace esphome::sx126x diff --git a/esphome/components/sx126x/packet_transport/sx126x_transport.cpp b/esphome/components/sx126x/packet_transport/sx126x_transport.cpp index 5e992cc731b..70dc818495d 100644 --- a/esphome/components/sx126x/packet_transport/sx126x_transport.cpp +++ b/esphome/components/sx126x/packet_transport/sx126x_transport.cpp @@ -4,7 +4,7 @@ namespace esphome::sx126x { -static const char *const TAG = "sx126x_transport"; +ESPHOME_LOG_TAG(TAG, "sx126x_transport"); void SX126xTransport::setup() { PacketTransport::setup(); diff --git a/esphome/components/sx126x/sx126x.cpp b/esphome/components/sx126x/sx126x.cpp index 376676ce850..6c1281c89b1 100644 --- a/esphome/components/sx126x/sx126x.cpp +++ b/esphome/components/sx126x/sx126x.cpp @@ -4,7 +4,7 @@ namespace esphome::sx126x { -static const char *const TAG = "sx126x"; +ESPHOME_LOG_TAG(TAG, "sx126x"); static const uint16_t RAMP[8] = {10, 20, 40, 80, 200, 800, 1700, 3400}; static const uint32_t BW_HZ[31] = {4800, 5800, 7300, 9700, 11700, 14600, 19500, 23400, 29300, 39000, 46900, 58600, 78200, 93800, 117300, 156200, 187200, 234300, 312000, 373600, 467000, 7810, @@ -42,13 +42,13 @@ uint8_t SX126x::read_fifo_(uint8_t offset, std::vector &packet) { return status; } -void SX126x::write_fifo_(uint8_t offset, const std::vector &packet) { +void SX126x::write_fifo_(uint8_t offset, const uint8_t *data, size_t len) { this->enable(); this->wait_busy_(); this->transfer_byte(RADIO_WRITE_BUFFER); this->transfer_byte(offset); - for (const uint8_t &byte : packet) { - this->transfer_byte(byte); + for (size_t i = 0; i < len; i++) { + this->transfer_byte(data[i]); } this->disable(); delayMicroseconds(SWITCHING_DELAY_US); @@ -280,7 +280,7 @@ size_t SX126x::get_max_packet_size() { if (this->payload_length_ > 0) { return this->payload_length_; } - return 255; + return SX126X_MAX_PACKET_SIZE; } void SX126x::set_packet_params_(uint8_t payload_length) { @@ -312,12 +312,12 @@ void SX126x::set_packet_params_(uint8_t payload_length) { } } -SX126xError SX126x::transmit_packet(const std::vector &packet) { - if (this->payload_length_ > 0 && this->payload_length_ != packet.size()) { +SX126xError SX126x::transmit_packet(const uint8_t *data, size_t len) { + if (this->payload_length_ > 0 && this->payload_length_ != len) { ESP_LOGE(TAG, "Packet size does not match config"); return SX126xError::INVALID_PARAMS; } - if (packet.empty() || packet.size() > this->get_max_packet_size()) { + if (len == 0 || len > this->get_max_packet_size()) { ESP_LOGE(TAG, "Packet size out of range"); return SX126xError::INVALID_PARAMS; } @@ -325,9 +325,9 @@ SX126xError SX126x::transmit_packet(const std::vector &packet) { SX126xError ret = SX126xError::NONE; this->set_mode_standby(STDBY_XOSC); if (this->payload_length_ == 0) { - this->set_packet_params_(packet.size()); + this->set_packet_params_(len); } - this->write_fifo_(0x00, packet); + this->write_fifo_(0x00, data, len); this->set_mode_tx(); // wait until transmit completes, typically the delay will be less than 100 ms diff --git a/esphome/components/sx126x/sx126x.h b/esphome/components/sx126x/sx126x.h index b3dfe6590a3..536bb93c48f 100644 --- a/esphome/components/sx126x/sx126x.h +++ b/esphome/components/sx126x/sx126x.h @@ -10,6 +10,8 @@ namespace esphome::sx126x { +static constexpr size_t SX126X_MAX_PACKET_SIZE = 255; + enum SX126xBw : uint8_t { // FSK SX126X_BW_4800, @@ -97,7 +99,10 @@ class SX126x final : public Component, void set_tcxo_delay(uint32_t tcxo_delay) { this->tcxo_delay_ = tcxo_delay; } void run_image_cal(); void configure(); - SX126xError transmit_packet(const std::vector &packet); + SX126xError transmit_packet(const uint8_t *data, size_t len); + SX126xError transmit_packet(const std::vector &packet) { + return this->transmit_packet(packet.data(), packet.size()); + } void register_listener(SX126xListener *listener) { this->listeners_.push_back(listener); } Trigger, float, float> *get_packet_trigger() { return &this->packet_trigger_; } @@ -107,7 +112,7 @@ class SX126x final : public Component, void configure_lora_(); void set_packet_params_(uint8_t payload_length); uint8_t read_fifo_(uint8_t offset, std::vector &packet); - void write_fifo_(uint8_t offset, const std::vector &packet); + void write_fifo_(uint8_t offset, const uint8_t *data, size_t len); void write_opcode_(uint8_t opcode, uint8_t *data, uint8_t size); uint8_t read_opcode_(uint8_t opcode, uint8_t *data, uint8_t size); void write_register_(uint16_t reg, uint8_t *data, uint8_t size); diff --git a/esphome/components/sx127x/__init__.py b/esphome/components/sx127x/__init__.py index 9f95c618445..ea7fe38ba3c 100644 --- a/esphome/components/sx127x/__init__.py +++ b/esphome/components/sx127x/__init__.py @@ -10,6 +10,7 @@ from esphome.core import ID from esphome.cpp_generator import MockObj, TemplateArgsType from esphome.types import ConfigType +DOMAIN = "sx127x" MULTI_CONF = True CODEOWNERS = ["@swoboda1337"] DEPENDENCIES = ["spi"] @@ -135,6 +136,11 @@ def validate_raw_data(value: Any) -> bytes | list[int]: ) +MAX_PACKET_SIZE = 255 +# The radio's payload length register is 8 bits, and empty packets are rejected. +validate_packet_data = cv.All(validate_raw_data, cv.Length(min=1, max=MAX_PACKET_SIZE)) + + def validate_config(config: ConfigType) -> ConfigType: if config[CONF_MODULATION] == "LORA": bws = [ @@ -198,7 +204,9 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_PA_POWER, default=17): cv.int_range(min=0, max=17), cv.Optional(CONF_PA_RAMP, default="40us"): cv.enum(RAMP), cv.Optional(CONF_PACKET_MODE): cv.boolean, - cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range(min=0, max=256), + cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range( + min=0, max=MAX_PACKET_SIZE + ), cv.Optional(CONF_PREAMBLE_DETECT, default=0): cv.int_range(min=0, max=3), cv.Optional(CONF_PREAMBLE_ERRORS, default=0): cv.int_range(min=0, max=31), cv.Optional(CONF_PREAMBLE_POLARITY, default=0xAA): cv.All( @@ -286,7 +294,7 @@ for _name, _call in ( SEND_PACKET_ACTION_SCHEMA = cv.maybe_simple_value( { cv.GenerateID(): cv.use_id(SX127x), - cv.Required(CONF_DATA): cv.templatable(validate_raw_data), + cv.Required(CONF_DATA): cv.templatable(validate_packet_data), }, key=CONF_DATA, ) @@ -306,15 +314,11 @@ async def send_packet_action_to_code( ) -> MockObj: var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) - data = config[CONF_DATA] - if isinstance(data, bytes): - data = list(data) - if cg.is_template(data): - templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) - cg.add(var.set_data_template(templ)) - else: - # Generate static array in flash to avoid RAM copy - arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8) - arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data)) - cg.add(var.set_data_static(arr, len(data))) + await automation.templatable_bytes( + config[CONF_DATA], + args, + var.set_data_template, + var.set_data_static, + "sx127x_data", + ) return var diff --git a/esphome/components/sx127x/automation.h b/esphome/components/sx127x/automation.h index 2ed16146813..8d409f0b012 100644 --- a/esphome/components/sx127x/automation.h +++ b/esphome/components/sx127x/automation.h @@ -7,35 +7,13 @@ namespace esphome::sx127x { template class SendPacketAction final : public Action, public Parented { + TEMPLATABLE_BYTES(data) + public: - void set_data_template(std::vector (*func)(Ts...)) { - this->data_.func = func; - this->len_ = -1; // Sentinel value indicates template mode - } - - void set_data_static(const uint8_t *data, size_t len) { - this->data_.data = data; - this->len_ = len; // Length >= 0 indicates static mode - } - void play(const Ts &...x) override { - std::vector data; - if (this->len_ >= 0) { - // Static mode: copy from flash to vector - data.assign(this->data_.data, this->data_.data + this->len_); - } else { - // Template mode: call function - data = this->data_.func(x...); - } - this->parent_->transmit_packet(data); + this->data_.template visit( + [this](const uint8_t *data, size_t len) { this->parent_->transmit_packet(data, len); }, x...); } - - protected: - ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length - union Data { - std::vector (*func)(Ts...); // Function pointer (stateless lambdas) - const uint8_t *data; // Pointer to static data in flash - } data_; }; } // namespace esphome::sx127x diff --git a/esphome/components/sx127x/packet_transport/sx127x_transport.cpp b/esphome/components/sx127x/packet_transport/sx127x_transport.cpp index 52d5631791c..77d1ea28801 100644 --- a/esphome/components/sx127x/packet_transport/sx127x_transport.cpp +++ b/esphome/components/sx127x/packet_transport/sx127x_transport.cpp @@ -4,7 +4,7 @@ namespace esphome::sx127x { -static const char *const TAG = "sx127x_transport"; +ESPHOME_LOG_TAG(TAG, "sx127x_transport"); void SX127xTransport::setup() { PacketTransport::setup(); diff --git a/esphome/components/sx127x/sx127x.cpp b/esphome/components/sx127x/sx127x.cpp index cd81f089149..16c3d1dd21f 100644 --- a/esphome/components/sx127x/sx127x.cpp +++ b/esphome/components/sx127x/sx127x.cpp @@ -4,7 +4,7 @@ namespace esphome::sx127x { -static const char *const TAG = "sx127x"; +ESPHOME_LOG_TAG(TAG, "sx127x"); static const uint32_t FXOSC = 32000000u; static const uint16_t RAMP[16] = {3400, 2000, 1000, 500, 250, 125, 100, 62, 50, 40, 31, 25, 20, 15, 12, 10}; static const uint32_t BW_HZ[22] = {2604, 3125, 3906, 5208, 6250, 7812, 10416, 12500, 15625, 20833, 25000, @@ -43,11 +43,11 @@ void SX127x::read_fifo_(std::vector &packet) { this->disable(); } -void SX127x::write_fifo_(const std::vector &packet) { +void SX127x::write_fifo_(const uint8_t *data, size_t len) { this->enable(); this->write_byte(REG_FIFO | 0x80); - for (const auto &byte : packet) { - this->transfer_byte(byte); + for (size_t i = 0; i < len; i++) { + this->transfer_byte(data[i]); } this->disable(); } @@ -250,18 +250,17 @@ size_t SX127x::get_max_packet_size() { return this->payload_length_; } if (this->modulation_ == MOD_LORA) { - return 256; - } else { - return 64; + return SX127X_MAX_PACKET_SIZE; } + return 64; } -SX127xError SX127x::transmit_packet(const std::vector &packet) { - if (this->payload_length_ > 0 && this->payload_length_ != packet.size()) { +SX127xError SX127x::transmit_packet(const uint8_t *data, size_t len) { + if (this->payload_length_ > 0 && this->payload_length_ != len) { ESP_LOGE(TAG, "Packet size does not match config"); return SX127xError::INVALID_PARAMS; } - if (packet.empty() || packet.size() > this->get_max_packet_size()) { + if (len == 0 || len > this->get_max_packet_size()) { ESP_LOGE(TAG, "Packet size out of range"); return SX127xError::INVALID_PARAMS; } @@ -275,18 +274,18 @@ SX127xError SX127x::transmit_packet(const std::vector &packet) { if (this->modulation_ == MOD_LORA) { this->set_mode_standby(); if (this->payload_length_ == 0) { - this->write_register_(REG_PAYLOAD_LENGTH, packet.size()); + this->write_register_(REG_PAYLOAD_LENGTH, len); } this->write_register_(REG_IRQ_FLAGS, 0xFF); this->write_register_(REG_FIFO_ADDR_PTR, 0); - this->write_fifo_(packet); + this->write_fifo_(data, len); this->set_mode_tx(); } else { this->set_mode_standby(); if (this->payload_length_ == 0) { - this->write_register_(REG_FIFO, packet.size()); + this->write_register_(REG_FIFO, len); } - this->write_fifo_(packet); + this->write_fifo_(data, len); this->set_mode_tx(); } diff --git a/esphome/components/sx127x/sx127x.h b/esphome/components/sx127x/sx127x.h index 070a6eeb96a..27031d98b2b 100644 --- a/esphome/components/sx127x/sx127x.h +++ b/esphome/components/sx127x/sx127x.h @@ -9,6 +9,8 @@ namespace esphome::sx127x { +static constexpr size_t SX127X_MAX_PACKET_SIZE = 255; // the payload length register is 8 bits + enum SX127xBw : uint8_t { SX127X_BW_2_6, SX127X_BW_3_1, @@ -81,7 +83,10 @@ class SX127x final : public Component, void set_sync_value(const std::vector &sync_value) { this->sync_value_ = sync_value; } void run_image_cal(); void configure(); - SX127xError transmit_packet(const std::vector &packet); + SX127xError transmit_packet(const uint8_t *data, size_t len); + SX127xError transmit_packet(const std::vector &packet) { + return this->transmit_packet(packet.data(), packet.size()); + } void register_listener(SX127xListener *listener) { this->listeners_.push_back(listener); } Trigger, float, float> *get_packet_trigger() { return &this->packet_trigger_; } @@ -90,7 +95,7 @@ class SX127x final : public Component, void configure_fsk_ook_(); void configure_lora_(); void set_mode_(uint8_t modulation, uint8_t mode); - void write_fifo_(const std::vector &packet); + void write_fifo_(const uint8_t *data, size_t len); void read_fifo_(std::vector &packet); void write_register_(uint8_t reg, uint8_t value); void call_listeners_(const std::vector &packet, float rssi, float snr); diff --git a/esphome/components/sx1509/__init__.py b/esphome/components/sx1509/__init__.py index 7694b8f7326..f96479498d6 100644 --- a/esphome/components/sx1509/__init__.py +++ b/esphome/components/sx1509/__init__.py @@ -29,6 +29,7 @@ CONF_SX1509_ID = "sx1509_id" AUTO_LOAD = ["key_provider", "gpio_expander"] DEPENDENCIES = ["i2c"] +DOMAIN = "sx1509" MULTI_CONF = True sx1509_ns = cg.esphome_ns.namespace("sx1509") @@ -43,6 +44,10 @@ SX1509KeyTrigger = sx1509_ns.class_( def check_keys(config: ConfigType) -> ConfigType: + for ch in config.get(CONF_KEYS, ""): + if not ch.isascii(): + # Each key is reported as one byte, so only ASCII characters can be key codes + raise cv.Invalid(f"Key code {ch!r} is not an ASCII character") if ( CONF_KEYS in config and len(config[CONF_KEYS]) != config[CONF_KEY_ROWS] * config[CONF_KEY_COLUMNS] @@ -98,8 +103,11 @@ async def to_code(config: ConfigType) -> None: cg.add(var.set_sleep_time(conf[CONF_SLEEP_TIME])) cg.add(var.set_scan_time(conf[CONF_SCAN_TIME])) cg.add(var.set_debounce_time(conf[CONF_DEBOUNCE_TIME])) - if keys := conf.get(CONF_KEYS): - cg.add(var.set_keys(keys)) + if (keys := conf.get(CONF_KEYS)) is not None: + table = cg.shared_progmem_array( + "sx1509_keys", cg.uint8, list(keys.encode()) + ) + cg.add(var.set_keys(table)) for tconf in conf.get(CONF_ON_KEY, []): trigger = cg.new_Pvariable(tconf[CONF_TRIGGER_ID]) cg.add(var.register_key_trigger(trigger)) @@ -126,7 +134,7 @@ CONF_SX1509 = "sx1509" SX1509_PIN_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(SX1509GPIOPin), - cv.Required(CONF_SX1509): cv.use_id(SX1509Component), + cv.Required(CONF_SX1509): pins.use_id_or_address(SX1509Component), cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), cv.Optional(CONF_MODE, default={}): cv.All( { diff --git a/esphome/components/sx1509/output/sx1509_float_output.cpp b/esphome/components/sx1509/output/sx1509_float_output.cpp index 528de1fde5a..5bf47e97bde 100644 --- a/esphome/components/sx1509/output/sx1509_float_output.cpp +++ b/esphome/components/sx1509/output/sx1509_float_output.cpp @@ -4,7 +4,7 @@ namespace esphome::sx1509 { -static const char *const TAG = "sx1509_float_channel"; +ESPHOME_LOG_TAG(TAG, "sx1509_float_channel"); void SX1509FloatOutputChannel::write_state(float state) { const uint16_t max_duty = 255; diff --git a/esphome/components/sx1509/sx1509.cpp b/esphome/components/sx1509/sx1509.cpp index 2397049000d..169d35331f8 100644 --- a/esphome/components/sx1509/sx1509.cpp +++ b/esphome/components/sx1509/sx1509.cpp @@ -4,7 +4,7 @@ namespace esphome::sx1509 { -static const char *const TAG = "sx1509"; +ESPHOME_LOG_TAG(TAG, "sx1509"); void SX1509Component::setup() { ESP_LOGV(TAG, " Resetting devices"); @@ -48,7 +48,7 @@ void SX1509Component::loop() { uint16_t key_data = this->read_key_data(); for (auto *binary_sensor : this->keypad_binary_sensors_) binary_sensor->process(key_data); - if (this->keys_.empty()) + if (this->keys_ == nullptr) return; if (key_data == 0) { this->last_key_ = 0; @@ -64,7 +64,9 @@ void SX1509Component::loop() { break; } col -= 8; - uint8_t key = this->keys_[row * this->cols_ + col]; + if (row >= this->rows_ || col >= this->cols_) // a partial read can leave only a row or a column bit + return; + uint8_t key = progmem_read_byte(&this->keys_[row * this->cols_ + col]); if (key == this->last_key_) return; this->last_key_ = key; diff --git a/esphome/components/sx1509/sx1509.h b/esphome/components/sx1509/sx1509.h index c7aed2cddd1..ef6ee6ec5e6 100644 --- a/esphome/components/sx1509/sx1509.h +++ b/esphome/components/sx1509/sx1509.h @@ -51,7 +51,8 @@ class SX1509Component final : public Component, this->cols_ = cols; this->has_keypad_ = true; }; - void set_keys(std::string keys) { this->keys_ = std::move(keys); }; // NOLINT(performance-unnecessary-value-param) + /// `keys` is a codegen PROGMEM table with one key code per button. + void set_keys(const uint8_t *keys) { this->keys_ = keys; } void set_sleep_time(uint16_t sleep_time) { this->sleep_time_ = sleep_time; }; void set_scan_time(uint8_t scan_time) { this->scan_time_ = scan_time; }; void set_debounce_time(uint8_t debounce_time = 1) { this->debounce_time_ = debounce_time; }; @@ -76,7 +77,7 @@ class SX1509Component final : public Component, bool has_keypad_ = false; uint8_t rows_ = 0; uint8_t cols_ = 0; - std::string keys_; + const uint8_t *keys_{nullptr}; uint16_t sleep_time_ = 128; uint8_t scan_time_ = 1; uint8_t debounce_time_ = 1; diff --git a/esphome/components/sx1509/sx1509_gpio_pin.cpp b/esphome/components/sx1509/sx1509_gpio_pin.cpp index 28ef1c5830f..41f967b6672 100644 --- a/esphome/components/sx1509/sx1509_gpio_pin.cpp +++ b/esphome/components/sx1509/sx1509_gpio_pin.cpp @@ -5,7 +5,7 @@ namespace esphome::sx1509 { -static const char *const TAG = "sx1509_gpio_pin"; +ESPHOME_LOG_TAG(TAG, "sx1509_gpio_pin"); void SX1509GPIOPin::setup() { pin_mode(flags_); } void SX1509GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } diff --git a/esphome/components/sy6970/__init__.py b/esphome/components/sy6970/__init__.py index cb9d64aee70..90bd9f46911 100644 --- a/esphome/components/sy6970/__init__.py +++ b/esphome/components/sy6970/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@linkedupbits"] DEPENDENCIES = ["i2c"] +DOMAIN = "sy6970" MULTI_CONF = True CONF_SY6970_ID = "sy6970_id" diff --git a/esphome/components/sy6970/sy6970.cpp b/esphome/components/sy6970/sy6970.cpp index 1f1648cfa77..3f381002cbd 100644 --- a/esphome/components/sy6970/sy6970.cpp +++ b/esphome/components/sy6970/sy6970.cpp @@ -4,7 +4,7 @@ namespace esphome::sy6970 { -static const char *const TAG = "sy6970"; +ESPHOME_LOG_TAG(TAG, "sy6970"); bool SY6970Component::read_all_registers_() { // Read all registers from 0x00 to 0x14 in one transaction (21 bytes) diff --git a/esphome/components/syslog/__init__.py b/esphome/components/syslog/__init__.py index e25567c783d..b48192ddd29 100644 --- a/esphome/components/syslog/__init__.py +++ b/esphome/components/syslog/__init__.py @@ -9,6 +9,7 @@ from esphome.cpp_types import Component, Parented from esphome.types import ConfigType CODEOWNERS = ["@clydebarrow"] +DOMAIN = "syslog" DEPENDENCIES = ["udp", "logger", "time"] diff --git a/esphome/components/systa_bus/__init__.py b/esphome/components/systa_bus/__init__.py index 0695b43d416..49ac214d400 100644 --- a/esphome/components/systa_bus/__init__.py +++ b/esphome/components/systa_bus/__init__.py @@ -6,6 +6,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@Mat931"] +DOMAIN = "systa_bus" DEPENDENCIES = ["uart"] diff --git a/esphome/components/systa_bus/sensor/systa_bus_sensor.cpp b/esphome/components/systa_bus/sensor/systa_bus_sensor.cpp index 08a46771805..5d9890eca51 100644 --- a/esphome/components/systa_bus/sensor/systa_bus_sensor.cpp +++ b/esphome/components/systa_bus/sensor/systa_bus_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::systa_bus { -static const char *const TAG = "systa_bus.sensor"; +ESPHOME_LOG_TAG(TAG, "systa_bus.sensor"); static int16_t get_i16be(std::span message, size_t start) { return static_cast(encode_uint16(message[start], message[start + 1])); diff --git a/esphome/components/systa_bus/systa_bus.cpp b/esphome/components/systa_bus/systa_bus.cpp index 3a2f6717e9b..4c537b75cbf 100644 --- a/esphome/components/systa_bus/systa_bus.cpp +++ b/esphome/components/systa_bus/systa_bus.cpp @@ -6,7 +6,7 @@ namespace esphome::systa_bus { -static const char *const TAG = "systa_bus"; +ESPHOME_LOG_TAG(TAG, "systa_bus"); void SystaBus::dump_config() { ESP_LOGCONFIG(TAG, "SystaBus:"); } diff --git a/esphome/components/t6615/__init__.py b/esphome/components/t6615/__init__.py index e69de29bb2d..bbbca72b13f 100644 --- a/esphome/components/t6615/__init__.py +++ b/esphome/components/t6615/__init__.py @@ -0,0 +1 @@ +DOMAIN = "t6615" diff --git a/esphome/components/t6615/t6615.cpp b/esphome/components/t6615/t6615.cpp index 982cc181b76..d3997528890 100644 --- a/esphome/components/t6615/t6615.cpp +++ b/esphome/components/t6615/t6615.cpp @@ -4,7 +4,7 @@ namespace esphome::t6615 { -static const char *const TAG = "t6615"; +ESPHOME_LOG_TAG(TAG, "t6615"); static const uint32_t T6615_TIMEOUT = 1000; static const uint8_t T6615_MAGIC = 0xFF; diff --git a/esphome/components/tas2780/__init__.py b/esphome/components/tas2780/__init__.py index 81e00ca74ba..f1fed4b698e 100644 --- a/esphome/components/tas2780/__init__.py +++ b/esphome/components/tas2780/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@remcom"] +DOMAIN = "tas2780" diff --git a/esphome/components/tas2780/tas2780.cpp b/esphome/components/tas2780/tas2780.cpp index 191dbf823c3..d69bd4dca13 100644 --- a/esphome/components/tas2780/tas2780.cpp +++ b/esphome/components/tas2780/tas2780.cpp @@ -8,7 +8,7 @@ namespace esphome::tas2780 { -static const char *const TAG = "tas2780"; +ESPHOME_LOG_TAG(TAG, "tas2780"); static constexpr uint8_t TAS2780_PAGE_SELECT = 0x00; // Page Select static constexpr uint8_t TAS2780_PAGE_0 = 0x00; // Page 0 diff --git a/esphome/components/tas58xx/__init__.py b/esphome/components/tas58xx/__init__.py index b87036984dc..56700aeafdf 100644 --- a/esphome/components/tas58xx/__init__.py +++ b/esphome/components/tas58xx/__init__.py @@ -1 +1,11 @@ +from esphome.config_helpers import filter_source_files_from_defines + CODEOWNERS = ["@mrtoy-me", "@remcom"] +DOMAIN = "tas58xx" + +FILTER_SOURCE_FILES = filter_source_files_from_defines( + { + "model_tas5805m.cpp": "USE_TAS58XX_TAS5805M", + "model_tas5825m.cpp": "USE_TAS58XX_TAS5825M", + } +) diff --git a/esphome/components/tas58xx/audio_dac.py b/esphome/components/tas58xx/audio_dac.py index 18a60a66455..7b1e3e239e8 100644 --- a/esphome/components/tas58xx/audio_dac.py +++ b/esphome/components/tas58xx/audio_dac.py @@ -11,6 +11,8 @@ from esphome.const import CONF_ENABLE_PIN, CONF_ID, CONF_MODEL from esphome.cpp_generator import MockObj from esphome.types import ConfigType +from . import FILTER_SOURCE_FILES # noqa: F401 pylint: disable=unused-import + _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ["i2c"] @@ -23,6 +25,9 @@ CONF_VOLUME_MIN_DB = "volume_min_db" CONF_VOLUME_MAX_DB = "volume_max_db" CONF_TAS58XX_ID = "tas58xx_id" +DAC_TAS5805M = "tas5805m" +DAC_TAS5825M = "tas5825m" + tas58xx_ns = cg.esphome_ns.namespace("tas58xx") TAS58xx = tas58xx_ns.class_("TAS58xx", AudioDac, cg.PollingComponent, i2c.I2CDevice) @@ -54,13 +59,20 @@ class Model: MODELS: dict[str, Model] = { - "tas5805m": Model( + DAC_TAS5805M: Model( model_info=tas58xx_ns.TAS5805M_MODEL, analog_gain_min_db=-15.5, volume_min_db=-103.0, volume_max_db=24.0, default_address=0x2D, ), + DAC_TAS5825M: Model( + model_info=tas58xx_ns.TAS5825M_MODEL, + analog_gain_min_db=-15.5, + volume_min_db=-103.0, + volume_max_db=24.0, + default_address=0x4C, + ), } @@ -162,6 +174,7 @@ for _name, _call in ( async def to_code(config: ConfigType) -> None: model = MODELS[config[CONF_MODEL]] + cg.add_define(f"USE_TAS58XX_{config[CONF_MODEL].upper()}") var = cg.new_Pvariable(config[CONF_ID], cg.RawExpression(f"&{model.model_info}")) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/tas58xx/binary_sensor.py b/esphome/components/tas58xx/binary_sensor.py index 398cfa2440a..d0577398741 100644 --- a/esphome/components/tas58xx/binary_sensor.py +++ b/esphome/components/tas58xx/binary_sensor.py @@ -1,15 +1,17 @@ import esphome.codegen as cg from esphome.components import binary_sensor import esphome.config_validation as cv -from esphome.const import DEVICE_CLASS_PROBLEM, ENTITY_CATEGORY_DIAGNOSTIC +from esphome.const import CONF_MODEL, DEVICE_CLASS_PROBLEM, ENTITY_CATEGORY_DIAGNOSTIC +import esphome.final_validate as fv from esphome.types import ConfigType -from .audio_dac import CONF_TAS58XX_ID, TAS58xx, tas58xx_ns +from . import FILTER_SOURCE_FILES # noqa: F401 pylint: disable=unused-import +from .audio_dac import CONF_TAS58XX_ID, DAC_TAS5825M, TAS58xx, tas58xx_ns CONF_HAVE_FAULT = "have_fault" -# Each name matches a FaultSensor value in tas58xx.h -FAULT_SENSORS = ( +# Faults common to TAS5805M and TAS5825M +COMMON_FAULT_SENSORS = ( "left_channel_dc_fault", "right_channel_dc_fault", "left_channel_over_current", @@ -20,8 +22,22 @@ FAULT_SENSORS = ( "pvdd_over_voltage", "pvdd_under_voltage", "over_temp_shutdown", - "over_temp_warning", + "over_temp_warning", # tas582x OTW Level 3 - use tas5805 naming ) +# Additional faults available on TAS5825M +TAS5825M_ONLY_FAULT_SENSORS = ( + "load_eeprom_error", + "right_channel_cbc_over_current", + "left_channel_cbc_over_current", + "left_channel_cbc_over_current_warning", + "right_channel_cbc_over_current_warning", + "over_temp_146c_warning", # tas582x OTW Level 4 + # "over_temp_122c_warning", # tas582x OTW Level 2 - not currently included + # "over_temp_112c_warning", # tas582x OTW Level 1 - not currently included +) + +# Each name matches a FaultSensor value in tas58xx.h +FAULT_SENSORS = COMMON_FAULT_SENSORS + TAS5825M_ONLY_FAULT_SENSORS FaultSensor = tas58xx_ns.enum("FaultSensor") @@ -39,6 +55,26 @@ CONFIG_SCHEMA = cv.Schema( ) +def _final_validate(config: ConfigType) -> ConfigType: + fconf = fv.full_config.get() + hub_path = fconf.get_path_for_id(config[CONF_TAS58XX_ID]) + hub_conf = fconf.get_config_for_path(hub_path[:-1]) + + if hub_conf[CONF_MODEL] == DAC_TAS5825M: + return config + + unsupported = sorted(frozenset(TAS5825M_ONLY_FAULT_SENSORS).intersection(config)) + if unsupported: + raise cv.Invalid( + f"binary_sensor platform tas58xx: '{', '.join(unsupported)}' is only available for 'model: {DAC_TAS5825M}' " + f"- Remove from YAML for 'model: {hub_conf[CONF_MODEL]}'" + ) + return config + + +FINAL_VALIDATE_SCHEMA = _final_validate + + async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_TAS58XX_ID]) binary_sensors = binary_sensor.sub_binary_sensors(config) diff --git a/esphome/components/tas58xx/model_tas5805m.cpp b/esphome/components/tas58xx/model_tas5805m.cpp index e5eb5a837e1..09b369435e9 100644 --- a/esphome/components/tas58xx/model_tas5805m.cpp +++ b/esphome/components/tas58xx/model_tas5805m.cpp @@ -15,9 +15,12 @@ static const uint8_t STARTUP_SEQUENCE[][2] PROGMEM = { {0x60, 0x01}, // ADR_PIN_CTRL: output {0x7D, 0x11}, {0x7E, 0xFF}, - {0x00, 0x01}, + {0x00, 0x01}, // Page 1 {0x51, 0x05}, - {0x00, 0x00}, + {0x00, 0x00}, // Page 0 + + // Additional register configuration + {0x53, 0x60}, // ANA_CTRL: for high audio performance use 175kHz bandwidth with Fsw=768kHz }; static const LogString *model_name() { return LOG_STR("TAS5805M"); } @@ -67,17 +70,23 @@ const ModelInfo TAS5805M_MODEL = { .fault_name = fault_name, .fault_sensor_bits = { - 3, // FAULT_SENSOR_LEFT_CHANNEL_DC_FAULT - 2, // FAULT_SENSOR_RIGHT_CHANNEL_DC_FAULT - 1, // FAULT_SENSOR_LEFT_CHANNEL_OVER_CURRENT - 0, // FAULT_SENSOR_RIGHT_CHANNEL_OVER_CURRENT - 15, // FAULT_SENSOR_OTP_CRC_CHECK - 14, // FAULT_SENSOR_BQ_WRITE_FAILED - 10, // FAULT_SENSOR_CLOCK_FAULT - 9, // FAULT_SENSOR_PVDD_OVER_VOLTAGE - 8, // FAULT_SENSOR_PVDD_UNDER_VOLTAGE - 16, // FAULT_SENSOR_OVER_TEMP_SHUTDOWN - 26, // FAULT_SENSOR_OVER_TEMP_WARNING + 3, // FAULT_SENSOR_LEFT_CHANNEL_DC_FAULT + 2, // FAULT_SENSOR_RIGHT_CHANNEL_DC_FAULT + 1, // FAULT_SENSOR_LEFT_CHANNEL_OVER_CURRENT + 0, // FAULT_SENSOR_RIGHT_CHANNEL_OVER_CURRENT + 15, // FAULT_SENSOR_OTP_CRC_CHECK + 14, // FAULT_SENSOR_BQ_WRITE_FAILED + 10, // FAULT_SENSOR_CLOCK_FAULT + 9, // FAULT_SENSOR_PVDD_OVER_VOLTAGE + 8, // FAULT_SENSOR_PVDD_UNDER_VOLTAGE + 16, // FAULT_SENSOR_OVER_TEMP_SHUTDOWN + 26, // FAULT_SENSOR_OVER_TEMP_WARNING + NO_BIT, // FAULT_SENSOR_LOAD_EEPROM_ERROR + NO_BIT, // FAULT_SENSOR_RIGHT_CHANNEL_CBC_OVER_CURRENT + NO_BIT, // FAULT_SENSOR_LEFT_CHANNEL_CBC_OVER_CURRENT + NO_BIT, // FAULT_SENSOR_LEFT_CHANNEL_CBC_OVER_CURRENT_WARNING + NO_BIT, // FAULT_SENSOR_RIGHT_CHANNEL_CBC_OVER_CURRENT_WARNING + NO_BIT, // FAULT_SENSOR_OVER_TEMP_146C_WARNING }, }; diff --git a/esphome/components/tas58xx/model_tas5825m.cpp b/esphome/components/tas58xx/model_tas5825m.cpp new file mode 100644 index 00000000000..cf5861cb4a4 --- /dev/null +++ b/esphome/components/tas58xx/model_tas5825m.cpp @@ -0,0 +1,118 @@ +#include "tas58xx.h" + +#include "esphome/core/hal.h" + +namespace esphome::tas58xx { + +// Remainder of the startup sequence, run after the reset in TAS58xx::init_(). Register 0x00 selects the page. +// Based on the TI PurePath Console (PPC) export for "TAS5825M 2-Band DRC&AGL 2.0 96k". +// Registers 0x46, 0x7D, 0x7E, page 1 register 0x51 and page 2 registers 0x1D, 0x19 are not documented in datasheet. +static const uint8_t STARTUP_SEQUENCE[][2] PROGMEM = { + {0x03, 0x00}, // DEVICE_CTRL_2: deep sleep, the state the PPC sequence starts from + {0x7D, 0x11}, + {0x7E, 0xFF}, + {0x00, 0x01}, // Page 1 + {0x51, 0x05}, + {0x00, 0x02}, // Page 2 + {0x1D, 0x00}, + {0x19, 0x80}, + {0x00, 0x00}, // Page 0 + {0x46, 0x11}, + // PPC also writes 0x02 (DEVICE_CTRL_1) and 0x54 (AGAIN) here; init_() writes both from YAML + {0x53, 0x01}, // ANA_CTRL: in phase, 100kHz bandwidth for high audio performance with Fsw=384kHz + {0x03, 0x02}, // DEVICE_CTRL_2: Hi-Z + + // Additional register configuration + // GPIO1 is a dedicated pin on TAS5825M (separate from ADR), used as FAULTZ + {0x62, 0x0B}, // GPIO1_SEL: GPIO1 as FAULTZ + {0x60, 0x02}, // GPIO_CTRL: GPIO1 is output + {0x77, 0x07}, // CBC_CONTROL: enable cycle-by-cycle current limit for warnings and faults +}; + +static const LogString *model_name() { return LOG_STR("TAS5825M"); } + +// An if chain rather than a switch: a switch table would land in rodata, which is RAM on ESP8266. +static const LogString *fault_name(uint8_t index) { + if (index == 0) + return LOG_STR("Right channel over current"); + if (index == 1) + return LOG_STR("Left channel over current"); + if (index == 2) + return LOG_STR("Right channel DC fault"); + if (index == 3) + return LOG_STR("Left channel DC fault"); + if (index == 8) + return LOG_STR("PVDD under voltage"); + if (index == 9) + return LOG_STR("PVDD over voltage"); + if (index == 10) + return LOG_STR("Clock fault"); + if (index == 13) + return LOG_STR("Load EEPROM error"); + if (index == 14) + return LOG_STR("BQ write failed"); + if (index == 15) + return LOG_STR("OTP CRC check error"); + if (index == 16) + return LOG_STR("Over temperature shutdown"); + if (index == 17) + return LOG_STR("Left channel CBC over current"); + if (index == 18) + return LOG_STR("Right channel CBC over current"); + // if (index == 24) + // return LOG_STR("Over temperature warning 112C"); // not currently included + // if (index == 25) + // return LOG_STR("Over temperature warning 122C"); // not currently included + if (index == 26) + return LOG_STR("Over temperature warning"); + if (index == 27) + return LOG_STR("Over temperature warning 146C"); + if (index == 28) + return LOG_STR("Right channel CBC over current warning"); + if (index == 29) + return LOG_STR("Left channel CBC over current warning"); + return LOG_STR("Unknown fault"); +} + +const ModelInfo TAS5825M_MODEL = { + .name = model_name, + .startup_sequence = STARTUP_SEQUENCE, + .startup_sequence_length = sizeof(STARTUP_SEQUENCE) / sizeof(STARTUP_SEQUENCE[0]), + .mixer_book = 0x8C, + .mixer_page = 0x0B, + .mixer_register = 0x14, + // The clock fault is left out of the log and have_fault: it is set whenever the I2S clock stops, which is normal + .fault_error_mask = 0x0007E30F, + .fault_warning_mask = + 0x3C000000, // 0x3F000000 if OVER_TEMP_122C_WARNING and OVER_TEMP_112C_WARNING included in future + // DC and over current faults keep the output off until cleared (datasheet 7.5.3.3.1, 7.5.3.3.2). They are not + // cleared automatically: a DC fault re-trips only after 570 ms, so a clear on every poll would pass DC to the + // speaker. + .fault_output_off_mask = 0x0000000F, + .fault_latched_mask = 0x3F07E70F, + .fault_name = fault_name, + .fault_sensor_bits = + { + 3, // FAULT_SENSOR_LEFT_CHANNEL_DC_FAULT + 2, // FAULT_SENSOR_RIGHT_CHANNEL_DC_FAULT + 1, // FAULT_SENSOR_LEFT_CHANNEL_OVER_CURRENT + 0, // FAULT_SENSOR_RIGHT_CHANNEL_OVER_CURRENT + 15, // FAULT_SENSOR_OTP_CRC_CHECK + 14, // FAULT_SENSOR_BQ_WRITE_FAILED + 10, // FAULT_SENSOR_CLOCK_FAULT + 9, // FAULT_SENSOR_PVDD_OVER_VOLTAGE + 8, // FAULT_SENSOR_PVDD_UNDER_VOLTAGE + 16, // FAULT_SENSOR_OVER_TEMP_SHUTDOWN + 26, // FAULT_SENSOR_OVER_TEMP_WARNING + 13, // FAULT_SENSOR_LOAD_EEPROM_ERROR + 18, // FAULT_SENSOR_RIGHT_CHANNEL_CBC_OVER_CURRENT + 17, // FAULT_SENSOR_LEFT_CHANNEL_CBC_OVER_CURRENT + 29, // FAULT_SENSOR_LEFT_CHANNEL_CBC_OVER_CURRENT_WARNING + 28, // FAULT_SENSOR_RIGHT_CHANNEL_CBC_OVER_CURRENT_WARNING + 27, // FAULT_SENSOR_OVER_TEMP_146C_WARNING + // 25, // FAULT_SENSOR_OVER_TEMP_122C_WARNING // not currently included + // 24, // FAULT_SENSOR_OVER_TEMP_112C_WARNING // not currently included + }, +}; + +} // namespace esphome::tas58xx diff --git a/esphome/components/tas58xx/tas58xx.cpp b/esphome/components/tas58xx/tas58xx.cpp index 458150a1254..3addbfda00d 100644 --- a/esphome/components/tas58xx/tas58xx.cpp +++ b/esphome/components/tas58xx/tas58xx.cpp @@ -8,7 +8,7 @@ namespace esphome::tas58xx { -static const char *const TAG = "tas58xx"; +ESPHOME_LOG_TAG(TAG, "tas58xx"); static constexpr uint8_t TAS58XX_PAGE_SELECT = 0x00; // Page Select, in every book static constexpr uint8_t TAS58XX_BOOK_SELECT = 0x7F; // Book Select, on page 0 of every book @@ -196,8 +196,9 @@ bool TAS58xx::read_faults_() { if (this->have_fault_binary_sensor_ != nullptr) this->have_fault_binary_sensor_->publish_state((active & model.fault_error_mask) != 0); for (uint8_t fault = 0; fault < FAULT_SENSOR_COUNT; fault++) { - if (this->fault_binary_sensors_[fault] != nullptr) - this->fault_binary_sensors_[fault]->publish_state(faults & (uint32_t{1} << model.fault_sensor_bits[fault])); + const uint8_t bit = model.fault_sensor_bits[fault]; + if (this->fault_binary_sensors_[fault] != nullptr && bit != NO_BIT) + this->fault_binary_sensors_[fault]->publish_state(faults & (uint32_t{1} << bit)); } #endif @@ -264,7 +265,7 @@ void TAS58xx::dump_config() { " Analog Gain: %.1f dB\n" " DAC Mode: %s\n" " Mixer Mode: %s\n" - " Volume Range: %.1f dB - %.1f dB", + " Volume Range: %.1f dB to %.1f dB", this->analog_gain_db_, this->dac_mode_ == DAC_MODE_PBTL ? LOG_STR_LITERAL("PBTL") : LOG_STR_LITERAL("BTL"), LOG_STR_ARG(mixer_mode), this->volume_min_db_, this->volume_max_db_); diff --git a/esphome/components/tas58xx/tas58xx.h b/esphome/components/tas58xx/tas58xx.h index b1e9a531ddc..d0f97a5cd9f 100644 --- a/esphome/components/tas58xx/tas58xx.h +++ b/esphome/components/tas58xx/tas58xx.h @@ -27,9 +27,11 @@ enum MixerMode : uint8_t { MIXER_MODE_RIGHT, }; -/// Fault binary sensors that map to a single fault bit. The Python FAULT_SENSORS list uses the same names. +/// Fault binary sensors that map to a single fault bit. Note that ordering is important. +/// The Python FAULT_SENSORS list in binary_sensor.py uses the same names and ordering. enum FaultSensor : uint8_t { - FAULT_SENSOR_LEFT_CHANNEL_DC_FAULT = 0, + // Faults common to TAS5805M and TAS5825M + FAULT_SENSOR_LEFT_CHANNEL_DC_FAULT, FAULT_SENSOR_RIGHT_CHANNEL_DC_FAULT, FAULT_SENSOR_LEFT_CHANNEL_OVER_CURRENT, FAULT_SENSOR_RIGHT_CHANNEL_OVER_CURRENT, @@ -40,9 +42,26 @@ enum FaultSensor : uint8_t { FAULT_SENSOR_PVDD_UNDER_VOLTAGE, FAULT_SENSOR_OVER_TEMP_SHUTDOWN, FAULT_SENSOR_OVER_TEMP_WARNING, - FAULT_SENSOR_COUNT, + // Faults available for TAS5825M Only + FAULT_SENSOR_LOAD_EEPROM_ERROR, + FAULT_SENSOR_RIGHT_CHANNEL_CBC_OVER_CURRENT, + FAULT_SENSOR_LEFT_CHANNEL_CBC_OVER_CURRENT, + FAULT_SENSOR_LEFT_CHANNEL_CBC_OVER_CURRENT_WARNING, + FAULT_SENSOR_RIGHT_CHANNEL_CBC_OVER_CURRENT_WARNING, + FAULT_SENSOR_OVER_TEMP_146C_WARNING, + // FAULT_SENSOR_OVER_TEMP_122C_WARNING, + // FAULT_SENSOR_OVER_TEMP_112C_WARNING, + FAULT_SENSOR_COUNT, // keep last }; +// compile time check +static_assert( + FAULT_SENSOR_COUNT == 17, + "enum FaultSensor altered: Update FAULT_SENSORS in binary_sensor.py and fault_sensor_bits in each model's cpp"); + +/// ModelInfo::fault_sensor_bits value for a FaultSensor that the model does not have. +static constexpr uint8_t NO_BIT = 0xFF; + /// Everything that differs between models of the family. One constant instance exists per model, see /// model_*.cpp, and each TAS58xx instance points to the one for its model. /// @@ -67,11 +86,16 @@ struct ModelInfo { /// Faults that stay set after the condition is gone and are cleared after each read uint32_t fault_latched_mask; const LogString *(*fault_name)(uint8_t index); - /// Bit index for each FaultSensor + /// Bit index for each FaultSensor, NO_BIT used where the model does not have that fault bit uint8_t fault_sensor_bits[FAULT_SENSOR_COUNT]; }; +#ifdef USE_TAS58XX_TAS5805M extern const ModelInfo TAS5805M_MODEL; +#endif +#ifdef USE_TAS58XX_TAS5825M +extern const ModelInfo TAS5825M_MODEL; +#endif class TAS58xx : public audio_dac::AudioDac, public PollingComponent, public i2c::I2CDevice { public: diff --git a/esphome/components/tc74/__init__.py b/esphome/components/tc74/__init__.py index c1407c7cad0..1021a425b8d 100644 --- a/esphome/components/tc74/__init__.py +++ b/esphome/components/tc74/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@sethgirvan"] +DOMAIN = "tc74" diff --git a/esphome/components/tc74/tc74.cpp b/esphome/components/tc74/tc74.cpp index bc522d1b745..98428cd1225 100644 --- a/esphome/components/tc74/tc74.cpp +++ b/esphome/components/tc74/tc74.cpp @@ -5,7 +5,7 @@ namespace esphome::tc74 { -static const char *const TAG = "tc74"; +ESPHOME_LOG_TAG(TAG, "tc74"); static const uint8_t TC74_REGISTER_TEMPERATURE = 0x00; static const uint8_t TC74_REGISTER_CONFIGURATION = 0x01; diff --git a/esphome/components/tca9548a/__init__.py b/esphome/components/tca9548a/__init__.py index 381b84cad65..a1945e97c52 100644 --- a/esphome/components/tca9548a/__init__.py +++ b/esphome/components/tca9548a/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_CHANNEL, CONF_CHANNELS, CONF_ID from esphome.types import ConfigType CODEOWNERS = ["@andreashergert1984"] +DOMAIN = "tca9548a" DEPENDENCIES = ["i2c"] diff --git a/esphome/components/tca9548a/tca9548a.cpp b/esphome/components/tca9548a/tca9548a.cpp index 3fc91a3bbb9..f10578c97c2 100644 --- a/esphome/components/tca9548a/tca9548a.cpp +++ b/esphome/components/tca9548a/tca9548a.cpp @@ -3,7 +3,7 @@ namespace esphome::tca9548a { -static const char *const TAG = "tca9548a"; +ESPHOME_LOG_TAG(TAG, "tca9548a"); i2c::ErrorCode TCA9548AChannel::write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer, size_t read_count) { diff --git a/esphome/components/tca9555/__init__.py b/esphome/components/tca9555/__init__.py index 3033cc65e08..a039872e164 100644 --- a/esphome/components/tca9555/__init__.py +++ b/esphome/components/tca9555/__init__.py @@ -15,6 +15,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@mobrembski"] +DOMAIN = "tca9555" AUTO_LOAD = ["gpio_expander"] DEPENDENCIES = ["i2c"] @@ -62,7 +63,7 @@ TCA9555_PIN_SCHEMA = pins.gpio_base_schema( invertible=True, ).extend( { - cv.Required(CONF_TCA9555): cv.use_id(TCA9555Component), + cv.Required(CONF_TCA9555): pins.use_id_or_address(TCA9555Component), } ) diff --git a/esphome/components/tca9555/tca9555.cpp b/esphome/components/tca9555/tca9555.cpp index b210c082dda..7a59c403f31 100644 --- a/esphome/components/tca9555/tca9555.cpp +++ b/esphome/components/tca9555/tca9555.cpp @@ -12,7 +12,7 @@ static const uint8_t TCA9555_CONFIGURATION_PORT_1 = 0x07; namespace esphome::tca9555 { -static const char *const TAG = "tca9555"; +ESPHOME_LOG_TAG(TAG, "tca9555"); void TCA9555Component::setup() { if (!this->read_gpio_modes_()) { diff --git a/esphome/components/tcl112/__init__.py b/esphome/components/tcl112/__init__.py index e69de29bb2d..03f39088177 100644 --- a/esphome/components/tcl112/__init__.py +++ b/esphome/components/tcl112/__init__.py @@ -0,0 +1 @@ +DOMAIN = "tcl112" diff --git a/esphome/components/tcl112/tcl112.cpp b/esphome/components/tcl112/tcl112.cpp index cd819e5b16b..5e7f0ef29c7 100644 --- a/esphome/components/tcl112/tcl112.cpp +++ b/esphome/components/tcl112/tcl112.cpp @@ -3,7 +3,7 @@ namespace esphome::tcl112 { -static const char *const TAG = "tcl112.climate"; +ESPHOME_LOG_TAG(TAG, "tcl112.climate"); const uint16_t TCL112_STATE_LENGTH = 14; const uint16_t TCL112_BITS = TCL112_STATE_LENGTH * 8; diff --git a/esphome/components/tcp_uart/__init__.py b/esphome/components/tcp_uart/__init__.py index 8e14e690f9e..35326d77d52 100644 --- a/esphome/components/tcp_uart/__init__.py +++ b/esphome/components/tcp_uart/__init__.py @@ -1,5 +1,5 @@ import esphome.codegen as cg -from esphome.components import binary_sensor, socket, uart +from esphome.components import binary_sensor, sensor, socket, uart from esphome.components.const import ( CONF_ALLOWED_IPS, CONF_CONNECTED, @@ -17,14 +17,18 @@ from esphome.const import ( CONF_PORT, DEVICE_CLASS_CONNECTIVITY, ENTITY_CATEGORY_DIAGNOSTIC, + STATE_CLASS_TOTAL_INCREASING, ) from esphome.types import ConfigType CODEOWNERS = ["@Bascht74"] DEPENDENCIES = ["network"] -AUTO_LOAD = ["uart", "binary_sensor", "socket"] +AUTO_LOAD = ["uart", "binary_sensor", "sensor", "socket"] +DOMAIN = "tcp_uart" MULTI_CONF = True +CONF_DISCONNECTS = "disconnects" + tcp_uart_ns = cg.esphome_ns.namespace("tcp_uart") TcpUart = tcp_uart_ns.class_("TcpUart", uart.UARTComponent, cg.Component) @@ -46,6 +50,11 @@ BASE_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_CONNECTIVITY, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), + cv.Optional(CONF_DISCONNECTS): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_TOTAL_INCREASING, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -54,7 +63,7 @@ CONFIG_SCHEMA = cv.All( { "client": BASE_SCHEMA.extend( { - cv.Required(CONF_HOST): cv.string, + cv.Required(CONF_HOST): socket.ipv4_host, } ), "server": BASE_SCHEMA.extend( @@ -93,3 +102,5 @@ async def to_code(config: ConfigType) -> None: cg.add(var.set_host(host)) binary_sensors = binary_sensor.sub_binary_sensors(config) await binary_sensors(CONF_CONNECTED, var.set_connected_sensor) + sensors = sensor.sub_sensors(config) + await sensors(CONF_DISCONNECTS, var.set_disconnects_sensor) diff --git a/esphome/components/tcp_uart/tcp_uart.cpp b/esphome/components/tcp_uart/tcp_uart.cpp index 5913610ee84..3dead1fb65c 100644 --- a/esphome/components/tcp_uart/tcp_uart.cpp +++ b/esphome/components/tcp_uart/tcp_uart.cpp @@ -8,7 +8,7 @@ namespace esphome::tcp_uart { -static const char *const TAG = "tcp_uart"; +ESPHOME_LOG_TAG(TAG, "tcp_uart"); static constexpr uint32_t DROP_LOG_INTERVAL_MS = 5000; @@ -20,6 +20,11 @@ void TcpUart::setup() { if (this->connected_sensor_ != nullptr) { this->connected_sensor_->publish_state(false); } +#ifdef USE_SENSOR + if (this->disconnects_sensor_ != nullptr) { + this->disconnects_sensor_->publish_state(0); + } +#endif } void TcpUart::dump_config() { @@ -34,6 +39,9 @@ void TcpUart::dump_config() { this->listener_.dump_config(); #endif LOG_BINARY_SENSOR(" ", "Connected", this->connected_sensor_); +#ifdef USE_SENSOR + LOG_SENSOR(" ", "Disconnects", this->disconnects_sensor_); +#endif } void TcpUart::on_shutdown() { @@ -46,12 +54,20 @@ void TcpUart::on_shutdown() { void TcpUart::sync_link_() { bool up = this->link_.connected(); this->link_was_up_ = up; - if (!up) { + if (up) { + // Unread bytes of the last session stay readable while down, never into the next one. this->rx_start_ = this->rx_end_ = 0; } if (this->connected_sensor_ != nullptr) { this->connected_sensor_->publish_state(up); } +#ifdef USE_SENSOR + // Only edges get here, so down is the falling edge. + if (!up && this->disconnects_sensor_ != nullptr) { + this->disconnects_++; + this->disconnects_sensor_->publish_state(this->disconnects_); + } +#endif } void TcpUart::read_socket_() { @@ -67,7 +83,7 @@ void TcpUart::read_socket_() { } ssize_t count = this->link_.read(this->rx_ + this->rx_end_, room); if (count <= 0) { - // A dropped link (-1) is cleaned up by sync_link_() on the next loop. + // A dropped link (-1) is seen by sync_link_() on the next loop. if (count == 0) { this->rx_pending_ = false; } @@ -81,7 +97,7 @@ void TcpUart::loop() { #ifdef USE_SOCKET_TCP_LISTENER if (this->server_) { // link_was_up_ holds the accept until the previous drop's edge has run, - // so the sensor and the cleared RX buffer always see the disconnect. + // so the sensor sees the disconnect and the new session's edge clears RX. this->listener_.poll(this->link_, !this->link_was_up_); } else { this->link_.poll(); diff --git a/esphome/components/tcp_uart/tcp_uart.h b/esphome/components/tcp_uart/tcp_uart.h index 29f7af564a6..1e1d48e4c20 100644 --- a/esphome/components/tcp_uart/tcp_uart.h +++ b/esphome/components/tcp_uart/tcp_uart.h @@ -1,6 +1,9 @@ #pragma once #include "esphome/components/binary_sensor/binary_sensor.h" +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif #include "esphome/components/socket/tcp_client_link.h" #ifdef USE_SOCKET_TCP_LISTENER #include "esphome/components/socket/tcp_listener.h" @@ -21,6 +24,9 @@ class TcpUart : public uart::UARTComponent, public Component { void set_port(uint16_t port) { this->link_.set_port(port); } void set_reconnect_interval(uint32_t ms) { this->link_.set_reconnect_interval(ms); } void set_connected_sensor(binary_sensor::BinarySensor *sensor) { this->connected_sensor_ = sensor; } +#ifdef USE_SENSOR + void set_disconnects_sensor(sensor::Sensor *sensor) { this->disconnects_sensor_ = sensor; } +#endif #ifdef USE_SOCKET_TCP_LISTENER void set_server(bool server) { this->server_ = server; } #ifdef USE_SOCKET_IPV4_ALLOW @@ -44,6 +50,7 @@ class TcpUart : public uart::UARTComponent, public Component { bool is_connected() override { return this->link_.connected(); } #if defined(USE_ESP8266) || defined(USE_ESP32) void load_settings(bool dump_config) override {} + using UARTComponent::load_settings; // also bring in the no-arg overload for convenience #endif protected: @@ -58,12 +65,16 @@ class TcpUart : public uart::UARTComponent, public Component { socket::TcpListener listener_; #endif binary_sensor::BinarySensor *connected_sensor_{nullptr}; +#ifdef USE_SENSOR + sensor::Sensor *disconnects_sensor_{nullptr}; + uint32_t disconnects_{0}; +#endif uint32_t last_drop_log_ms_{0}; // rx_[rx_start_, rx_end_) holds unread bytes; read_socket_() compacts to the front. uint16_t rx_start_{0}; uint16_t rx_end_{0}; bool server_{false}; - // The link state loop() saw last; edges clear rx_ and publish the sensor. + // The link state loop() saw last; edges publish the sensor, the up edge clears rx_. bool link_was_up_{false}; // A read stopped before EAGAIN. ready() stays false until new data arrives. bool rx_pending_{false}; diff --git a/esphome/components/tcs34725/__init__.py b/esphome/components/tcs34725/__init__.py index e69de29bb2d..cf99348a86a 100644 --- a/esphome/components/tcs34725/__init__.py +++ b/esphome/components/tcs34725/__init__.py @@ -0,0 +1 @@ +DOMAIN = "tcs34725" diff --git a/esphome/components/tcs34725/tcs34725.cpp b/esphome/components/tcs34725/tcs34725.cpp index b5853927900..8e79ac1c162 100644 --- a/esphome/components/tcs34725/tcs34725.cpp +++ b/esphome/components/tcs34725/tcs34725.cpp @@ -6,7 +6,7 @@ namespace esphome::tcs34725 { -static const char *const TAG = "tcs34725"; +ESPHOME_LOG_TAG(TAG, "tcs34725"); static const uint8_t TCS34725_ADDRESS = 0x29; static const uint8_t TCS34725_COMMAND_BIT = 0x80; diff --git a/esphome/components/tee501/__init__.py b/esphome/components/tee501/__init__.py index e69de29bb2d..d3790674e22 100644 --- a/esphome/components/tee501/__init__.py +++ b/esphome/components/tee501/__init__.py @@ -0,0 +1 @@ +DOMAIN = "tee501" diff --git a/esphome/components/tee501/tee501.cpp b/esphome/components/tee501/tee501.cpp index c198ff10813..74906ba5c45 100644 --- a/esphome/components/tee501/tee501.cpp +++ b/esphome/components/tee501/tee501.cpp @@ -4,7 +4,7 @@ namespace esphome::tee501 { -static const char *const TAG = "tee501"; +ESPHOME_LOG_TAG(TAG, "tee501"); static constexpr size_t TEE501_SERIAL_NUMBER_SIZE = 7; diff --git a/esphome/components/teleinfo/__init__.py b/esphome/components/teleinfo/__init__.py index 67aad11d0f1..8caf0202129 100644 --- a/esphome/components/teleinfo/__init__.py +++ b/esphome/components/teleinfo/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_ID from esphome.types import ConfigType CODEOWNERS = ["@0hax"] +DOMAIN = "teleinfo" MULTI_CONF = True teleinfo_ns = cg.esphome_ns.namespace("teleinfo") diff --git a/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp b/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp index 3878a3967b2..443d4be8a10 100644 --- a/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp +++ b/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::teleinfo { -static const char *const TAG = "teleinfo_sensor"; +ESPHOME_LOG_TAG(TAG, "teleinfo_sensor"); TeleInfoSensor::TeleInfoSensor(const char *tag) { this->tag = std::string(tag); } void TeleInfoSensor::publish_val(const std::string &val) { auto newval = parse_number(val).value_or(0.0f); diff --git a/esphome/components/teleinfo/teleinfo.cpp b/esphome/components/teleinfo/teleinfo.cpp index 17d3d6c0999..6b68dacd31b 100644 --- a/esphome/components/teleinfo/teleinfo.cpp +++ b/esphome/components/teleinfo/teleinfo.cpp @@ -3,7 +3,7 @@ namespace esphome::teleinfo { -static const char *const TAG = "teleinfo"; +ESPHOME_LOG_TAG(TAG, "teleinfo"); /* Helpers */ static int get_field(char *dest, char *buf_start, char *buf_end, int sep, int max_len) { diff --git a/esphome/components/teleinfo/text_sensor/teleinfo_text_sensor.cpp b/esphome/components/teleinfo/text_sensor/teleinfo_text_sensor.cpp index 7c638d8545b..a2646e5bb7b 100644 --- a/esphome/components/teleinfo/text_sensor/teleinfo_text_sensor.cpp +++ b/esphome/components/teleinfo/text_sensor/teleinfo_text_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::teleinfo { -static const char *const TAG = "teleinfo_text_sensor"; +ESPHOME_LOG_TAG(TAG, "teleinfo_text_sensor"); TeleInfoTextSensor::TeleInfoTextSensor(const char *tag) { this->tag = std::string(tag); } void TeleInfoTextSensor::publish_val(const std::string &val) { publish_state(val); } void TeleInfoTextSensor::dump_config() { LOG_TEXT_SENSOR(" ", "Teleinfo Text Sensor", this); } diff --git a/esphome/components/tem3200/__init__.py b/esphome/components/tem3200/__init__.py index e69de29bb2d..728db67d549 100644 --- a/esphome/components/tem3200/__init__.py +++ b/esphome/components/tem3200/__init__.py @@ -0,0 +1 @@ +DOMAIN = "tem3200" diff --git a/esphome/components/tem3200/tem3200.cpp b/esphome/components/tem3200/tem3200.cpp index 72cf31e0a67..e964814bbb9 100644 --- a/esphome/components/tem3200/tem3200.cpp +++ b/esphome/components/tem3200/tem3200.cpp @@ -5,7 +5,7 @@ namespace esphome::tem3200 { -static const char *const TAG = "tem3200"; +ESPHOME_LOG_TAG(TAG, "tem3200"); enum ErrorCode { NONE = 0, diff --git a/esphome/components/template/__init__.py b/esphome/components/template/__init__.py index 6253af9090f..542e78809c7 100644 --- a/esphome/components/template/__init__.py +++ b/esphome/components/template/__init__.py @@ -1,3 +1,5 @@ import esphome.codegen as cg +DOMAIN = "template" + template_ns = cg.esphome_ns.namespace("template_") diff --git a/esphome/components/template/alarm_control_panel/__init__.py b/esphome/components/template/alarm_control_panel/__init__.py index 59624a5f53f..a708c83d09f 100644 --- a/esphome/components/template/alarm_control_panel/__init__.py +++ b/esphome/components/template/alarm_control_panel/__init__.py @@ -117,8 +117,13 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): var = await alarm_control_panel.new_alarm_control_panel(config) await cg.register_component(var, config) - if CONF_CODES in config: - cg.add(var.set_codes(config[CONF_CODES])) + # Shared flash table ended by nullptr, so the panel stores only a pointer. + # requires_code_to_arm needs codes (validate_config) and defaults to false. + if codes := config.get(CONF_CODES): + table = cg.shared_progmem_array( + "alarm_codes", cg.const_char_ptr, [*codes, cg.nullptr] + ) + cg.add(var.set_codes(table)) if CONF_REQUIRES_CODE_TO_ARM in config: cg.add(var.set_requires_code_to_arm(config[CONF_REQUIRES_CODE_TO_ARM])) diff --git a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp index a224ab84599..12ff0b5a4b4 100644 --- a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +++ b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp @@ -11,7 +11,7 @@ namespace esphome::template_ { using namespace esphome::alarm_control_panel; -static const char *const TAG = "template.alarm_control_panel"; +ESPHOME_LOG_TAG(TAG, "template.alarm_control_panel"); TemplateAlarmControlPanel::TemplateAlarmControlPanel(){}; @@ -36,6 +36,11 @@ static const LogString *sensor_type_to_string(AlarmSensorType type) { #endif void TemplateAlarmControlPanel::dump_config() { + size_t num_codes = 0; + if (this->codes_ != nullptr) { + while (this->codes_[num_codes] != nullptr) + num_codes++; + } ESP_LOGCONFIG(TAG, "TemplateAlarmControlPanel:\n" " Current State: %s\n" @@ -47,8 +52,8 @@ void TemplateAlarmControlPanel::dump_config() { " Pending Time: %" PRIu32 "s\n" " Trigger Time: %" PRIu32 "s\n" " Supported Features: %" PRIu32, - LOG_STR_ARG(alarm_control_panel_state_to_string(this->current_state_)), this->codes_.size(), - YESNO(!this->codes_.empty() && this->requires_code_to_arm_), (this->arming_away_time_ / 1000), + LOG_STR_ARG(alarm_control_panel_state_to_string(this->current_state_)), num_codes, + YESNO(this->codes_ != nullptr && this->requires_code_to_arm_), (this->arming_away_time_ / 1000), (this->arming_home_time_ / 1000), (this->arming_night_time_ / 1000), (this->pending_time_ / 1000), (this->trigger_time_ / 1000), this->get_supported_features()); #ifdef USE_BINARY_SENSOR @@ -194,13 +199,12 @@ void TemplateAlarmControlPanel::loop() { } bool TemplateAlarmControlPanel::is_code_valid_(optional code) { - if (!this->codes_.empty()) { + if (this->codes_ != nullptr) { if (code.has_value()) { ESP_LOGVV(TAG, "Checking code: %s", code.value().c_str()); - // Use strcmp for const char* comparison const char *code_cstr = code.value().c_str(); - for (const char *stored_code : this->codes_) { - if (strcmp(stored_code, code_cstr) == 0) + for (const char *const *stored = this->codes_; *stored != nullptr; stored++) { + if (strcmp(*stored, code_cstr) == 0) return true; } return false; diff --git a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h index 5888ce5e29b..c977da10a9b 100644 --- a/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h +++ b/esphome/components/template/alarm_control_panel/template_alarm_control_panel.h @@ -61,7 +61,7 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl void setup() override; void loop() override; uint32_t get_supported_features() const override; - bool get_requires_code() const override { return !this->codes_.empty(); } + bool get_requires_code() const override { return this->codes_ != nullptr; } bool get_requires_code_to_arm() const override { return this->requires_code_to_arm_; } bool get_all_sensors_ready() { return this->sensors_ready_; }; void set_restore_mode(TemplateAlarmControlPanelRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } @@ -83,14 +83,11 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl AlarmSensorType type = ALARM_SENSOR_TYPE_DELAYED); #endif - /** Set the codes (from initializer list). + /** Set the codes. * - * @param codes The list of valid codes + * @param codes nullptr-terminated table of valid codes; must outlive the panel */ - void set_codes(std::initializer_list codes) { this->codes_ = codes; } - - // Deleted overload to catch incorrect std::string usage at compile time - void set_codes(std::initializer_list codes) = delete; + void set_codes(const char *const *codes) { this->codes_ = codes; } /** set requires a code to arm * @@ -150,8 +147,8 @@ class TemplateAlarmControlPanel final : public alarm_control_panel::AlarmControl uint32_t pending_time_; // the time in trigger uint32_t trigger_time_; - // a list of codes (const char* pointers to string literals in flash) - FixedVector codes_; + // nullptr-terminated table of codes, shared in flash + const char *const *codes_{nullptr}; // requires a code to arm bool requires_code_to_arm_ = false; bool supports_arm_home_ = false; diff --git a/esphome/components/template/binary_sensor/template_binary_sensor.cpp b/esphome/components/template/binary_sensor/template_binary_sensor.cpp index b63121d2dbc..8ce51aa4ca8 100644 --- a/esphome/components/template/binary_sensor/template_binary_sensor.cpp +++ b/esphome/components/template/binary_sensor/template_binary_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::template_ { -static const char *const TAG = "template.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "template.binary_sensor"); void TemplateBinarySensor::setup() { if (!this->f_.has_value()) { diff --git a/esphome/components/template/climate/template_climate.cpp b/esphome/components/template/climate/template_climate.cpp index a7a4d2ccabf..a731f037e32 100644 --- a/esphome/components/template/climate/template_climate.cpp +++ b/esphome/components/template/climate/template_climate.cpp @@ -3,7 +3,7 @@ namespace esphome::template_ { -static const char *const TAG = "template.climate"; +ESPHOME_LOG_TAG(TAG, "template.climate"); void TemplateClimate::setup() { if (this->restore_mode_ == TemplateClimateRestoreMode::TEMPLATE_CLIMATE_RESTORE_MODE_RESTORE) { diff --git a/esphome/components/template/cover/template_cover.cpp b/esphome/components/template/cover/template_cover.cpp index 1bf057da5b5..88a0af10b5c 100644 --- a/esphome/components/template/cover/template_cover.cpp +++ b/esphome/components/template/cover/template_cover.cpp @@ -5,7 +5,7 @@ namespace esphome::template_ { using namespace esphome::cover; -static const char *const TAG = "template.cover"; +ESPHOME_LOG_TAG(TAG, "template.cover"); TemplateCover::TemplateCover() = default; void TemplateCover::setup() { diff --git a/esphome/components/template/datetime/template_date.cpp b/esphome/components/template/datetime/template_date.cpp index c0f5d96c3da..4ae7429e96a 100644 --- a/esphome/components/template/datetime/template_date.cpp +++ b/esphome/components/template/datetime/template_date.cpp @@ -6,7 +6,7 @@ namespace esphome::template_ { -static const char *const TAG = "template.date"; +ESPHOME_LOG_TAG(TAG, "template.date"); void TemplateDate::setup() { if (this->f_.has_value()) diff --git a/esphome/components/template/datetime/template_datetime.cpp b/esphome/components/template/datetime/template_datetime.cpp index 5b8b308c008..0776a6968a2 100644 --- a/esphome/components/template/datetime/template_datetime.cpp +++ b/esphome/components/template/datetime/template_datetime.cpp @@ -6,7 +6,7 @@ namespace esphome::template_ { -static const char *const TAG = "template.datetime"; +ESPHOME_LOG_TAG(TAG, "template.datetime"); void TemplateDateTime::setup() { if (this->f_.has_value()) diff --git a/esphome/components/template/datetime/template_time.cpp b/esphome/components/template/datetime/template_time.cpp index b5efa62ae78..fd676e66a6e 100644 --- a/esphome/components/template/datetime/template_time.cpp +++ b/esphome/components/template/datetime/template_time.cpp @@ -6,7 +6,7 @@ namespace esphome::template_ { -static const char *const TAG = "template.time"; +ESPHOME_LOG_TAG(TAG, "template.time"); void TemplateTime::setup() { if (this->f_.has_value()) diff --git a/esphome/components/template/fan/template_fan.cpp b/esphome/components/template/fan/template_fan.cpp index 431be846544..93b02f12453 100644 --- a/esphome/components/template/fan/template_fan.cpp +++ b/esphome/components/template/fan/template_fan.cpp @@ -3,7 +3,7 @@ namespace esphome::template_ { -static const char *const TAG = "template.fan"; +ESPHOME_LOG_TAG(TAG, "template.fan"); void TemplateFan::setup() { // Construct traits before restore so preset modes can be looked up by index diff --git a/esphome/components/template/lock/template_lock.cpp b/esphome/components/template/lock/template_lock.cpp index 4a293aab858..54e4e49ce93 100644 --- a/esphome/components/template/lock/template_lock.cpp +++ b/esphome/components/template/lock/template_lock.cpp @@ -5,7 +5,7 @@ namespace esphome::template_ { using namespace esphome::lock; -static const char *const TAG = "template.lock"; +ESPHOME_LOG_TAG(TAG, "template.lock"); TemplateLock::TemplateLock() = default; diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp index 64c2deb281a..93eb42708ec 100644 --- a/esphome/components/template/number/template_number.cpp +++ b/esphome/components/template/number/template_number.cpp @@ -3,7 +3,7 @@ namespace esphome::template_ { -static const char *const TAG = "template.number"; +ESPHOME_LOG_TAG(TAG, "template.number"); void TemplateNumber::setup() { if (this->f_.has_value()) diff --git a/esphome/components/template/select/template_select.cpp b/esphome/components/template/select/template_select.cpp index e68729c2d4e..8c46b3a58c9 100644 --- a/esphome/components/template/select/template_select.cpp +++ b/esphome/components/template/select/template_select.cpp @@ -3,7 +3,7 @@ namespace esphome::template_ { -static const char *const TAG = "template.select"; +ESPHOME_LOG_TAG(TAG, "template.select"); void dump_config_helper(BaseTemplateSelect *sel_comp, bool optimistic, bool has_lambda, const size_t initial_option_index, bool restore_value) { diff --git a/esphome/components/template/sensor/template_sensor.cpp b/esphome/components/template/sensor/template_sensor.cpp index 313a163e389..cc12d7bbeca 100644 --- a/esphome/components/template/sensor/template_sensor.cpp +++ b/esphome/components/template/sensor/template_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::template_ { -static const char *const TAG = "template.sensor"; +ESPHOME_LOG_TAG(TAG, "template.sensor"); void TemplateSensor::update() { if (!this->f_.has_value()) diff --git a/esphome/components/template/switch/template_switch.cpp b/esphome/components/template/switch/template_switch.cpp index 729db370531..456e93d13dd 100644 --- a/esphome/components/template/switch/template_switch.cpp +++ b/esphome/components/template/switch/template_switch.cpp @@ -3,7 +3,7 @@ namespace esphome::template_ { -static const char *const TAG = "template.switch"; +ESPHOME_LOG_TAG(TAG, "template.switch"); TemplateSwitch::TemplateSwitch() = default; diff --git a/esphome/components/template/text/template_text.cpp b/esphome/components/template/text/template_text.cpp index af134e6ed4a..d892afade4d 100644 --- a/esphome/components/template/text/template_text.cpp +++ b/esphome/components/template/text/template_text.cpp @@ -3,7 +3,7 @@ namespace esphome::template_ { -static const char *const TAG = "template.text"; +ESPHOME_LOG_TAG(TAG, "template.text"); void TemplateText::setup() { if (this->f_.has_value()) diff --git a/esphome/components/template/text_sensor/template_text_sensor.cpp b/esphome/components/template/text_sensor/template_text_sensor.cpp index 89a15b6081e..38de4ea8de9 100644 --- a/esphome/components/template/text_sensor/template_text_sensor.cpp +++ b/esphome/components/template/text_sensor/template_text_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::template_ { -static const char *const TAG = "template.text_sensor"; +ESPHOME_LOG_TAG(TAG, "template.text_sensor"); void TemplateTextSensor::update() { if (!this->f_.has_value()) diff --git a/esphome/components/template/time/template_real_time_clock.cpp b/esphome/components/template/time/template_real_time_clock.cpp index 4b523def251..4e8a272285d 100644 --- a/esphome/components/template/time/template_real_time_clock.cpp +++ b/esphome/components/template/time/template_real_time_clock.cpp @@ -2,7 +2,7 @@ namespace esphome::template_ { -static const char *const TAG = "template.time"; +ESPHOME_LOG_TAG(TAG, "template.time"); time_t TemplateRealTimeClock::timestamp_now() { auto val = this->f_.call(); diff --git a/esphome/components/template/valve/template_valve.cpp b/esphome/components/template/valve/template_valve.cpp index 50906876399..77e65f04570 100644 --- a/esphome/components/template/valve/template_valve.cpp +++ b/esphome/components/template/valve/template_valve.cpp @@ -5,7 +5,7 @@ namespace esphome::template_ { using namespace esphome::valve; -static const char *const TAG = "template.valve"; +ESPHOME_LOG_TAG(TAG, "template.valve"); TemplateValve::TemplateValve() = default; diff --git a/esphome/components/template/water_heater/template_water_heater.cpp b/esphome/components/template/water_heater/template_water_heater.cpp index 7718688d68a..0a71225de30 100644 --- a/esphome/components/template/water_heater/template_water_heater.cpp +++ b/esphome/components/template/water_heater/template_water_heater.cpp @@ -5,7 +5,7 @@ namespace esphome::template_ { -static const char *const TAG = "template.water_heater"; +ESPHOME_LOG_TAG(TAG, "template.water_heater"); TemplateWaterHeater::TemplateWaterHeater() = default; diff --git a/esphome/components/text/__init__.py b/esphome/components/text/__init__.py index 2f82f19ff54..fbbc09bd2d1 100644 --- a/esphome/components/text/__init__.py +++ b/esphome/components/text/__init__.py @@ -23,6 +23,7 @@ from esphome.cpp_generator import MockObj, MockObjClass from esphome.types import ConfigType CODEOWNERS = ["@mauritskorse"] +DOMAIN = "text" IS_PLATFORM_COMPONENT = True text_ns = cg.esphome_ns.namespace("text") diff --git a/esphome/components/text/text.cpp b/esphome/components/text/text.cpp index a1df6286c70..58ab300b904 100644 --- a/esphome/components/text/text.cpp +++ b/esphome/components/text/text.cpp @@ -6,7 +6,7 @@ namespace esphome::text { -static const char *const TAG = "text"; +ESPHOME_LOG_TAG(TAG, "text"); void Text::publish_state(const char *state, size_t len) { this->set_has_state(true); diff --git a/esphome/components/text/text_call.cpp b/esphome/components/text/text_call.cpp index b7692658af1..93f0610d7d6 100644 --- a/esphome/components/text/text_call.cpp +++ b/esphome/components/text/text_call.cpp @@ -4,7 +4,7 @@ namespace esphome::text { -static const char *const TAG = "text"; +ESPHOME_LOG_TAG(TAG, "text"); TextCall &TextCall::set_value(const std::string &value) { this->value_ = value; diff --git a/esphome/components/text/text_sensor/text_text_sensor.cpp b/esphome/components/text/text_sensor/text_text_sensor.cpp index 50504c605ea..6574141e9f8 100644 --- a/esphome/components/text/text_sensor/text_text_sensor.cpp +++ b/esphome/components/text/text_sensor/text_text_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::text { -static const char *const TAG = "text.text_sensor"; +ESPHOME_LOG_TAG(TAG, "text.text_sensor"); void TextTextSensor::setup() { this->source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); }); diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index 08870db64aa..e9c70f7a2e3 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -39,6 +39,7 @@ DEVICE_CLASSES = [ ] +DOMAIN = "text_sensor" IS_PLATFORM_COMPONENT = True text_sensor_ns = cg.esphome_ns.namespace("text_sensor") diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp index d4e6b5b9bbf..9d18fb9b1a6 100644 --- a/esphome/components/text_sensor/filter.cpp +++ b/esphome/components/text_sensor/filter.cpp @@ -8,7 +8,7 @@ namespace esphome::text_sensor { -static const char *const TAG = "text_sensor.filter"; +ESPHOME_LOG_TAG(TAG, "text_sensor.filter"); // Filter void Filter::input(std::string value) { diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 17c606d2530..af4c5fd0816 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -6,7 +6,7 @@ namespace esphome::text_sensor { -static const char *const TAG = "text_sensor"; +ESPHOME_LOG_TAG(TAG, "text_sensor"); void log_text_sensor(const char *tag, const char *prefix, const char *type, TextSensor *obj) { if (obj == nullptr) { diff --git a/esphome/components/tfluna/__init__.py b/esphome/components/tfluna/__init__.py index 6ba821b1ab8..6914894ba78 100644 --- a/esphome/components/tfluna/__init__.py +++ b/esphome/components/tfluna/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@candrews"] DEPENDENCIES = ["i2c"] +DOMAIN = "tfluna" MULTI_CONF = True tfluna_ns = cg.esphome_ns.namespace("tfluna") diff --git a/esphome/components/tfluna/tfluna.cpp b/esphome/components/tfluna/tfluna.cpp index 1466ba89470..e3d7045d8e6 100644 --- a/esphome/components/tfluna/tfluna.cpp +++ b/esphome/components/tfluna/tfluna.cpp @@ -16,8 +16,10 @@ static constexpr uint8_t MODE_TRIGGER = 0x01; static constexpr uint8_t TRIGGER_ONESHOT_REGISTER = 0x24; static constexpr uint8_t MAX_READ_ATTEMPTS = 5; static constexpr uint8_t READ_RETRY_MS = 5; -static const char *const TAG = "tfluna"; -static const char *const READ_RETRY_TIMEOUT = "read_retry"; +ESPHOME_LOG_TAG(TAG, "tfluna"); + +static constexpr uint32_t SETUP_TIMEOUT_ID = 0; +static constexpr uint32_t READ_RETRY_TIMEOUT_ID = 1; void TFLuna::dump_config() { ESP_LOGCONFIG(TAG, "TF-Luna (i2c):"); @@ -121,7 +123,7 @@ void TFLuna::read_data_timeout_() { } else { if (this->attempt_ < MAX_READ_ATTEMPTS) { this->attempt_++; - this->set_timeout(READ_RETRY_TIMEOUT, READ_RETRY_MS, [this]() { this->read_data_timeout_(); }); + this->set_timeout(READ_RETRY_TIMEOUT_ID, READ_RETRY_MS, [this]() { this->read_data_timeout_(); }); } else { this->status_set_warning("Hung device, restarting..."); this->restart(); @@ -130,7 +132,7 @@ void TFLuna::read_data_timeout_() { } void TFLuna::update() { - this->cancel_timeout(READ_RETRY_TIMEOUT); + this->cancel_timeout(READ_RETRY_TIMEOUT_ID); this->attempt_ = 0; if (!this->write_byte(TRIGGER_ONESHOT_REGISTER, 0x01)) { this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); @@ -145,7 +147,7 @@ void TFLuna::factory_reset() { return; } this->status_set_warning("Factory reset issued; waiting for device to become ready"); - this->set_timeout("_setup", 100, [this]() { this->setup(); }); + this->set_timeout(SETUP_TIMEOUT_ID, 100, [this]() { this->setup(); }); } void TFLuna::restart() { diff --git a/esphome/components/thermopro_ble/__init__.py b/esphome/components/thermopro_ble/__init__.py index e69de29bb2d..f2f59ccbc2e 100644 --- a/esphome/components/thermopro_ble/__init__.py +++ b/esphome/components/thermopro_ble/__init__.py @@ -0,0 +1 @@ +DOMAIN = "thermopro_ble" diff --git a/esphome/components/thermopro_ble/thermopro_ble.cpp b/esphome/components/thermopro_ble/thermopro_ble.cpp index d10a6c33cd0..38a58137aec 100644 --- a/esphome/components/thermopro_ble/thermopro_ble.cpp +++ b/esphome/components/thermopro_ble/thermopro_ble.cpp @@ -19,7 +19,7 @@ static optional parse_tp972(const uint8_t *data, std::size_t data_s static optional parse_tp96(const uint8_t *data, std::size_t data_size); static optional parse_tp3(const uint8_t *data, std::size_t data_size); -static const char *const TAG = "thermopro_ble"; +ESPHOME_LOG_TAG(TAG, "thermopro_ble"); static const struct DeviceParserMapping DEVICE_PARSER_MAP[] = { {"TP972", parse_tp972}, {"TP970", parse_tp96}, {"TP96", parse_tp96}, {"TP3", parse_tp3}}; diff --git a/esphome/components/thermostat/__init__.py b/esphome/components/thermostat/__init__.py index e69de29bb2d..7bed41dc56b 100644 --- a/esphome/components/thermostat/__init__.py +++ b/esphome/components/thermostat/__init__.py @@ -0,0 +1 @@ +DOMAIN = "thermostat" diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index f64673e13fa..abfe1b027a5 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -6,7 +6,7 @@ namespace esphome::thermostat { -static const char *const TAG = "thermostat.climate"; +ESPHOME_LOG_TAG(TAG, "thermostat.climate"); void ThermostatClimate::setup() { if (this->use_startup_delay_) { diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index cedb9b3c9b5..a017109e7d3 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -46,7 +46,6 @@ DOMAIN = "time" time_ns = cg.esphome_ns.namespace("time") RealTimeClock = time_ns.class_("RealTimeClock", cg.PollingComponent) CronTrigger = time_ns.class_("CronTrigger", automation.Trigger.template(), cg.Component) -SyncTrigger = time_ns.class_("SyncTrigger", automation.Trigger.template(), cg.Component) # C++ types for pre-parsed timezone struct generation DSTRuleType_cpp = time_ns.enum("DSTRuleType", is_class=True) @@ -357,11 +356,7 @@ TIME_SCHEMA = cv.Schema( }, validate_cron_keys, ), - cv.Optional(CONF_ON_TIME_SYNC): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SyncTrigger), - } - ), + cv.Optional(CONF_ON_TIME_SYNC): automation.validate_automation({}), } ).extend( # ``visibility=ADVANCED`` flags the inherited ``update_interval`` @@ -424,8 +419,7 @@ async def setup_time_core_(time_var, config): _emit_parsed_timezone_fields(parsed) on_time = config.get(CONF_ON_TIME, []) - on_time_sync = config.get(CONF_ON_TIME_SYNC, []) - if on_time or on_time_sync: + if on_time: cg.add_define("USE_TIME_TRIGGERS") for conf in on_time: @@ -447,11 +441,10 @@ async def setup_time_core_(time_var, config): await cg.register_component(trigger, conf) await automation.build_automation(trigger, [], conf) - for conf in on_time_sync: - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], time_var) - - await cg.register_component(trigger, conf) - await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_TIME_SYNC, []): + await automation.build_callback_automation( + time_var, "add_on_time_sync_callback", [], conf + ) async def register_time(time_var, config): @@ -478,8 +471,8 @@ automation.register_apply_condition( # posix_tz.cpp is fully #ifdef'd on USE_TIME_TIMEZONE, set only when a -# timezone is configured or detected; automation.cpp holds the on_time and -# on_time_sync triggers and is #ifdef'd on USE_TIME_TRIGGERS. +# timezone is configured or detected; automation.cpp holds the on_time +# trigger and is #ifdef'd on USE_TIME_TRIGGERS. FILTER_SOURCE_FILES = filter_source_files_from_defines( { "posix_tz.cpp": "USE_TIME_TIMEZONE", diff --git a/esphome/components/time/automation.cpp b/esphome/components/time/automation.cpp index b91f8b03604..1c3d4625667 100644 --- a/esphome/components/time/automation.cpp +++ b/esphome/components/time/automation.cpp @@ -7,7 +7,7 @@ namespace esphome::time { -static const char *const TAG = "automation"; +ESPHOME_LOG_TAG(TAG, "automation"); static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider // there has been a drastic time synchronization @@ -94,10 +94,6 @@ void CronTrigger::add_days_of_week(const std::vector &days_of_week) { } float CronTrigger::get_setup_priority() const { return setup_priority::HARDWARE; } -SyncTrigger::SyncTrigger(RealTimeClock *rtc) : rtc_(rtc) { - rtc->add_on_time_sync_callback([this]() { this->trigger(); }); -} - } // namespace esphome::time #endif // USE_TIME_TRIGGERS diff --git a/esphome/components/time/automation.h b/esphome/components/time/automation.h index 5f980690a41..5e70000ac3f 100644 --- a/esphome/components/time/automation.h +++ b/esphome/components/time/automation.h @@ -43,14 +43,6 @@ class CronTrigger final : public Trigger<>, public Component { RealTimeClock *rtc_; optional last_check_; }; - -class SyncTrigger final : public Trigger<>, public Component { - public: - explicit SyncTrigger(RealTimeClock *rtc); - - protected: - RealTimeClock *rtc_; -}; } // namespace esphome::time #endif // USE_TIME_TRIGGERS diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 4f325605250..5f6b7d150f9 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -19,7 +19,7 @@ namespace esphome::time { -static const char *const TAG = "time"; +ESPHOME_LOG_TAG(TAG, "time"); RealTimeClock::RealTimeClock() = default; diff --git a/esphome/components/time_based/__init__.py b/esphome/components/time_based/__init__.py index ce2f453bda7..39ec2d9a8d5 100644 --- a/esphome/components/time_based/__init__.py +++ b/esphome/components/time_based/__init__.py @@ -1,3 +1,5 @@ import esphome.codegen as cg +DOMAIN = "time_based" + time_based_ns = cg.esphome_ns.namespace("time_based") diff --git a/esphome/components/time_based/cover/time_based_cover.cpp b/esphome/components/time_based/cover/time_based_cover.cpp index 613b190cf3c..4be933dd46c 100644 --- a/esphome/components/time_based/cover/time_based_cover.cpp +++ b/esphome/components/time_based/cover/time_based_cover.cpp @@ -5,7 +5,7 @@ namespace esphome::time_based { -static const char *const TAG = "time_based.cover"; +ESPHOME_LOG_TAG(TAG, "time_based.cover"); using namespace esphome::cover; diff --git a/esphome/components/tinyusb/__init__.py b/esphome/components/tinyusb/__init__.py index 1b819088cad..586b69d603b 100644 --- a/esphome/components/tinyusb/__init__.py +++ b/esphome/components/tinyusb/__init__.py @@ -10,12 +10,14 @@ from esphome.components.esp32 import ( add_idf_component, add_idf_sdkconfig_option, ) +from esphome.components.logger import DOMAIN as LOGGER_DOMAIN import esphome.config_validation as cv from esphome.const import CONF_HARDWARE_UART, CONF_ID from esphome.types import ConfigType CODEOWNERS = ["@kbx81"] CONFLICTS_WITH = ["usb_host"] +DOMAIN = "tinyusb" CONF_ON_MOUNT = "on_mount" CONF_ON_UNMOUNT = "on_unmount" @@ -95,7 +97,7 @@ def _final_validate(config: ConfigType) -> None: # tinyusb owns the USB OTG peripheral. The logger's USB_CDC backend routes # the ROM console through that same peripheral, so the two cannot coexist. # (USB_SERIAL_JTAG is a separate peripheral and is fine alongside tinyusb.) - logger_config = full_config.get("logger") + logger_config = full_config.get(LOGGER_DOMAIN) if logger_config and logger_config.get(CONF_HARDWARE_UART) == "USB_CDC": raise cv.Invalid( "'tinyusb' cannot be used with 'logger.hardware_uart: USB_CDC' " diff --git a/esphome/components/tinyusb/tinyusb_component.cpp b/esphome/components/tinyusb/tinyusb_component.cpp index 3fab9de0086..14291fb3284 100644 --- a/esphome/components/tinyusb/tinyusb_component.cpp +++ b/esphome/components/tinyusb/tinyusb_component.cpp @@ -7,7 +7,7 @@ namespace esphome::tinyusb { -static const char *const TAG = "tinyusb"; +ESPHOME_LOG_TAG(TAG, "tinyusb"); // Runs on the TinyUSB task: only wake the main loop, which reads the state and runs // the automations. diff --git a/esphome/components/tlc59208f/__init__.py b/esphome/components/tlc59208f/__init__.py index 1af9f66b957..5d719e40ba2 100644 --- a/esphome/components/tlc59208f/__init__.py +++ b/esphome/components/tlc59208f/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_ID from esphome.types import ConfigType DEPENDENCIES = ["i2c"] +DOMAIN = "tlc59208f" MULTI_CONF = True tlc59208f_ns = cg.esphome_ns.namespace("tlc59208f") diff --git a/esphome/components/tlc59208f/tlc59208f_output.cpp b/esphome/components/tlc59208f/tlc59208f_output.cpp index def337befc0..d76845b4f03 100644 --- a/esphome/components/tlc59208f/tlc59208f_output.cpp +++ b/esphome/components/tlc59208f/tlc59208f_output.cpp @@ -5,7 +5,7 @@ namespace esphome::tlc59208f { -static const char *const TAG = "tlc59208f"; +ESPHOME_LOG_TAG(TAG, "tlc59208f"); // * marks register defaults // 0*: Register auto increment disabled, 1: Register auto increment enabled diff --git a/esphome/components/tlc5947/__init__.py b/esphome/components/tlc5947/__init__.py index 05e2ef037dd..71247dd13ef 100644 --- a/esphome/components/tlc5947/__init__.py +++ b/esphome/components/tlc5947/__init__.py @@ -16,6 +16,7 @@ from esphome.types import ConfigType CONF_LAT_PIN = "lat_pin" CODEOWNERS = ["@rnauber"] +DOMAIN = "tlc5947" tlc5947_ns = cg.esphome_ns.namespace("tlc5947") TLC5947 = tlc5947_ns.class_("TLC5947", cg.Component) diff --git a/esphome/components/tlc5947/tlc5947.cpp b/esphome/components/tlc5947/tlc5947.cpp index f886118a088..5502679c9d8 100644 --- a/esphome/components/tlc5947/tlc5947.cpp +++ b/esphome/components/tlc5947/tlc5947.cpp @@ -3,7 +3,7 @@ namespace esphome::tlc5947 { -static const char *const TAG = "tlc5947"; +ESPHOME_LOG_TAG(TAG, "tlc5947"); void TLC5947::setup() { this->data_pin_->setup(); diff --git a/esphome/components/tlc5971/__init__.py b/esphome/components/tlc5971/__init__.py index 5b9027e61ac..26beec280e1 100644 --- a/esphome/components/tlc5971/__init__.py +++ b/esphome/components/tlc5971/__init__.py @@ -8,6 +8,7 @@ from esphome.const import CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_ID, CONF_NUM_CHIPS from esphome.types import ConfigType CODEOWNERS = ["@IJIJI"] +DOMAIN = "tlc5971" tlc5971_ns = cg.esphome_ns.namespace("tlc5971") TLC5971 = tlc5971_ns.class_("TLC5971", cg.Component) diff --git a/esphome/components/tlc5971/tlc5971.cpp b/esphome/components/tlc5971/tlc5971.cpp index 5818eace679..2c4ec567321 100644 --- a/esphome/components/tlc5971/tlc5971.cpp +++ b/esphome/components/tlc5971/tlc5971.cpp @@ -3,7 +3,7 @@ namespace esphome::tlc5971 { -static const char *const TAG = "tlc5971"; +ESPHOME_LOG_TAG(TAG, "tlc5971"); void TLC5971::setup() { this->data_pin_->setup(); diff --git a/esphome/components/tm1621/__init__.py b/esphome/components/tm1621/__init__.py index 2e88d4f3660..1b78f353cac 100644 --- a/esphome/components/tm1621/__init__.py +++ b/esphome/components/tm1621/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@Philippe12"] +DOMAIN = "tm1621" diff --git a/esphome/components/tm1621/tm1621.cpp b/esphome/components/tm1621/tm1621.cpp index 68d16e38116..5ca1c83c48e 100644 --- a/esphome/components/tm1621/tm1621.cpp +++ b/esphome/components/tm1621/tm1621.cpp @@ -5,7 +5,7 @@ namespace esphome::tm1621 { -static const char *const TAG = "tm1621"; +ESPHOME_LOG_TAG(TAG, "tm1621"); const uint8_t TM1621_PULSE_WIDTH = 10; // microseconds (Sonoff = 100) diff --git a/esphome/components/tm1637/__init__.py b/esphome/components/tm1637/__init__.py index e69de29bb2d..c8b08abf037 100644 --- a/esphome/components/tm1637/__init__.py +++ b/esphome/components/tm1637/__init__.py @@ -0,0 +1 @@ +DOMAIN = "tm1637" diff --git a/esphome/components/tm1637/tm1637.cpp b/esphome/components/tm1637/tm1637.cpp index a1604fa60e8..03eb427e76b 100644 --- a/esphome/components/tm1637/tm1637.cpp +++ b/esphome/components/tm1637/tm1637.cpp @@ -5,7 +5,7 @@ namespace esphome::tm1637 { -static const char *const TAG = "display.tm1637"; +ESPHOME_LOG_TAG(TAG, "display.tm1637"); const uint8_t TM1637_CMD_DATA = 0x40; //!< Display data command const uint8_t TM1637_CMD_CTRL = 0x80; //!< Display control command const uint8_t TM1637_CMD_ADDR = 0xc0; //!< Display address command diff --git a/esphome/components/tm1638/__init__.py b/esphome/components/tm1638/__init__.py index e69de29bb2d..4500a10ff43 100644 --- a/esphome/components/tm1638/__init__.py +++ b/esphome/components/tm1638/__init__.py @@ -0,0 +1 @@ +DOMAIN = "tm1638" diff --git a/esphome/components/tm1638/output/tm1638_output_led.cpp b/esphome/components/tm1638/output/tm1638_output_led.cpp index e32826fa930..8e2020459ce 100644 --- a/esphome/components/tm1638/output/tm1638_output_led.cpp +++ b/esphome/components/tm1638/output/tm1638_output_led.cpp @@ -3,7 +3,7 @@ namespace esphome::tm1638 { -static const char *const TAG = "tm1638.led"; +ESPHOME_LOG_TAG(TAG, "tm1638.led"); void TM1638OutputLed::write_state(bool state) { tm1638_->set_led(led_, state); } diff --git a/esphome/components/tm1638/switch/tm1638_switch_led.cpp b/esphome/components/tm1638/switch/tm1638_switch_led.cpp index 743d0af5075..f5573a35921 100644 --- a/esphome/components/tm1638/switch/tm1638_switch_led.cpp +++ b/esphome/components/tm1638/switch/tm1638_switch_led.cpp @@ -3,7 +3,7 @@ namespace esphome::tm1638 { -static const char *const TAG = "tm1638.led"; +ESPHOME_LOG_TAG(TAG, "tm1638.led"); void TM1638SwitchLed::write_state(bool state) { tm1638_->set_led(led_, state); diff --git a/esphome/components/tm1638/tm1638.cpp b/esphome/components/tm1638/tm1638.cpp index 1f0692479c2..ffb0ead0cd8 100644 --- a/esphome/components/tm1638/tm1638.cpp +++ b/esphome/components/tm1638/tm1638.cpp @@ -6,7 +6,7 @@ namespace esphome::tm1638 { -static const char *const TAG = "display.tm1638"; +ESPHOME_LOG_TAG(TAG, "display.tm1638"); static const uint8_t TM1638_REGISTER_FIXEDADDRESS = 0x44; static const uint8_t TM1638_REGISTER_AUTOADDRESS = 0x40; static const uint8_t TM1638_REGISTER_READBUTTONS = 0x42; diff --git a/esphome/components/tm1651/__init__.py b/esphome/components/tm1651/__init__.py index dfe475f7f2c..14aa1c8842b 100644 --- a/esphome/components/tm1651/__init__.py +++ b/esphome/components/tm1651/__init__.py @@ -12,6 +12,7 @@ from esphome.const import ( from esphome.types import ConfigType CODEOWNERS = ["@mrtoy-me"] +DOMAIN = "tm1651" CONF_LEVEL_PERCENT = "level_percent" diff --git a/esphome/components/tm1651/tm1651.cpp b/esphome/components/tm1651/tm1651.cpp index 282b0dcf76b..467695b4b43 100644 --- a/esphome/components/tm1651/tm1651.cpp +++ b/esphome/components/tm1651/tm1651.cpp @@ -53,7 +53,7 @@ namespace esphome::tm1651 { -static const char *const TAG = "tm1651.display"; +ESPHOME_LOG_TAG(TAG, "tm1651.display"); static const bool LINE_HIGH = true; static const bool LINE_LOW = false; diff --git a/esphome/components/tmp102/__init__.py b/esphome/components/tmp102/__init__.py index 3e32a230f28..8510f18769a 100644 --- a/esphome/components/tmp102/__init__.py +++ b/esphome/components/tmp102/__init__.py @@ -7,3 +7,5 @@ reading temperatures to a resolution of 0.0625°C. https://www.sparkfun.com/datasheets/Sensors/Temperature/tmp102.pdf """ + +DOMAIN = "tmp102" diff --git a/esphome/components/tmp102/tmp102.cpp b/esphome/components/tmp102/tmp102.cpp index cb2462858b6..d851bd925fa 100644 --- a/esphome/components/tmp102/tmp102.cpp +++ b/esphome/components/tmp102/tmp102.cpp @@ -4,7 +4,9 @@ namespace esphome::tmp102 { -static const char *const TAG = "tmp102"; +ESPHOME_LOG_TAG(TAG, "tmp102"); + +static constexpr uint32_t READ_TEMP_TIMEOUT_ID = 0; static const uint8_t TMP102_ADDRESS = 0x48; static const uint8_t TMP102_REGISTER_TEMPERATURE = 0x00; @@ -29,7 +31,7 @@ void TMP102Component::update() { this->status_set_warning(); return; } - this->set_timeout("read_temp", 50, [this]() { + this->set_timeout(READ_TEMP_TIMEOUT_ID, 50, [this]() { int16_t raw_temperature; if (this->read(reinterpret_cast(&raw_temperature), 2) != i2c::ERROR_OK) { this->status_set_warning(); diff --git a/esphome/components/tmp1075/__init__.py b/esphome/components/tmp1075/__init__.py index ddd04ad11a4..81c0e500337 100644 --- a/esphome/components/tmp1075/__init__.py +++ b/esphome/components/tmp1075/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@sybrenstuvel"] +DOMAIN = "tmp1075" diff --git a/esphome/components/tmp1075/tmp1075.cpp b/esphome/components/tmp1075/tmp1075.cpp index 681603d1136..9b152c1ad34 100644 --- a/esphome/components/tmp1075/tmp1075.cpp +++ b/esphome/components/tmp1075/tmp1075.cpp @@ -3,7 +3,7 @@ namespace esphome::tmp1075 { -static const char *const TAG = "tmp1075"; +ESPHOME_LOG_TAG(TAG, "tmp1075"); constexpr uint8_t REG_TEMP = 0x0; // Temperature result constexpr uint8_t REG_CFGR = 0x1; // Configuration diff --git a/esphome/components/tmp117/__init__.py b/esphome/components/tmp117/__init__.py index e69de29bb2d..e7429bd52e6 100644 --- a/esphome/components/tmp117/__init__.py +++ b/esphome/components/tmp117/__init__.py @@ -0,0 +1 @@ +DOMAIN = "tmp117" diff --git a/esphome/components/tmp117/tmp117.cpp b/esphome/components/tmp117/tmp117.cpp index b3e900f5b65..5925b297c2b 100644 --- a/esphome/components/tmp117/tmp117.cpp +++ b/esphome/components/tmp117/tmp117.cpp @@ -6,7 +6,7 @@ namespace esphome::tmp117 { -static const char *const TAG = "tmp117"; +ESPHOME_LOG_TAG(TAG, "tmp117"); void TMP117Component::update() { int16_t data; diff --git a/esphome/components/tof10120/__init__.py b/esphome/components/tof10120/__init__.py index e69de29bb2d..89b7cffed44 100644 --- a/esphome/components/tof10120/__init__.py +++ b/esphome/components/tof10120/__init__.py @@ -0,0 +1 @@ +DOMAIN = "tof10120" diff --git a/esphome/components/tof10120/tof10120_sensor.cpp b/esphome/components/tof10120/tof10120_sensor.cpp index 290bc76a521..3943b37150c 100644 --- a/esphome/components/tof10120/tof10120_sensor.cpp +++ b/esphome/components/tof10120/tof10120_sensor.cpp @@ -7,7 +7,7 @@ namespace esphome::tof10120 { -static const char *const TAG = "tof10120"; +ESPHOME_LOG_TAG(TAG, "tof10120"); static const uint8_t TOF10120_READ_DISTANCE_CMD[] = {0x00}; static const uint8_t TOF10120_DEFAULT_DELAY = 30; diff --git a/esphome/components/tormatic/__init__.py b/esphome/components/tormatic/__init__.py index 7f3f05a3cd8..e26792c1d33 100644 --- a/esphome/components/tormatic/__init__.py +++ b/esphome/components/tormatic/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@ti-mo"] +DOMAIN = "tormatic" diff --git a/esphome/components/tormatic/tormatic_cover.cpp b/esphome/components/tormatic/tormatic_cover.cpp index 5c8d6623b64..fe7b4cf112e 100644 --- a/esphome/components/tormatic/tormatic_cover.cpp +++ b/esphome/components/tormatic/tormatic_cover.cpp @@ -7,7 +7,7 @@ using namespace std; namespace esphome::tormatic { -static const char *const TAG = "tormatic.cover"; +ESPHOME_LOG_TAG(TAG, "tormatic.cover"); // Time to poll the UART when flushing after desync. At 9600 baud, a full // 12-byte message takes ~12.5ms, so 15ms guarantees all bytes have arrived. diff --git a/esphome/components/toshiba/__init__.py b/esphome/components/toshiba/__init__.py index e69de29bb2d..1e5ec1fc278 100644 --- a/esphome/components/toshiba/__init__.py +++ b/esphome/components/toshiba/__init__.py @@ -0,0 +1 @@ +DOMAIN = "toshiba" diff --git a/esphome/components/toshiba/toshiba.cpp b/esphome/components/toshiba/toshiba.cpp index 19950bbd159..f9f09a98bc4 100644 --- a/esphome/components/toshiba/toshiba.cpp +++ b/esphome/components/toshiba/toshiba.cpp @@ -11,7 +11,7 @@ struct RacPt1411hwruFanSpeed { uint8_t code2; }; -static const char *const TAG = "toshiba.climate"; +ESPHOME_LOG_TAG(TAG, "toshiba.climate"); // Timings for IR bits/data const uint16_t TOSHIBA_HEADER_MARK = 4380; const uint16_t TOSHIBA_HEADER_SPACE = 4370; diff --git a/esphome/components/total_daily_energy/__init__.py b/esphome/components/total_daily_energy/__init__.py index e69de29bb2d..d3880a5084f 100644 --- a/esphome/components/total_daily_energy/__init__.py +++ b/esphome/components/total_daily_energy/__init__.py @@ -0,0 +1 @@ +DOMAIN = "total_daily_energy" diff --git a/esphome/components/total_daily_energy/total_daily_energy.cpp b/esphome/components/total_daily_energy/total_daily_energy.cpp index 7c9dbb604f7..c8e6bdd04aa 100644 --- a/esphome/components/total_daily_energy/total_daily_energy.cpp +++ b/esphome/components/total_daily_energy/total_daily_energy.cpp @@ -4,7 +4,7 @@ namespace esphome::total_daily_energy { -static const char *const TAG = "total_daily_energy"; +ESPHOME_LOG_TAG(TAG, "total_daily_energy"); static constexpr uint32_t TIMEOUT_ID_MIDNIGHT = 1; static constexpr uint8_t SECONDS_PER_MINUTE = 60; static constexpr uint8_t MINUTES_PER_HOUR = 60; diff --git a/esphome/components/touchscreen/__init__.py b/esphome/components/touchscreen/__init__.py index c8b918007b3..7a23d140690 100644 --- a/esphome/components/touchscreen/__init__.py +++ b/esphome/components/touchscreen/__init__.py @@ -23,6 +23,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@jesserockz", "@nielsnl68"] DEPENDENCIES = ["display"] +DOMAIN = "touchscreen" IS_PLATFORM_COMPONENT = True diff --git a/esphome/components/touchscreen/touchscreen.cpp b/esphome/components/touchscreen/touchscreen.cpp index f4ef66ef3e0..7072b88d554 100644 --- a/esphome/components/touchscreen/touchscreen.cpp +++ b/esphome/components/touchscreen/touchscreen.cpp @@ -4,7 +4,8 @@ namespace esphome::touchscreen { -static const char *const TAG = "touchscreen"; +ESPHOME_LOG_TAG(TAG, "touchscreen"); +static constexpr uint32_t TOUCH_TIMEOUT_ID = 0; void TouchscreenInterrupt::gpio_intr(TouchscreenInterrupt *store) { store->touched = true; } @@ -63,9 +64,9 @@ void Touchscreen::loop() { // Simulate a touch after touch_timeout_> ms. This will reset any existing timeout operation. // This is to detect touch release. if (this->is_touched_) { - this->set_timeout(TAG, this->touch_timeout_, [this]() { this->store_.touched = true; }); + this->set_timeout(TOUCH_TIMEOUT_ID, this->touch_timeout_, [this]() { this->store_.touched = true; }); } else { - this->cancel_timeout(TAG); + this->cancel_timeout(TOUCH_TIMEOUT_ID); } } } diff --git a/esphome/components/tsl2561/__init__.py b/esphome/components/tsl2561/__init__.py index e69de29bb2d..9986b7fd3ef 100644 --- a/esphome/components/tsl2561/__init__.py +++ b/esphome/components/tsl2561/__init__.py @@ -0,0 +1 @@ +DOMAIN = "tsl2561" diff --git a/esphome/components/tsl2561/tsl2561.cpp b/esphome/components/tsl2561/tsl2561.cpp index 4e4d403488d..d1d1d66a265 100644 --- a/esphome/components/tsl2561/tsl2561.cpp +++ b/esphome/components/tsl2561/tsl2561.cpp @@ -3,7 +3,9 @@ namespace esphome::tsl2561 { -static const char *const TAG = "tsl2561"; +ESPHOME_LOG_TAG(TAG, "tsl2561"); + +static constexpr uint32_t READ_TIMEOUT_ID = 0; static const uint8_t TSL2561_COMMAND_BIT = 0x80; static const uint8_t TSL2561_WORD_BIT = 0x20; @@ -60,7 +62,7 @@ void TSL2561Sensor::update() { // Make sure the data is there when we will read it. auto timeout = static_cast(this->get_integration_time_ms_() + 20); - this->set_timeout("illuminance", timeout, [this]() { this->read_data_(); }); + this->set_timeout(READ_TIMEOUT_ID, timeout, [this]() { this->read_data_(); }); } float TSL2561Sensor::calculate_lx_(uint16_t ch0, uint16_t ch1) { diff --git a/esphome/components/tsl2591/__init__.py b/esphome/components/tsl2591/__init__.py index 63331641c5a..c611ad0ebda 100644 --- a/esphome/components/tsl2591/__init__.py +++ b/esphome/components/tsl2591/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@wjcarpenter"] +DOMAIN = "tsl2591" diff --git a/esphome/components/tsl2591/tsl2591.cpp b/esphome/components/tsl2591/tsl2591.cpp index d147aae88ab..f1f9ae9cfdb 100644 --- a/esphome/components/tsl2591/tsl2591.cpp +++ b/esphome/components/tsl2591/tsl2591.cpp @@ -4,7 +4,9 @@ namespace esphome::tsl2591 { -static const char *const TAG = "tsl2591.sensor"; +ESPHOME_LOG_TAG(TAG, "tsl2591.sensor"); + +static constexpr uint32_t ADC_WAIT_INTERVAL_ID = 0; // Various constants used in TSL2591 register manipulation #define TSL2591_COMMAND_BIT (0xA0) // 1010 0000: bits 7 and 5 for 'command, normal' @@ -163,19 +165,17 @@ void TSL2591Component::process_update_() { this->status_clear_warning(); } -#define interval_name "tsl2591_interval_for_update" - void TSL2591Component::interval_function_for_update_() { if (!this->is_adc_valid()) { uint64_t now = millis(); ESP_LOGD(TAG, "Elapsed %3llu ms; still waiting for valid ADC", (now - this->interval_start_)); if (now > this->interval_timeout_) { ESP_LOGW(TAG, "Interval timeout for '%s' expired before ADCs became valid", this->name_); - this->cancel_interval(interval_name); + this->cancel_interval(ADC_WAIT_INTERVAL_ID); } return; } - this->cancel_interval(interval_name); + this->cancel_interval(ADC_WAIT_INTERVAL_ID); this->process_update_(); } @@ -191,7 +191,7 @@ void TSL2591Component::update() { } else { this->interval_start_ = millis(); this->interval_timeout_ = this->interval_start_ + 620; - this->set_interval(interval_name, 100, [this] { this->interval_function_for_update_(); }); + this->set_interval(ADC_WAIT_INTERVAL_ID, 100, [this] { this->interval_function_for_update_(); }); } } } diff --git a/esphome/components/tt21100/__init__.py b/esphome/components/tt21100/__init__.py index a309d34bebd..a8f0ec0dd37 100644 --- a/esphome/components/tt21100/__init__.py +++ b/esphome/components/tt21100/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg CODEOWNERS = ["@kroimon"] +DOMAIN = "tt21100" tt21100_ns = cg.esphome_ns.namespace("tt21100") diff --git a/esphome/components/tt21100/binary_sensor/tt21100_button.cpp b/esphome/components/tt21100/binary_sensor/tt21100_button.cpp index ccf6e53d665..ea388703e08 100644 --- a/esphome/components/tt21100/binary_sensor/tt21100_button.cpp +++ b/esphome/components/tt21100/binary_sensor/tt21100_button.cpp @@ -3,7 +3,7 @@ namespace esphome::tt21100 { -static const char *const TAG = "tt21100.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "tt21100.binary_sensor"); void TT21100Button::setup() { this->parent_->register_button_listener(this); diff --git a/esphome/components/tt21100/touchscreen/tt21100.cpp b/esphome/components/tt21100/touchscreen/tt21100.cpp index 018094df737..b850dbc696a 100644 --- a/esphome/components/tt21100/touchscreen/tt21100.cpp +++ b/esphome/components/tt21100/touchscreen/tt21100.cpp @@ -3,7 +3,7 @@ namespace esphome::tt21100 { -static const char *const TAG = "tt21100"; +ESPHOME_LOG_TAG(TAG, "tt21100"); static const uint8_t MAX_BUTTONS = 4; static const uint8_t MAX_TOUCH_POINTS = 5; diff --git a/esphome/components/ttp229_bsf/__init__.py b/esphome/components/ttp229_bsf/__init__.py index 9e2a4ad114f..1b10940570b 100644 --- a/esphome/components/ttp229_bsf/__init__.py +++ b/esphome/components/ttp229_bsf/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_ID, CONF_SCL_PIN, CONF_SDO_PIN from esphome.types import ConfigType AUTO_LOAD = ["binary_sensor"] +DOMAIN = "ttp229_bsf" CONF_TTP229_ID = "ttp229_id" ttp229_bsf_ns = cg.esphome_ns.namespace("ttp229_bsf") diff --git a/esphome/components/ttp229_bsf/ttp229_bsf.cpp b/esphome/components/ttp229_bsf/ttp229_bsf.cpp index 1c7fa3531f0..807d7365572 100644 --- a/esphome/components/ttp229_bsf/ttp229_bsf.cpp +++ b/esphome/components/ttp229_bsf/ttp229_bsf.cpp @@ -3,7 +3,7 @@ namespace esphome::ttp229_bsf { -static const char *const TAG = "ttp229_bsf"; +ESPHOME_LOG_TAG(TAG, "ttp229_bsf"); void TTP229BSFComponent::setup() { this->sdo_pin_->setup(); diff --git a/esphome/components/ttp229_lsf/__init__.py b/esphome/components/ttp229_lsf/__init__.py index d32f72fcce9..e89b177a3ee 100644 --- a/esphome/components/ttp229_lsf/__init__.py +++ b/esphome/components/ttp229_lsf/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType DEPENDENCIES = ["i2c"] AUTO_LOAD = ["binary_sensor"] +DOMAIN = "ttp229_lsf" CONF_TTP229_ID = "ttp229_id" ttp229_lsf_ns = cg.esphome_ns.namespace("ttp229_lsf") diff --git a/esphome/components/ttp229_lsf/ttp229_lsf.cpp b/esphome/components/ttp229_lsf/ttp229_lsf.cpp index eaef33d7935..7ed06c6d6f2 100644 --- a/esphome/components/ttp229_lsf/ttp229_lsf.cpp +++ b/esphome/components/ttp229_lsf/ttp229_lsf.cpp @@ -3,7 +3,7 @@ namespace esphome::ttp229_lsf { -static const char *const TAG = "ttp229_lsf"; +ESPHOME_LOG_TAG(TAG, "ttp229_lsf"); void TTP229LSFComponent::setup() { uint8_t data[2]; diff --git a/esphome/components/tuya/__init__.py b/esphome/components/tuya/__init__.py index 2cc60e225c8..bf392c78268 100644 --- a/esphome/components/tuya/__init__.py +++ b/esphome/components/tuya/__init__.py @@ -2,9 +2,11 @@ from esphome import automation, pins import esphome.codegen as cg from esphome.components import time, uart import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT, CONF_TIME_ID, CONF_TRIGGER_ID +from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT, CONF_TIME_ID +from esphome.cpp_generator import MockObj DEPENDENCIES = ["uart"] +DOMAIN = "tuya" CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS = "ignore_mcu_update_on_datapoints" @@ -16,6 +18,8 @@ tuya_ns = cg.esphome_ns.namespace("tuya") TuyaDatapointType = tuya_ns.enum("TuyaDatapointType", is_class=True) Tuya = tuya_ns.class_("Tuya", cg.Component, uart.UARTDevice) +TuyaDatapoint = tuya_ns.struct("TuyaDatapoint") + DPTYPE_ANY = "any" DPTYPE_RAW = "raw" DPTYPE_BOOL = "bool" @@ -25,60 +29,18 @@ DPTYPE_STRING = "string" DPTYPE_ENUM = "enum" DPTYPE_BITMASK = "bitmask" +# Automation argument type, plus the expected TuyaDatapointType and the field forwarded DATAPOINT_TYPES = { - DPTYPE_ANY: tuya_ns.struct("TuyaDatapoint"), - DPTYPE_RAW: cg.std_vector.template(cg.uint8), - DPTYPE_BOOL: cg.bool_, - DPTYPE_INT: cg.int_, - DPTYPE_UINT: cg.uint32, - DPTYPE_STRING: cg.std_string, - DPTYPE_ENUM: cg.uint8, - DPTYPE_BITMASK: cg.uint32, + DPTYPE_ANY: (TuyaDatapoint, None, None), + DPTYPE_RAW: (cg.std_vector.template(cg.uint8), TuyaDatapointType.RAW, "value_raw"), + DPTYPE_BOOL: (cg.bool_, TuyaDatapointType.BOOLEAN, "value_bool"), + DPTYPE_INT: (cg.int_, TuyaDatapointType.INTEGER, "value_int"), + DPTYPE_UINT: (cg.uint32, TuyaDatapointType.INTEGER, "value_uint"), + DPTYPE_STRING: (cg.std_string, TuyaDatapointType.STRING, "value_string"), + DPTYPE_ENUM: (cg.uint8, TuyaDatapointType.ENUM, "value_enum"), + DPTYPE_BITMASK: (cg.uint32, TuyaDatapointType.BITMASK, "value_bitmask"), } -DATAPOINT_TRIGGERS = { - DPTYPE_ANY: tuya_ns.class_( - "TuyaDatapointUpdateTrigger", - automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_ANY]), - ), - DPTYPE_RAW: tuya_ns.class_( - "TuyaRawDatapointUpdateTrigger", - automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_RAW]), - ), - DPTYPE_BOOL: tuya_ns.class_( - "TuyaBoolDatapointUpdateTrigger", - automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_BOOL]), - ), - DPTYPE_INT: tuya_ns.class_( - "TuyaIntDatapointUpdateTrigger", - automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_INT]), - ), - DPTYPE_UINT: tuya_ns.class_( - "TuyaUIntDatapointUpdateTrigger", - automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_UINT]), - ), - DPTYPE_STRING: tuya_ns.class_( - "TuyaStringDatapointUpdateTrigger", - automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_STRING]), - ), - DPTYPE_ENUM: tuya_ns.class_( - "TuyaEnumDatapointUpdateTrigger", - automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_ENUM]), - ), - DPTYPE_BITMASK: tuya_ns.class_( - "TuyaBitmaskDatapointUpdateTrigger", - automation.Trigger.template(DATAPOINT_TYPES[DPTYPE_BITMASK]), - ), -} - - -def assign_declare_id(value): - value = value.copy() - value[CONF_TRIGGER_ID] = cv.declare_id( - DATAPOINT_TRIGGERS[value[CONF_DATAPOINT_TYPE]] - )(value[CONF_TRIGGER_ID].id) - return value - CONF_TUYA_ID = "tuya_id" CONFIG_SCHEMA = ( @@ -92,15 +54,11 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_STATUS_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_ON_DATAPOINT_UPDATE): automation.validate_automation( { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( - DATAPOINT_TRIGGERS[DPTYPE_ANY] - ), cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, cv.Optional(CONF_DATAPOINT_TYPE, default=DPTYPE_ANY): cv.one_of( - *DATAPOINT_TRIGGERS, lower=True + *DATAPOINT_TYPES, lower=True ), - }, - extra_validators=assign_declare_id, + } ), } ) @@ -123,9 +81,11 @@ async def to_code(config): for dp in config[CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS]: cg.add(var.add_ignore_mcu_update_on_datapoints(dp)) for conf in config.get(CONF_ON_DATAPOINT_UPDATE, []): - trigger = cg.new_Pvariable( - conf[CONF_TRIGGER_ID], var, conf[CONF_SENSOR_DATAPOINT] - ) - await automation.build_automation( - trigger, [(DATAPOINT_TYPES[conf[CONF_DATAPOINT_TYPE]], "x")], conf + type_, expected, field = DATAPOINT_TYPES[conf[CONF_DATAPOINT_TYPE]] + forward = None + if expected is not None: + forward = [getattr(MockObj("x", ".").expect_type(expected), field)] + callback = await automation.build_trigger_callback( + [(type_, "x")], conf, params=[(TuyaDatapoint, "x")], forward=forward ) + cg.add(var.register_listener(conf[CONF_SENSOR_DATAPOINT], callback)) diff --git a/esphome/components/tuya/automation.cpp b/esphome/components/tuya/automation.cpp deleted file mode 100644 index 5c84f36b832..00000000000 --- a/esphome/components/tuya/automation.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "esphome/core/log.h" - -#include "automation.h" - -static const char *const TAG = "tuya.automation"; - -namespace esphome::tuya { - -void check_expected_datapoint(const TuyaDatapoint &dp, TuyaDatapointType expected) { - if (dp.type != expected) { - ESP_LOGW(TAG, "Tuya sensor %u expected datapoint type %#02hhX but got %#02hhX", dp.id, - static_cast(expected), static_cast(dp.type)); - } -} - -TuyaRawDatapointUpdateTrigger::TuyaRawDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { - parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { - check_expected_datapoint(dp, TuyaDatapointType::RAW); - this->trigger(dp.value_raw); - }); -} - -TuyaBoolDatapointUpdateTrigger::TuyaBoolDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { - parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { - check_expected_datapoint(dp, TuyaDatapointType::BOOLEAN); - this->trigger(dp.value_bool); - }); -} - -TuyaIntDatapointUpdateTrigger::TuyaIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { - parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { - check_expected_datapoint(dp, TuyaDatapointType::INTEGER); - this->trigger(dp.value_int); - }); -} - -TuyaUIntDatapointUpdateTrigger::TuyaUIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { - parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { - check_expected_datapoint(dp, TuyaDatapointType::INTEGER); - this->trigger(dp.value_uint); - }); -} - -TuyaStringDatapointUpdateTrigger::TuyaStringDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { - parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { - check_expected_datapoint(dp, TuyaDatapointType::STRING); - this->trigger(dp.value_string); - }); -} - -TuyaEnumDatapointUpdateTrigger::TuyaEnumDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { - parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { - check_expected_datapoint(dp, TuyaDatapointType::ENUM); - this->trigger(dp.value_enum); - }); -} - -TuyaBitmaskDatapointUpdateTrigger::TuyaBitmaskDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { - parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { - check_expected_datapoint(dp, TuyaDatapointType::BITMASK); - this->trigger(dp.value_bitmask); - }); -} - -} // namespace esphome::tuya diff --git a/esphome/components/tuya/automation.h b/esphome/components/tuya/automation.h deleted file mode 100644 index 0cd63a76be7..00000000000 --- a/esphome/components/tuya/automation.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/core/automation.h" -#include "tuya.h" - -#include - -namespace esphome::tuya { - -class TuyaDatapointUpdateTrigger final : public Trigger { - public: - explicit TuyaDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id) { - parent->register_listener(sensor_id, [this](const TuyaDatapoint &dp) { this->trigger(dp); }); - } -}; - -class TuyaRawDatapointUpdateTrigger final : public Trigger> { - public: - explicit TuyaRawDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); -}; - -class TuyaBoolDatapointUpdateTrigger final : public Trigger { - public: - explicit TuyaBoolDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); -}; - -class TuyaIntDatapointUpdateTrigger final : public Trigger { - public: - explicit TuyaIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); -}; - -class TuyaUIntDatapointUpdateTrigger final : public Trigger { - public: - explicit TuyaUIntDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); -}; - -class TuyaStringDatapointUpdateTrigger final : public Trigger { - public: - explicit TuyaStringDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); -}; - -class TuyaEnumDatapointUpdateTrigger final : public Trigger { - public: - explicit TuyaEnumDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); -}; - -class TuyaBitmaskDatapointUpdateTrigger final : public Trigger { - public: - explicit TuyaBitmaskDatapointUpdateTrigger(Tuya *parent, uint8_t sensor_id); -}; - -} // namespace esphome::tuya diff --git a/esphome/components/tuya/binary_sensor/tuya_binary_sensor.cpp b/esphome/components/tuya/binary_sensor/tuya_binary_sensor.cpp index f93bd31b9d9..9d0589305ed 100644 --- a/esphome/components/tuya/binary_sensor/tuya_binary_sensor.cpp +++ b/esphome/components/tuya/binary_sensor/tuya_binary_sensor.cpp @@ -3,7 +3,7 @@ namespace esphome::tuya { -static const char *const TAG = "tuya.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "tuya.binary_sensor"); void TuyaBinarySensor::setup() { this->parent_->register_listener(this->sensor_id_, [this](const TuyaDatapoint &datapoint) { diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index 111d090c3e1..e07806326c5 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -3,7 +3,7 @@ namespace esphome::tuya { -static const char *const TAG = "tuya.climate"; +ESPHOME_LOG_TAG(TAG, "tuya.climate"); void TuyaClimate::setup() { auto switch_id = this->switch_id_; diff --git a/esphome/components/tuya/cover/tuya_cover.cpp b/esphome/components/tuya/cover/tuya_cover.cpp index dd268388d00..b23dde75db9 100644 --- a/esphome/components/tuya/cover/tuya_cover.cpp +++ b/esphome/components/tuya/cover/tuya_cover.cpp @@ -9,7 +9,7 @@ const uint8_t COMMAND_STOP = 0x01; using namespace esphome::cover; -static const char *const TAG = "tuya.cover"; +ESPHOME_LOG_TAG(TAG, "tuya.cover"); void TuyaCover::setup() { this->value_range_ = this->max_value_ - this->min_value_; diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index 0b5fc19038c..8e4694baed2 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -3,7 +3,7 @@ namespace esphome::tuya { -static const char *const TAG = "tuya.fan"; +ESPHOME_LOG_TAG(TAG, "tuya.fan"); void TuyaFan::setup() { auto speed_id = this->speed_id_; diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index 32cbca9a390..4852e5ff445 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -4,7 +4,7 @@ namespace esphome::tuya { -static const char *const TAG = "tuya.light"; +ESPHOME_LOG_TAG(TAG, "tuya.light"); void TuyaLight::setup() { if (this->color_temperature_id_.has_value()) { diff --git a/esphome/components/tuya/number/tuya_number.cpp b/esphome/components/tuya/number/tuya_number.cpp index b0bbfce6490..538021bca24 100644 --- a/esphome/components/tuya/number/tuya_number.cpp +++ b/esphome/components/tuya/number/tuya_number.cpp @@ -5,7 +5,7 @@ namespace esphome::tuya { -static const char *const TAG = "tuya.number"; +ESPHOME_LOG_TAG(TAG, "tuya.number"); void TuyaNumber::setup() { if (this->restore_value_) { diff --git a/esphome/components/tuya/select/tuya_select.cpp b/esphome/components/tuya/select/tuya_select.cpp index 057f7f8ea69..7c00c7c0f97 100644 --- a/esphome/components/tuya/select/tuya_select.cpp +++ b/esphome/components/tuya/select/tuya_select.cpp @@ -3,7 +3,7 @@ namespace esphome::tuya { -static const char *const TAG = "tuya.select"; +ESPHOME_LOG_TAG(TAG, "tuya.select"); void TuyaSelect::setup() { this->parent_->register_listener(this->select_id_, [this](const TuyaDatapoint &datapoint) { diff --git a/esphome/components/tuya/sensor/tuya_sensor.cpp b/esphome/components/tuya/sensor/tuya_sensor.cpp index c9489847864..6b16d930804 100644 --- a/esphome/components/tuya/sensor/tuya_sensor.cpp +++ b/esphome/components/tuya/sensor/tuya_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::tuya { -static const char *const TAG = "tuya.sensor"; +ESPHOME_LOG_TAG(TAG, "tuya.sensor"); void TuyaSensor::setup() { this->parent_->register_listener(this->sensor_id_, [this](const TuyaDatapoint &datapoint) { diff --git a/esphome/components/tuya/switch/tuya_switch.cpp b/esphome/components/tuya/switch/tuya_switch.cpp index 8d4d183d5b6..204ee84170a 100644 --- a/esphome/components/tuya/switch/tuya_switch.cpp +++ b/esphome/components/tuya/switch/tuya_switch.cpp @@ -3,7 +3,7 @@ namespace esphome::tuya { -static const char *const TAG = "tuya.switch"; +ESPHOME_LOG_TAG(TAG, "tuya.switch"); void TuyaSwitch::setup() { this->parent_->register_listener(this->switch_id_, [this](const TuyaDatapoint &datapoint) { diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp index ebb35cead7f..2e64fb1dea1 100644 --- a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::tuya { -static const char *const TAG = "tuya.text_sensor"; +ESPHOME_LOG_TAG(TAG, "tuya.text_sensor"); void TuyaTextSensor::setup() { this->parent_->register_listener(this->sensor_id_, [this](const TuyaDatapoint &datapoint) { diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index f9b4fe24532..b23dcc946bd 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -18,7 +18,11 @@ namespace esphome::tuya { -static const char *const TAG = "tuya"; +ESPHOME_LOG_TAG(TAG, "tuya"); + +static constexpr uint32_t HEARTBEAT_INTERVAL_ID = 0; +static constexpr uint32_t WIFI_STATUS_INTERVAL_ID = 1; +static constexpr uint32_t DATAPOINT_DUMP_TIMEOUT_ID = 2; static const int COMMAND_DELAY = 10; static const int RECEIVE_TIMEOUT = 300; static const int MAX_RETRIES = 5; @@ -34,7 +38,7 @@ static bool network_is_connected() { } void Tuya::setup() { - this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); + this->set_interval(HEARTBEAT_INTERVAL_ID, 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); if (this->status_pin_ != nullptr) { this->status_pin_->digital_write(false); } @@ -223,7 +227,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff this->status_pin_reported_); } ESP_LOGV(TAG, "Configured status pin %i", this->status_pin_->get_pin()); - this->set_interval("wifi", 1000, [this] { this->set_status_pin_(); }); + this->set_interval(WIFI_STATUS_INTERVAL_ID, 1000, [this] { this->set_status_pin_(); }); } else { ESP_LOGW(TAG, "MCU reported status_pin %i but no status_pin was configured; running in limited mode.", this->status_pin_reported_); @@ -231,7 +235,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff } else { this->init_state_ = TuyaInitState::INIT_WIFI; ESP_LOGV(TAG, "Configured WIFI_STATE periodic send"); - this->set_interval("wifi", 1000, [this] { this->send_wifi_status_(); }); + this->set_interval(WIFI_STATUS_INTERVAL_ID, 1000, [this] { this->send_wifi_status_(); }); } } break; @@ -279,7 +283,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff case TuyaCommandType::DATAPOINT_REPORT_SYNC: if (this->init_state_ == TuyaInitState::INIT_DATAPOINT) { this->init_state_ = TuyaInitState::INIT_DONE; - this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); }); + this->set_timeout(DATAPOINT_DUMP_TIMEOUT_ID, 1000, [this] { this->dump_config(); }); this->initialized_callback_.call(); } this->handle_datapoints_(buffer, len); @@ -478,7 +482,7 @@ void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) { // Run through listeners for (auto &listener : this->listeners_) { if (listener.datapoint_id == datapoint.id) - listener.on_datapoint(datapoint); + listener.on_datapoint.call(datapoint); } } } @@ -792,20 +796,27 @@ void Tuya::send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datap this->send_command_(TuyaCommand{.cmd = TuyaCommandType::DATAPOINT_DELIVER, .payload = buffer}); } -void Tuya::register_listener(uint8_t datapoint_id, const std::function &func) { - auto listener = TuyaDatapointListener{ +void Tuya::register_listener_(uint8_t datapoint_id, Callback func) { + this->listeners_.push_back(TuyaDatapointListener{ .datapoint_id = datapoint_id, .on_datapoint = func, - }; - this->listeners_.push_back(listener); + }); // Run through existing datapoints for (auto &datapoint : this->datapoints_) { if (datapoint.id == datapoint_id) - func(datapoint); + func.call(datapoint); } } +const TuyaDatapoint &TuyaDatapoint::expect_type(TuyaDatapointType expected) const { + if (this->type != expected) { + ESP_LOGW(TAG, "Tuya sensor %u expected datapoint type %#02hhX but got %#02hhX", this->id, + static_cast(expected), static_cast(this->type)); + } + return *this; +} + TuyaInitState Tuya::get_init_state() { return this->init_state_; } } // namespace esphome::tuya diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index b8bf4e0ab18..f074d301629 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -37,11 +37,14 @@ struct TuyaDatapoint { }; std::string value_string; std::vector value_raw; + + /// Log a warning when this datapoint is not of type `expected`, and return it. + const TuyaDatapoint &expect_type(TuyaDatapointType expected) const; }; struct TuyaDatapointListener { uint8_t datapoint_id; - std::function on_datapoint; + Callback on_datapoint; }; enum class TuyaCommandType : uint8_t { @@ -91,7 +94,9 @@ class Tuya final : public Component, public uart::UARTDevice { void setup() override; void loop() override; void dump_config() override; - void register_listener(uint8_t datapoint_id, const std::function &func); + template void register_listener(uint8_t datapoint_id, F &&func) { + this->register_listener_(datapoint_id, Callback::create(std::forward(func))); + } void set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value); void set_boolean_datapoint_value(uint8_t datapoint_id, bool value); void set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value); @@ -117,6 +122,7 @@ class Tuya final : public Component, public uart::UARTDevice { } protected: + void register_listener_(uint8_t datapoint_id, Callback func); void handle_char_(uint8_t c); void handle_datapoints_(const uint8_t *buffer, size_t len); optional get_datapoint_(uint8_t datapoint_id); diff --git a/esphome/components/tuya/water_heater/tuya_water_heater.cpp b/esphome/components/tuya/water_heater/tuya_water_heater.cpp index e1c78530e33..4ece2b0a03d 100644 --- a/esphome/components/tuya/water_heater/tuya_water_heater.cpp +++ b/esphome/components/tuya/water_heater/tuya_water_heater.cpp @@ -3,7 +3,7 @@ namespace esphome::tuya { -static const char *const TAG = "tuya.water_heater"; +ESPHOME_LOG_TAG(TAG, "tuya.water_heater"); void TuyaWaterHeater::setup() { if (this->switch_id_.has_value()) { diff --git a/esphome/components/tx20/__init__.py b/esphome/components/tx20/__init__.py index e69de29bb2d..83dbfc84f65 100644 --- a/esphome/components/tx20/__init__.py +++ b/esphome/components/tx20/__init__.py @@ -0,0 +1 @@ +DOMAIN = "tx20" diff --git a/esphome/components/tx20/tx20.cpp b/esphome/components/tx20/tx20.cpp index 3574bd7c2d5..ce4c20ede41 100644 --- a/esphome/components/tx20/tx20.cpp +++ b/esphome/components/tx20/tx20.cpp @@ -6,7 +6,7 @@ namespace esphome::tx20 { -static const char *const TAG = "tx20"; +ESPHOME_LOG_TAG(TAG, "tx20"); static const uint8_t MAX_BUFFER_SIZE = 41; static const uint16_t TX20_MAX_TIME = MAX_BUFFER_SIZE * 1200 + 5000; static const uint16_t TX20_BIT_TIME = 1200; diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index db24ef7b3a7..6661aff3a03 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -403,7 +403,9 @@ async def to_code(config): cg.add(var.set_clock_source(UART_CLOCK_SOURCES[clock_source])) cg.add(var.set_stop_bits(config[CONF_STOP_BITS])) cg.add(var.set_data_bits(config[CONF_DATA_BITS])) - cg.add(var.set_parity(config[CONF_PARITY])) + # Skip the setter when the config matches the C++ initializer (UART_CONFIG_PARITY_NONE). + if (parity := config[CONF_PARITY]) != "NONE": + cg.add(var.set_parity(parity)) if CONF_DEBUG in config: await debug_to_code(config[CONF_DEBUG], var) @@ -488,7 +490,8 @@ def final_validate_device_schema( return value def validate_stop_bits(value): - if value != stop_bits: + # usb_uart channels store stop bits as strings ("1", "1.5", "2"). + if float(value) != stop_bits: raise cv.Invalid( f"Component {name} requires {stop_bits} stop bits for the uart referenced by {uart_bus}" ) @@ -531,6 +534,25 @@ def final_validate_device_schema( ) +def subtree_references_uart( + node: object, uart_id: str, conf_key: str = CONF_UART_ID +) -> bool: + """Return True if any dict in the subtree has a conf_key entry naming this bus. + + For the final validation of a component that needs a UART for itself. Bare + `id:` references (a uart.write action) and lambdas are not found. + """ + if isinstance(node, dict): + return any( + (key == conf_key and str(value) == uart_id) + or subtree_references_uart(value, uart_id, conf_key) + for key, value in node.items() + ) + if isinstance(node, list): + return any(subtree_references_uart(item, uart_id, conf_key) for item in node) + return False + + async def register_uart_device(var, config): """Register a UART device, setting up all the internal values. diff --git a/esphome/components/uart/button/uart_button.cpp b/esphome/components/uart/button/uart_button.cpp index 809ceaabb03..b55207cb46e 100644 --- a/esphome/components/uart/button/uart_button.cpp +++ b/esphome/components/uart/button/uart_button.cpp @@ -3,7 +3,7 @@ namespace esphome::uart { -static const char *const TAG = "uart.button"; +ESPHOME_LOG_TAG(TAG, "uart.button"); void UARTButton::press_action() { ESP_LOGD(TAG, "'%s': Sending data", this->get_name().c_str()); diff --git a/esphome/components/uart/event/uart_event.cpp b/esphome/components/uart/event/uart_event.cpp index dea36e2e18e..8ae0e467e35 100644 --- a/esphome/components/uart/event/uart_event.cpp +++ b/esphome/components/uart/event/uart_event.cpp @@ -4,7 +4,7 @@ namespace esphome::uart { -static const char *const TAG = "uart.event"; +ESPHOME_LOG_TAG(TAG, "uart.event"); void UARTEvent::setup() {} diff --git a/esphome/components/uart/packet_transport/uart_transport.cpp b/esphome/components/uart/packet_transport/uart_transport.cpp index 6b8eae611c0..215cdded990 100644 --- a/esphome/components/uart/packet_transport/uart_transport.cpp +++ b/esphome/components/uart/packet_transport/uart_transport.cpp @@ -4,7 +4,7 @@ namespace esphome::uart { -static const char *const TAG = "uart_transport"; +ESPHOME_LOG_TAG(TAG, "uart_transport"); void UARTTransport::loop() { PacketTransport::loop(); diff --git a/esphome/components/uart/switch/uart_switch.cpp b/esphome/components/uart/switch/uart_switch.cpp index 642bd19772f..6b6aa273f7a 100644 --- a/esphome/components/uart/switch/uart_switch.cpp +++ b/esphome/components/uart/switch/uart_switch.cpp @@ -4,7 +4,7 @@ namespace esphome::uart { -static const char *const TAG = "uart.switch"; +ESPHOME_LOG_TAG(TAG, "uart.switch"); void UARTSwitch::loop() { if (this->send_every_) { diff --git a/esphome/components/uart/uart.cpp b/esphome/components/uart/uart.cpp index 337fa352c14..20f6385b6a7 100644 --- a/esphome/components/uart/uart.cpp +++ b/esphome/components/uart/uart.cpp @@ -8,7 +8,7 @@ namespace esphome::uart { -static const char *const TAG = "uart"; +ESPHOME_LOG_TAG(TAG, "uart"); // UART parity strings indexed by UARTParityOptions enum (0-2): NONE, EVEN, ODD PROGMEM_STRING_TABLE(UARTParityStrings, "NONE", "EVEN", "ODD", "UNKNOWN"); diff --git a/esphome/components/uart/uart_component.cpp b/esphome/components/uart/uart_component.cpp index 762e56c3990..82bc5927024 100644 --- a/esphome/components/uart/uart_component.cpp +++ b/esphome/components/uart/uart_component.cpp @@ -1,8 +1,14 @@ #include "uart_component.h" +#include "esphome/core/application.h" + +#include namespace esphome::uart { -static const char *const TAG = "uart"; +ESPHOME_LOG_TAG(TAG, "uart"); + +// Keeps the pacing product in 32 bits up to about 10 Mbaud. +static constexpr uint32_t MAX_PACE_SPAN_MS = 4000; bool UARTComponent::check_read_timeout_(size_t len) { if (this->available() >= len) @@ -19,6 +25,19 @@ bool UARTComponent::check_read_timeout_(size_t len) { return true; } +size_t UARTComponent::paced_write_room(uint32_t last_write_ms) { + size_t room = this->available_for_write(); + if (room != SIZE_MAX) { + return room; + } + // A pass woken early writes little. + uint32_t span = + std::min({App.get_loop_component_start_time() - last_write_ms, App.get_loop_interval(), MAX_PACE_SPAN_MS}); + // 10 bits per byte on the line. + uint32_t paced = this->baud_rate_ / 10 * span / 1000; + return std::max(1, paced); +} + void UARTComponent::set_rx_full_threshold_ms(uint8_t time) { uint8_t bytelength = this->data_bits_ + this->stop_bits_ + 1; if (this->parity_ != UARTParityOptions::UART_CONFIG_PARITY_NONE) diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h index 4269ef0b16f..2543cb54768 100644 --- a/esphome/components/uart/uart_component.h +++ b/esphome/components/uart/uart_component.h @@ -86,6 +86,10 @@ class UARTComponent { // Platforms that cannot tell return SIZE_MAX: write_array() takes everything and may block. virtual size_t available_for_write() { return SIZE_MAX; } + // Bytes a write can take now. Where available_for_write() cannot tell (SIZE_MAX), paces to the line time since + // last_write_ms, a loop start time: at most one loop interval and 4 s, at least one byte. + size_t paced_write_room(uint32_t last_write_ms); + // Pure virtual method to block until all bytes have been written to the UART bus. // @return UARTFlushResult indicating whether the flush was confirmed, timed out, failed, or assumed successful. virtual UARTFlushResult flush() = 0; @@ -217,7 +221,7 @@ class UARTComponent { uint32_t baud_rate_{0}; uint8_t stop_bits_{0}; uint8_t data_bits_{0}; - UARTParityOptions parity_{UART_CONFIG_PARITY_NONE}; + UARTParityOptions parity_{UART_CONFIG_PARITY_NONE}; // Must match the parity check in __init__.py #ifdef USE_UART_DEBUGGER CallbackManager debug_callback_{}; #endif diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 0f6d7f6577c..a32677b3daa 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -15,7 +15,7 @@ namespace esphome::uart { -static const char *const TAG = "uart"; +ESPHOME_LOG_TAG(TAG, "uart"); bool ESP8266UartComponent::serial0_in_use = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) uint32_t ESP8266UartComponent::get_config() { diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 5d90fc1c8a6..cccb7289fba 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -23,7 +23,7 @@ namespace esphome::uart { -static const char *const TAG = "uart"; +ESPHOME_LOG_TAG(TAG, "uart"); /// Check if a pin number matches one of the default UART0 GPIO pins. /// These pins may have residual IOMUX state from the ROM bootloader that @@ -238,6 +238,10 @@ void IDFUARTComponent::load_settings(bool dump_config) { this->mark_failed(); return; } +#ifdef USE_GPIO_HOLD + // Release held pins so the UART peripheral can drive them + this->set_pins_hold_(false); +#endif #ifdef USE_UART_WAKE_LOOP_ON_RX // Register ISR callback to wake the main loop when UART data arrives. @@ -514,7 +518,20 @@ void IDFUARTComponent::on_shutdown() { if (err != ESP_OK) { ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err)); } +#ifdef USE_GPIO_HOLD + // Hold the pins again so they keep their state through the reset + this->set_pins_hold_(true); +#endif } +#ifdef USE_GPIO_HOLD +void IDFUARTComponent::set_pins_hold_(bool hold) { + for (auto *pin : {this->tx_pin_, this->rx_pin_, this->flow_control_pin_}) { + if (pin != nullptr) + pin->set_hold(hold); + } +} +#endif + } // namespace esphome::uart #endif // USE_ESP32 diff --git a/esphome/components/uart/uart_component_esp_idf.h b/esphome/components/uart/uart_component_esp_idf.h index 7c93b74fbdd..260117c4299 100644 --- a/esphome/components/uart/uart_component_esp_idf.h +++ b/esphome/components/uart/uart_component_esp_idf.h @@ -90,6 +90,9 @@ class IDFUARTComponent final : public UARTComponent, public Component { protected: void check_logger_conflict() override; +#ifdef USE_GPIO_HOLD + void set_pins_hold_(bool hold); +#endif uint32_t line_inversion_mask_(); // Re-applies what uart_param_config() resets: inversion, RX threshold/timeout, mode. esp_err_t apply_line_settings_(); diff --git a/esphome/components/uart/uart_component_host.cpp b/esphome/components/uart/uart_component_host.cpp index 63b5631564d..9e2f273f47c 100644 --- a/esphome/components/uart/uart_component_host.cpp +++ b/esphome/components/uart/uart_component_host.cpp @@ -98,7 +98,7 @@ speed_t get_baud(int baud) { namespace esphome::uart { -static const char *const TAG = "uart"; +ESPHOME_LOG_TAG(TAG, "uart"); HostUartComponent::~HostUartComponent() { if (this->file_descriptor_ != -1) { diff --git a/esphome/components/uart/uart_component_libretiny.cpp b/esphome/components/uart/uart_component_libretiny.cpp index 4eacd980db3..5a77bf26d9e 100644 --- a/esphome/components/uart/uart_component_libretiny.cpp +++ b/esphome/components/uart/uart_component_libretiny.cpp @@ -6,6 +6,8 @@ #include "esphome/core/log.h" #include "uart_component_libretiny.h" +#include "esphome/components/libretiny/core.h" + #ifdef USE_LOGGER #include "esphome/components/logger/logger.h" #endif @@ -16,7 +18,7 @@ namespace esphome::uart { -static const char *const TAG = "uart"; +ESPHOME_LOG_TAG(TAG, "uart"); static const char *const UART_TYPE[] = { "hardware", @@ -118,6 +120,8 @@ void LibreTinyUARTComponent::setup() { #endif } + if (this->hardware_idx_ >= 0) + libretiny::ensure_serial_handle(this->hardware_idx_); this->serial_->begin(this->baud_rate_, get_config()); } diff --git a/esphome/components/uart/uart_component_rp2.cpp b/esphome/components/uart/uart_component_rp2.cpp index ffb9bc0f2d2..07245bc230c 100644 --- a/esphome/components/uart/uart_component_rp2.cpp +++ b/esphome/components/uart/uart_component_rp2.cpp @@ -13,7 +13,7 @@ namespace esphome::uart { -static const char *const TAG = "uart"; +ESPHOME_LOG_TAG(TAG, "uart"); uint16_t RP2UartComponent::get_config() { uint16_t config = 0; diff --git a/esphome/components/uart/uart_debugger.cpp b/esphome/components/uart/uart_debugger.cpp index c102c663df5..cf1fac149ca 100644 --- a/esphome/components/uart/uart_debugger.cpp +++ b/esphome/components/uart/uart_debugger.cpp @@ -9,7 +9,7 @@ namespace esphome::uart { -static const char *const TAG = "uart_debug"; +ESPHOME_LOG_TAG(TAG, "uart_debug"); UARTDebugger::UARTDebugger(UARTComponent *parent) { parent->add_debug_callback([this](UARTDirection direction, uint8_t byte) { diff --git a/esphome/components/uart_mux/uart_mux.cpp b/esphome/components/uart_mux/uart_mux.cpp index 953e81d5337..11057ac078c 100644 --- a/esphome/components/uart_mux/uart_mux.cpp +++ b/esphome/components/uart_mux/uart_mux.cpp @@ -4,7 +4,7 @@ namespace esphome::uart_mux { -static const char *const TAG = "uart_mux"; +ESPHOME_LOG_TAG(TAG, "uart_mux"); void UARTMux::setup() { // A failed UART never assigned its port; nothing behind the mux can work. diff --git a/esphome/components/uart_tcp/__init__.py b/esphome/components/uart_tcp/__init__.py index 3829f046ca6..9d62d48d7c2 100644 --- a/esphome/components/uart_tcp/__init__.py +++ b/esphome/components/uart_tcp/__init__.py @@ -1,5 +1,5 @@ import esphome.codegen as cg -from esphome.components import binary_sensor, socket, uart +from esphome.components import binary_sensor, sensor, socket, uart from esphome.components.const import ( CONF_ALLOWED_IPS, CONF_CONNECTED, @@ -7,21 +7,30 @@ from esphome.components.const import ( CONF_RECONNECT_INTERVAL, CONF_ROLE, ) +from esphome.components.tcp_uart import DOMAIN as TCP_UART_DOMAIN import esphome.config_validation as cv from esphome.const import ( + CONF_DEBUG, + CONF_DUMMY_RECEIVER, CONF_ID, CONF_PORT, CONF_UART_ID, DEVICE_CLASS_CONNECTIVITY, ENTITY_CATEGORY_DIAGNOSTIC, + STATE_CLASS_TOTAL_INCREASING, ) +from esphome.core import CORE +import esphome.final_validate as fv from esphome.types import ConfigType CODEOWNERS = ["@Bascht74"] +DOMAIN = "uart_tcp" DEPENDENCIES = ["network", "uart"] -AUTO_LOAD = ["binary_sensor", "socket"] +AUTO_LOAD = ["binary_sensor", "sensor", "socket"] MULTI_CONF = True +CONF_DISCONNECTS = "disconnects" + uart_tcp_ns = cg.esphome_ns.namespace("uart_tcp") UartTcp = uart_tcp_ns.class_("UartTcp", cg.Component, uart.UARTDevice) @@ -38,13 +47,18 @@ BASE_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_CONNECTIVITY, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), + cv.Optional(CONF_DISCONNECTS): sensor.sensor_schema( + accuracy_decimals=0, + state_class=STATE_CLASS_TOTAL_INCREASING, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), } ).extend(cv.COMPONENT_SCHEMA) CONFIG_SCHEMA = cv.All( cv.typed_schema( { - "client": BASE_SCHEMA.extend({cv.Required(CONF_HOST): cv.string}), + "client": BASE_SCHEMA.extend({cv.Required(CONF_HOST): socket.ipv4_host}), "server": BASE_SCHEMA.extend( {cv.Optional(CONF_ALLOWED_IPS): socket.IPV4_ALLOW_SCHEMA} ), @@ -57,6 +71,63 @@ CONFIG_SCHEMA = cv.All( ) +def _reject_dummy_receiver(uart_conf: ConfigType) -> ConfigType: + debug = uart_conf.get(CONF_DEBUG) + if isinstance(debug, dict) and debug.get(CONF_DUMMY_RECEIVER): + raise cv.Invalid( + "dummy_receiver reads this UART and drops the bytes uart_tcp should forward.", + [CONF_DEBUG, CONF_DUMMY_RECEIVER], + ) + return uart_conf + + +def _final_validate(config: ConfigType) -> ConfigType: + # A second reader would split the bytes with this one, and every connect + # discards what the other reader has not read yet. + full_config = fv.full_config.get() + data = full_config.data.setdefault(DOMAIN, {}) + uart_id = str(config[CONF_UART_ID]) + used = data.setdefault(CONF_UART_ID, set()) + if uart_id in used: + raise cv.Invalid( + f"The UART '{uart_id}' is already used by another 'uart_tcp' entry. " + "Each uart_tcp needs its own UART.", + [CONF_UART_ID], + ) + used.add(uart_id) + # Grouped CI builds share one bus between components, like uart's pin check. + # Bare `id:` references (a uart.write action) and lambdas are not caught. + if not CORE.testing_mode: + for domain, domain_conf in full_config.items(): + if domain != DOMAIN and uart.subtree_references_uart(domain_conf, uart_id): + raise cv.Invalid( + f"The UART '{uart_id}' is also used by '{domain}'. " + "uart_tcp requires exclusive use of that UART.", + [CONF_UART_ID], + ) + fv.id_declaration_match_schema(_reject_dummy_receiver)(config[CONF_UART_ID]) + + if config[CONF_ROLE] == "server": + # Two listeners on one port cannot both serve it. Only uart_tcp and + # tcp_uart servers are compared here, not other listeners such as api. + port = config[CONF_PORT] + ports = data.setdefault(CONF_PORT, set()) + if port in ports or any( + conf[CONF_ROLE] == "server" and conf[CONF_PORT] == port + for conf in full_config.get(TCP_UART_DOMAIN, []) + ): + raise cv.Invalid( + f"Port {port} is already the listen port of another uart_tcp " + "server or of a tcp_uart server.", + [CONF_PORT], + ) + ports.add(port) + return config + + +FINAL_VALIDATE_SCHEMA = _final_validate + + async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) @@ -75,3 +146,5 @@ async def to_code(config: ConfigType) -> None: cg.add(var.set_host(host)) binary_sensors = binary_sensor.sub_binary_sensors(config) await binary_sensors(CONF_CONNECTED, var.set_connected_sensor) + sensors = sensor.sub_sensors(config) + await sensors(CONF_DISCONNECTS, var.set_disconnects_sensor) diff --git a/esphome/components/uart_tcp/uart_tcp.cpp b/esphome/components/uart_tcp/uart_tcp.cpp index cf15802b55f..d634ac579f2 100644 --- a/esphome/components/uart_tcp/uart_tcp.cpp +++ b/esphome/components/uart_tcp/uart_tcp.cpp @@ -1,5 +1,6 @@ #include "uart_tcp.h" +#include "esphome/core/application.h" #include "esphome/core/log.h" #include @@ -8,10 +9,7 @@ namespace esphome::uart_tcp { -static const char *const TAG = "uart_tcp"; - -// Bytes per 16 ms loop pass at 10 bits per byte: baud / 10 / 62.5. -static constexpr uint32_t BAUD_PACE_DIVISOR = 625; +ESPHOME_LOG_TAG(TAG, "uart_tcp"); void UartTcp::setup() { this->link_.begin(TAG); @@ -21,6 +19,11 @@ void UartTcp::setup() { if (this->connected_sensor_ != nullptr) { this->connected_sensor_->publish_state(false); } +#ifdef USE_SENSOR + if (this->disconnects_sensor_ != nullptr) { + this->disconnects_sensor_->publish_state(0); + } +#endif } void UartTcp::dump_config() { @@ -35,6 +38,9 @@ void UartTcp::dump_config() { this->listener_.dump_config(); #endif LOG_BINARY_SENSOR(" ", "Connected", this->connected_sensor_); +#ifdef USE_SENSOR + LOG_SENSOR(" ", "Disconnects", this->disconnects_sensor_); +#endif } void UartTcp::on_shutdown() { @@ -54,17 +60,19 @@ void UartTcp::sync_link_() { if (this->connected_sensor_ != nullptr) { this->connected_sensor_->publish_state(up); } +#ifdef USE_SENSOR + // Only edges get here, so down is the falling edge. + if (!up && this->disconnects_sensor_ != nullptr) { + this->disconnects_++; + this->disconnects_sensor_->publish_state(this->disconnects_); + } +#endif } void UartTcp::read_socket_() { // A hardware write blocks until the driver takes every byte. Leave what does // not fit in the socket, so TCP flow control throttles the peer. - size_t room = this->parent_->available_for_write(); - if (room == SIZE_MAX) { - // Capacity unknown on this platform; pace to one loop pass of UART time - // (16 ms at 10 bits per byte) so a blocking write stays short. - room = std::max(1, this->parent_->get_baud_rate() / BAUD_PACE_DIVISOR); - } + size_t room = this->parent_->paced_write_room(this->last_write_ms_); if (room == 0) { this->rx_pending_ = true; return; @@ -81,11 +89,12 @@ void UartTcp::read_socket_() { } this->rx_pending_ = static_cast(count) == want; this->write_array(tmp, static_cast(count)); + this->last_write_ms_ = App.get_loop_component_start_time(); } void UartTcp::discard_uart_() { // Drain exactly what was buffered while the link was down; later bytes are live. - uint8_t dump[32]; + uint8_t dump[DISCARD_CHUNK]; size_t left = this->available(); while (left != 0) { size_t n = std::min(left, sizeof(dump)); diff --git a/esphome/components/uart_tcp/uart_tcp.h b/esphome/components/uart_tcp/uart_tcp.h index 1e19166f879..256e059133a 100644 --- a/esphome/components/uart_tcp/uart_tcp.h +++ b/esphome/components/uart_tcp/uart_tcp.h @@ -1,6 +1,9 @@ #pragma once #include "esphome/components/binary_sensor/binary_sensor.h" +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif #include "esphome/components/socket/tcp_client_link.h" #ifdef USE_SOCKET_TCP_LISTENER #include "esphome/components/socket/tcp_listener.h" @@ -14,12 +17,15 @@ namespace esphome::uart_tcp { /// Copies raw bytes between one hardware UART and one TCP socket. -class UartTcp : public Component, public uart::UARTDevice { +class UartTcp final : public Component, public uart::UARTDevice { public: void set_host(const char *host) { this->link_.set_host(host); } void set_port(uint16_t port) { this->link_.set_port(port); } void set_reconnect_interval(uint32_t ms) { this->link_.set_reconnect_interval(ms); } void set_connected_sensor(binary_sensor::BinarySensor *sensor) { this->connected_sensor_ = sensor; } +#ifdef USE_SENSOR + void set_disconnects_sensor(sensor::Sensor *sensor) { this->disconnects_sensor_ = sensor; } +#endif #ifdef USE_SOCKET_TCP_LISTENER void set_server(bool server) { this->server_ = server; } #ifdef USE_SOCKET_IPV4_ALLOW @@ -40,12 +46,20 @@ class UartTcp : public Component, public uart::UARTDevice { void discard_uart_(); static constexpr size_t READ_CHUNK = 128; + // Scratch size for dropping stale UART bytes on connect. + static constexpr size_t DISCARD_CHUNK = 32; socket::TcpClientLink link_; #ifdef USE_SOCKET_TCP_LISTENER socket::TcpListener listener_; #endif binary_sensor::BinarySensor *connected_sensor_{nullptr}; + // Loop start time of the last socket-to-UART write; sizes the next paced write. + uint32_t last_write_ms_{0}; +#ifdef USE_SENSOR + sensor::Sensor *disconnects_sensor_{nullptr}; + uint32_t disconnects_{0}; +#endif bool server_{false}; // The link state loop() saw last; edges clear the buffer and publish the sensor. bool link_was_up_{false}; diff --git a/esphome/components/udp/__init__.py b/esphome/components/udp/__init__.py index d96a731e9c0..81149f056ba 100644 --- a/esphome/components/udp/__init__.py +++ b/esphome/components/udp/__init__.py @@ -21,6 +21,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@clydebarrow"] DEPENDENCIES = ["network"] AUTO_LOAD = ["socket"] +DOMAIN = "udp" MULTI_CONF = True udp_ns = cg.esphome_ns.namespace("udp") @@ -124,7 +125,15 @@ async def to_code(config: ConfigType) -> None: cg.add(var.set_broadcast_port(conf_port[CONF_BROADCAST_PORT])) if (listen_address := str(config[CONF_LISTEN_ADDRESS])) != "255.255.255.255": cg.add(var.set_listen_address(listen_address)) - cg.add(var.set_addresses([str(addr) for addr in config[CONF_ADDRESSES]])) + # Shared flash table ended by nullptr, so the component stores only a pointer. + if addresses := [str(addr) for addr in config[CONF_ADDRESSES]]: + cg.add( + var.set_addresses( + cg.shared_progmem_array( + "udp_addresses", cg.const_char_ptr, [*addresses, cg.nullptr] + ) + ) + ) for conf in config.get(CONF_ON_RECEIVE, []): trigger_id = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) trigger = await automation.build_automation(trigger_id, trigger_argtype, conf) @@ -176,16 +185,7 @@ async def udp_write_to_code( udp_var = await cg.get_variable(config[CONF_ID]) await cg.register_parented(var, udp_var) cg.add(udp_var.set_should_broadcast()) - data = config[CONF_DATA] - if isinstance(data, bytes): - data = list(data) - - if cg.is_template(data): - templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) - cg.add(var.set_data_template(templ)) - else: - # Generate static array in flash to avoid RAM copy - arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8) - arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data)) - cg.add(var.set_data_static(arr, len(data))) + await automation.templatable_bytes( + config[CONF_DATA], args, var.set_data_template, var.set_data_static, "udp_data" + ) return var diff --git a/esphome/components/udp/automation.h b/esphome/components/udp/automation.h index 9b3ba7e0ddb..a6a40829b11 100644 --- a/esphome/components/udp/automation.h +++ b/esphome/components/udp/automation.h @@ -4,39 +4,19 @@ #ifdef USE_NETWORK #include "esphome/core/automation.h" -#include - namespace esphome::udp { -template class UDPWriteAction final : public Action, public Parented { - public: - void set_data_template(std::vector (*func)(Ts...)) { - this->data_.func = func; - this->len_ = -1; // Sentinel value indicates template mode - } +/// Constant payloads up to this size are sent from the stack on ESP8266. +static constexpr size_t STACK_PAYLOAD_SIZE = 128; - void set_data_static(const uint8_t *data, size_t len) { - this->data_.data = data; - this->len_ = len; // Length >= 0 indicates static mode - } +template class UDPWriteAction final : public Action, public Parented { + TEMPLATABLE_BYTES(data) void play(const Ts &...x) override { - if (this->len_ >= 0) { - // Static mode: pass pointer directly to send_packet(const uint8_t *, size_t) - this->parent_->send_packet(this->data_.data, static_cast(this->len_)); - } else { - // Template mode: call function and pass vector to send_packet(const std::vector &) - auto val = this->data_.func(x...); - this->parent_->send_packet(val); - } + // One write per packet: WiFiUDP appends all or nothing, so a failed allocation never sends a partial packet + this->data_.template visit( + [this](const uint8_t *data, size_t len) { this->parent_->send_packet(data, len); }, x...); } - - protected: - ssize_t len_{-1}; // -1 = template mode, >=0 = static mode with length - union Data { - std::vector (*func)(Ts...); // Function pointer (stateless lambdas) - const uint8_t *data; // Pointer to static data in flash - } data_; }; } // namespace esphome::udp diff --git a/esphome/components/udp/packet_transport/udp_transport.cpp b/esphome/components/udp/packet_transport/udp_transport.cpp index 40ce46d74e9..2e49b676ce8 100644 --- a/esphome/components/udp/packet_transport/udp_transport.cpp +++ b/esphome/components/udp/packet_transport/udp_transport.cpp @@ -5,7 +5,7 @@ namespace esphome::udp { -static const char *const TAG = "udp_transport"; +ESPHOME_LOG_TAG(TAG, "udp_transport"); bool UDPTransport::should_send() { return network::is_connected(); } void UDPTransport::setup() { diff --git a/esphome/components/udp/udp_component.cpp b/esphome/components/udp/udp_component.cpp index f662cba4cfb..3918ddc1fa8 100644 --- a/esphome/components/udp/udp_component.cpp +++ b/esphome/components/udp/udp_component.cpp @@ -7,11 +7,12 @@ namespace esphome::udp { -static const char *const TAG = "udp"; +ESPHOME_LOG_TAG(TAG, "udp"); void UDPComponent::setup() { #if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) - for (const auto &address : this->addresses_) { + for (const char *const *it = this->addresses_; it != nullptr && *it != nullptr; it++) { + const char *address = *it; struct sockaddr saddr {}; if (socket::set_sockaddr(&saddr, sizeof(saddr), address, this->broadcast_port_) == 0) { ESP_LOGW(TAG, "Invalid address %s", address); @@ -97,7 +98,8 @@ void UDPComponent::setup() { #endif #ifdef USE_SOCKET_IMPL_LWIP_TCP // 8266 and RP2040 `Duino - for (const auto &address : this->addresses_) { + for (const char *const *it = this->addresses_; it != nullptr && *it != nullptr; it++) { + const char *address = *it; auto ipaddr = IPAddress(); if (!ipaddr.fromString(address)) { ESP_LOGW(TAG, "Invalid address %s", address); @@ -138,7 +140,8 @@ void UDPComponent::dump_config() { " Listen Port: %u\n" " Broadcast Port: %u", this->listen_port_, this->broadcast_port_); - for (const char *address : this->addresses_) { + for (const char *const *it = this->addresses_; it != nullptr && *it != nullptr; it++) { + const char *address = *it; ESP_LOGCONFIG(TAG, " Address: %s", address); } if (this->listen_address_.has_value()) { diff --git a/esphome/components/udp/udp_component.h b/esphome/components/udp/udp_component.h index 274e0119ee5..17e27fb7704 100644 --- a/esphome/components/udp/udp_component.h +++ b/esphome/components/udp/udp_component.h @@ -20,9 +20,8 @@ namespace esphome::udp { static const size_t MAX_PACKET_SIZE = 508; class UDPComponent final : public Component { public: - void set_addresses(std::initializer_list addresses) { this->addresses_ = addresses; } - /// Prevent accidental use of std::string which would dangle - void set_addresses(std::initializer_list addresses) = delete; + /// nullptr-terminated table of addresses; must outlive the component. + void set_addresses(const char *const *addresses) { this->addresses_ = addresses; } void set_listen_address(const char *listen_addr) { this->listen_address_ = network::IPAddress(listen_addr); } void set_listen_port(uint16_t port) { this->listen_port_ = port; } void set_broadcast_port(uint16_t port) { this->broadcast_port_ = port; } @@ -52,7 +51,7 @@ class UDPComponent final : public Component { std::vector ipaddrs_{}; WiFiUDP udp_client_{}; #endif - FixedVector addresses_{}; + const char *const *addresses_{nullptr}; optional listen_address_{}; }; diff --git a/esphome/components/ufire_ec/__init__.py b/esphome/components/ufire_ec/__init__.py index 08f36c7934c..e5461b4ff7a 100644 --- a/esphome/components/ufire_ec/__init__.py +++ b/esphome/components/ufire_ec/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@pvizeli"] +DOMAIN = "ufire_ec" diff --git a/esphome/components/ufire_ec/ufire_ec.cpp b/esphome/components/ufire_ec/ufire_ec.cpp index 72bf837409f..1bb12229417 100644 --- a/esphome/components/ufire_ec/ufire_ec.cpp +++ b/esphome/components/ufire_ec/ufire_ec.cpp @@ -4,7 +4,9 @@ namespace esphome::ufire_ec { -static const char *const TAG = "ufire_ec"; +ESPHOME_LOG_TAG(TAG, "ufire_ec"); + +static constexpr uint32_t DATA_TIMEOUT_ID = 0; void UFireECComponent::setup() { uint8_t version; @@ -45,7 +47,7 @@ void UFireECComponent::update() { } if (wait > 0) { - this->set_timeout("data", wait, [this]() { this->update_internal_(); }); + this->set_timeout(DATA_TIMEOUT_ID, wait, [this]() { this->update_internal_(); }); } } diff --git a/esphome/components/ufire_ise/__init__.py b/esphome/components/ufire_ise/__init__.py index 08f36c7934c..9fb4cb9c65e 100644 --- a/esphome/components/ufire_ise/__init__.py +++ b/esphome/components/ufire_ise/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@pvizeli"] +DOMAIN = "ufire_ise" diff --git a/esphome/components/ufire_ise/ufire_ise.cpp b/esphome/components/ufire_ise/ufire_ise.cpp index d595b37a83c..aa899968e44 100644 --- a/esphome/components/ufire_ise/ufire_ise.cpp +++ b/esphome/components/ufire_ise/ufire_ise.cpp @@ -5,7 +5,9 @@ namespace esphome::ufire_ise { -static const char *const TAG = "ufire_ise"; +ESPHOME_LOG_TAG(TAG, "ufire_ise"); + +static constexpr uint32_t DATA_TIMEOUT_ID = 0; void UFireISEComponent::setup() { uint8_t version; @@ -38,7 +40,7 @@ void UFireISEComponent::update() { } // Wait until measurement are taken - this->set_timeout("data", wait, [this]() { this->update_internal_(); }); + this->set_timeout(DATA_TIMEOUT_ID, wait, [this]() { this->update_internal_(); }); } void UFireISEComponent::update_internal_() { diff --git a/esphome/components/ufm01/__init__.py b/esphome/components/ufm01/__init__.py index 85ca0eecaef..e1fcef107d5 100644 --- a/esphome/components/ufm01/__init__.py +++ b/esphome/components/ufm01/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_ID from esphome.types import ConfigType CODEOWNERS = ["@ljungqvist"] +DOMAIN = "ufm01" MULTI_CONF = True diff --git a/esphome/components/ufm01/ufm01.cpp b/esphome/components/ufm01/ufm01.cpp index fbacd84db0f..cd4388372cd 100644 --- a/esphome/components/ufm01/ufm01.cpp +++ b/esphome/components/ufm01/ufm01.cpp @@ -9,7 +9,7 @@ namespace esphome::ufm01 { -static const char *const TAG = "ufm01"; +ESPHOME_LOG_TAG(TAG, "ufm01"); static constexpr uint8_t COMMAND_ACK = 0xE5; static constexpr uint32_t COMMAND_ACK_TIMEOUT_MS = 500; diff --git a/esphome/components/uln2003/__init__.py b/esphome/components/uln2003/__init__.py index e69de29bb2d..6f2260af57f 100644 --- a/esphome/components/uln2003/__init__.py +++ b/esphome/components/uln2003/__init__.py @@ -0,0 +1 @@ +DOMAIN = "uln2003" diff --git a/esphome/components/uln2003/uln2003.cpp b/esphome/components/uln2003/uln2003.cpp index a2244eadaaa..64db28a5306 100644 --- a/esphome/components/uln2003/uln2003.cpp +++ b/esphome/components/uln2003/uln2003.cpp @@ -3,7 +3,7 @@ namespace esphome::uln2003 { -static const char *const TAG = "uln2003.stepper"; +ESPHOME_LOG_TAG(TAG, "uln2003.stepper"); static const LogString *step_mode_to_log_string(ULN2003StepMode mode) { switch (mode) { diff --git a/esphome/components/ultrasonic/__init__.py b/esphome/components/ultrasonic/__init__.py index 3bca12bffcb..cdc67e78138 100644 --- a/esphome/components/ultrasonic/__init__.py +++ b/esphome/components/ultrasonic/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@swoboda1337", "@ssieb"] +DOMAIN = "ultrasonic" diff --git a/esphome/components/ultrasonic/ultrasonic_sensor.cpp b/esphome/components/ultrasonic/ultrasonic_sensor.cpp index a354b198b4b..039d08eaae7 100644 --- a/esphome/components/ultrasonic/ultrasonic_sensor.cpp +++ b/esphome/components/ultrasonic/ultrasonic_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::ultrasonic { -static const char *const TAG = "ultrasonic.sensor"; +ESPHOME_LOG_TAG(TAG, "ultrasonic.sensor"); static constexpr uint32_t DEBOUNCE_US = 50; // Ignore edges within 50us of each other (noise filtering) static constexpr uint32_t START_DELAY_US = 100; // Ignore edges within 100us of trigger (filters bleed-through) diff --git a/esphome/components/unicode/__init__.py b/esphome/components/unicode/__init__.py new file mode 100644 index 00000000000..c5464cbe6c2 --- /dev/null +++ b/esphome/components/unicode/__init__.py @@ -0,0 +1,2 @@ +CODEOWNERS = ["@esphome/core"] +DOMAIN = "unicode" diff --git a/esphome/components/unicode/unicode.cpp b/esphome/components/unicode/unicode.cpp new file mode 100644 index 00000000000..f5b4084fe09 --- /dev/null +++ b/esphome/components/unicode/unicode.cpp @@ -0,0 +1,97 @@ +#include "unicode.h" + +namespace esphome::unicode { + +uint32_t extract_unicode_codepoint(const char *utf8_str, size_t *length) { + // Safely cast to uint8_t* for correct bitwise operations on bytes + const uint8_t *current = reinterpret_cast(utf8_str); + uint32_t code_point = 0; + uint8_t c1 = *current++; + + // check for end of string + if (c1 == 0) { + *length = 0; + return 0; + } + + // --- 1-Byte Sequence: 0xxxxxxx (ASCII) --- + if (c1 < 0x80) { + // Valid ASCII byte. + code_point = c1; + // Optimization: No need to check for continuation bytes. + } + // --- 2-Byte Sequence: 110xxxxx 10xxxxxx --- + else if ((c1 & 0xE0) == 0xC0) { + uint8_t c2 = *current++; + + // Error Check 1: Check if c2 is a valid continuation byte (10xxxxxx) + if ((c2 & 0xC0) != 0x80) { + *length = 0; + return 0; + } + + code_point = (c1 & 0x1F) << 6; + code_point |= (c2 & 0x3F); + + // Error Check 2: Overlong check (2-byte must be > 0x7F) + if (code_point <= 0x7F) { + *length = 0; + return 0; + } + } + // --- 3-Byte Sequence: 1110xxxx 10xxxxxx 10xxxxxx --- + else if ((c1 & 0xF0) == 0xE0) { + uint8_t c2 = *current++; + uint8_t c3 = *current++; + + // Error Check 1: Check continuation bytes + if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) { + *length = 0; + return 0; + } + + code_point = (c1 & 0x0F) << 12; + code_point |= (c2 & 0x3F) << 6; + code_point |= (c3 & 0x3F); + + // Error Check 2: Overlong check (3-byte must be > 0x7FF) + // Also check for surrogates (0xD800-0xDFFF) + if (code_point <= 0x7FF || (code_point >= 0xD800 && code_point <= 0xDFFF)) { + *length = 0; + return 0; + } + } + // --- 4-Byte Sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx --- + else if ((c1 & 0xF8) == 0xF0) { + uint8_t c2 = *current++; + uint8_t c3 = *current++; + uint8_t c4 = *current++; + + // Error Check 1: Check continuation bytes + if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80) || ((c4 & 0xC0) != 0x80)) { + *length = 0; + return 0; + } + + code_point = (c1 & 0x07) << 18; + code_point |= (c2 & 0x3F) << 12; + code_point |= (c3 & 0x3F) << 6; + code_point |= (c4 & 0x3F); + + // Error Check 2: Overlong check (4-byte must be > 0xFFFF) + // Also check for valid Unicode range (must be <= 0x10FFFF) + if (code_point <= 0xFFFF || code_point > 0x10FFFF) { + *length = 0; + return 0; + } + } + // --- Invalid leading byte (e.g., 10xxxxxx or 11111xxx) --- + else { + *length = 0; + return 0; + } + *length = current - reinterpret_cast(utf8_str); + return code_point; +} + +} // namespace esphome::unicode diff --git a/esphome/components/unicode/unicode.h b/esphome/components/unicode/unicode.h new file mode 100644 index 00000000000..9e69d036e0c --- /dev/null +++ b/esphome/components/unicode/unicode.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +namespace esphome::unicode { + +/** + * Attempt to extract a 32 bit Unicode codepoint from a UTF-8 string. + * If successful, return the codepoint and set the length to the number of bytes read. + * If the end of the string has been reached and a valid codepoint has not been found, return 0 and set the length to + * 0. + * + * @param utf8_str The input string + * @param length Pointer to length storage + * @return The extracted code point + */ +uint32_t extract_unicode_codepoint(const char *utf8_str, size_t *length); + +} // namespace esphome::unicode diff --git a/esphome/components/update/__init__.py b/esphome/components/update/__init__.py index 1533a893ba6..e455a4f7686 100644 --- a/esphome/components/update/__init__.py +++ b/esphome/components/update/__init__.py @@ -25,6 +25,7 @@ from esphome.cpp_generator import MockObj, MockObjClass from esphome.types import ConfigType CODEOWNERS = ["@jesserockz"] +DOMAIN = "update" IS_PLATFORM_COMPONENT = True update_ns = cg.esphome_ns.namespace("update") diff --git a/esphome/components/update/update_entity.cpp b/esphome/components/update/update_entity.cpp index b0d4c01cc96..33c33ccb621 100644 --- a/esphome/components/update/update_entity.cpp +++ b/esphome/components/update/update_entity.cpp @@ -6,7 +6,7 @@ namespace esphome::update { -static const char *const TAG = "update"; +ESPHOME_LOG_TAG(TAG, "update"); // Update state strings indexed by UpdateState enum (0-3): UNKNOWN, NO UPDATE, UPDATE AVAILABLE, INSTALLING PROGMEM_STRING_TABLE(UpdateStateStrings, "UNKNOWN", "NO UPDATE", "UPDATE AVAILABLE", "INSTALLING"); diff --git a/esphome/components/uponor_smatrix/__init__.py b/esphome/components/uponor_smatrix/__init__.py index ba686dc22ad..5dc32259ea2 100644 --- a/esphome/components/uponor_smatrix/__init__.py +++ b/esphome/components/uponor_smatrix/__init__.py @@ -6,6 +6,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@kroimon"] +DOMAIN = "uponor_smatrix" DEPENDENCIES = ["uart"] diff --git a/esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp b/esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp index 0400888511d..7b6717c682b 100644 --- a/esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +++ b/esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp @@ -7,7 +7,7 @@ namespace esphome::uponor_smatrix { -static const char *const TAG = "uponor_smatrix.climate"; +ESPHOME_LOG_TAG(TAG, "uponor_smatrix.climate"); void UponorSmatrixClimate::dump_config() { LOG_CLIMATE("", "Uponor Smatrix Climate", this); diff --git a/esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp b/esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp index 97e9c275700..9b5f7df87a2 100644 --- a/esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp +++ b/esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp @@ -5,7 +5,7 @@ namespace esphome::uponor_smatrix { -static const char *const TAG = "uponor_smatrix.sensor"; +ESPHOME_LOG_TAG(TAG, "uponor_smatrix.sensor"); void UponorSmatrixSensor::dump_config() { ESP_LOGCONFIG(TAG, diff --git a/esphome/components/uponor_smatrix/uponor_smatrix.cpp b/esphome/components/uponor_smatrix/uponor_smatrix.cpp index 74974548af9..fd6eda73a0c 100644 --- a/esphome/components/uponor_smatrix/uponor_smatrix.cpp +++ b/esphome/components/uponor_smatrix/uponor_smatrix.cpp @@ -7,7 +7,7 @@ namespace esphome::uponor_smatrix { -static const char *const TAG = "uponor_smatrix"; +ESPHOME_LOG_TAG(TAG, "uponor_smatrix"); // Maximum bytes to log in verbose hex output static constexpr size_t UPONOR_MAX_LOG_BYTES = 36; diff --git a/esphome/components/uptime/__init__.py b/esphome/components/uptime/__init__.py index e69de29bb2d..74eb20c656e 100644 --- a/esphome/components/uptime/__init__.py +++ b/esphome/components/uptime/__init__.py @@ -0,0 +1 @@ +DOMAIN = "uptime" diff --git a/esphome/components/uptime/sensor/__init__.py b/esphome/components/uptime/sensor/__init__.py index a13d6cd7fd5..04140ba431d 100644 --- a/esphome/components/uptime/sensor/__init__.py +++ b/esphome/components/uptime/sensor/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import sensor, time +from esphome.components.time import DOMAIN as TIME_DOMAIN from esphome.config_helpers import filter_source_files_from_defines import esphome.config_validation as cv from esphome.const import ( @@ -44,7 +45,8 @@ CONFIG_SCHEMA = cv.typed_schema( cv.Schema( { cv.GenerateID(CONF_TIME_ID): cv.All( - cv.requires_component("time"), cv.use_id(time.RealTimeClock) + cv.requires_component(TIME_DOMAIN), + cv.use_id(time.RealTimeClock), ), } ) diff --git a/esphome/components/uptime/sensor/uptime_seconds_sensor.cpp b/esphome/components/uptime/sensor/uptime_seconds_sensor.cpp index 552e4e5da42..c5dbf742a16 100644 --- a/esphome/components/uptime/sensor/uptime_seconds_sensor.cpp +++ b/esphome/components/uptime/sensor/uptime_seconds_sensor.cpp @@ -5,7 +5,7 @@ namespace esphome::uptime { -static const char *const TAG = "uptime.sensor"; +ESPHOME_LOG_TAG(TAG, "uptime.sensor"); void UptimeSecondsSensor::update() { const uint64_t uptime = millis_64(); diff --git a/esphome/components/uptime/sensor/uptime_timestamp_sensor.cpp b/esphome/components/uptime/sensor/uptime_timestamp_sensor.cpp index e8b8a21562b..43c8d1cf752 100644 --- a/esphome/components/uptime/sensor/uptime_timestamp_sensor.cpp +++ b/esphome/components/uptime/sensor/uptime_timestamp_sensor.cpp @@ -8,7 +8,7 @@ namespace esphome::uptime { -static const char *const TAG = "uptime.sensor"; +ESPHOME_LOG_TAG(TAG, "uptime.sensor"); void UptimeTimestampSensor::setup() { this->time_->add_on_time_sync_callback([this]() { diff --git a/esphome/components/uptime/text_sensor/uptime_text_sensor.cpp b/esphome/components/uptime/text_sensor/uptime_text_sensor.cpp index 7a56654804b..33cde23b946 100644 --- a/esphome/components/uptime/text_sensor/uptime_text_sensor.cpp +++ b/esphome/components/uptime/text_sensor/uptime_text_sensor.cpp @@ -6,7 +6,7 @@ namespace esphome::uptime { -static const char *const TAG = "uptime.sensor"; +ESPHOME_LOG_TAG(TAG, "uptime.sensor"); static void append_unit(char *buf, size_t buf_size, size_t &pos, const char *separator, unsigned value, const char *label) { diff --git a/esphome/components/usb_cdc_acm/__init__.py b/esphome/components/usb_cdc_acm/__init__.py index 8cd078ab496..e49531c80bc 100644 --- a/esphome/components/usb_cdc_acm/__init__.py +++ b/esphome/components/usb_cdc_acm/__init__.py @@ -15,6 +15,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@kbx81"] AUTO_LOAD = ["uart"] DEPENDENCIES = ["tinyusb"] +DOMAIN = "usb_cdc_acm" CONF_INTERFACES = "interfaces" diff --git a/esphome/components/usb_cdc_acm/usb_cdc_acm.cpp b/esphome/components/usb_cdc_acm/usb_cdc_acm.cpp index 454c049da3c..f24339b4bf3 100644 --- a/esphome/components/usb_cdc_acm/usb_cdc_acm.cpp +++ b/esphome/components/usb_cdc_acm/usb_cdc_acm.cpp @@ -6,7 +6,7 @@ #include "esphome/core/log.h" namespace esphome::usb_cdc_acm { -static const char *const TAG = "usb_cdc_acm"; +ESPHOME_LOG_TAG(TAG, "usb_cdc_acm"); // Global component instance for managing USB device USBCDCACMComponent *global_usb_cdc_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp b/esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp index 7aa7b46b7b9..9ac94802e17 100644 --- a/esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp +++ b/esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp @@ -17,7 +17,7 @@ namespace esphome::usb_cdc_acm { -static const char *const TAG = "usb_cdc_acm"; +ESPHOME_LOG_TAG(TAG, "usb_cdc_acm"); // Maximum bytes to log in very verbose hex output (168 * 3 = 504, under TX buffer size of 512) static constexpr size_t USB_CDC_MAX_LOG_BYTES = 168; diff --git a/esphome/components/usb_host/__init__.py b/esphome/components/usb_host/__init__.py index 4abcc3a4499..317cec8236b 100644 --- a/esphome/components/usb_host/__init__.py +++ b/esphome/components/usb_host/__init__.py @@ -1,4 +1,8 @@ +import itertools +import logging + import esphome.codegen as cg +from esphome.components.const import CONF_MANUFACTURER from esphome.components.esp32 import ( VARIANT_ESP32H4, VARIANT_ESP32P4, @@ -15,8 +19,12 @@ from esphome.const import CONF_DEVICES, CONF_ID from esphome.core import CORE from esphome.cpp_generator import MockObj from esphome.cpp_types import Component +import esphome.final_validate as fv +from esphome.helpers import cpp_u16string_escape from esphome.types import ConfigType +_LOGGER = logging.getLogger(__name__) + AUTO_LOAD = ["bytebuffer"] CODEOWNERS = ["@clydebarrow"] DEPENDENCIES = ["esp32"] @@ -26,11 +34,21 @@ USBClient = usb_host_ns.class_("USBClient", Component) DOMAIN = "usb_host" CONF_VID = "vid" CONF_PID = "pid" +CONF_PRODUCT = "product" CONF_ENABLE_HUBS = "enable_hubs" CONF_MAX_TRANSFER_REQUESTS = "max_transfer_requests" CONF_MAX_PACKET_SIZE = "max_packet_size" +# VID/PID set to 0 or `None` product/manufacturer are wildcards +_FILTER_WILDCARDS = { + CONF_VID: 0, + CONF_PID: 0, + CONF_MANUFACTURER: None, + CONF_PRODUCT: None, +} + + def usb_device_schema( cls=USBClient, vid: int | None = None, pid: int | None = None ) -> cv.Schema: @@ -47,7 +65,62 @@ def usb_device_schema( schema = schema.extend({cv.Optional(CONF_PID, default=pid): cv.hex_uint16_t}) else: schema = schema.extend({cv.Required(CONF_PID): cv.hex_uint16_t}) - return schema + + return schema.extend( + { + cv.Optional(CONF_MANUFACTURER): cv.string_strict, + cv.Optional(CONF_PRODUCT): cv.string_strict, + } + ) + + +def _clients_overlap(first: ConfigType, second: ConfigType) -> bool: + # Two entries overlap when no field they both constrain tells them apart + for key, wildcard in _FILTER_WILDCARDS.items(): + a = first.get(key) + b = second.get(key) + if wildcard not in (a, b) and a != b: + return False + return True + + +def validate_usb_clients(configs: list[ConfigType]) -> list[ConfigType]: + for first, second in itertools.combinations(configs, 2): + if _clients_overlap(first, second): + raise cv.Invalid( + f"USB configs overlap: {first[CONF_ID]}, {second[CONF_ID]}" + ) + return configs + + +def _final_validate(config: ConfigType) -> ConfigType: + # Any two overlapping clients could otherwise open the same device + devices = config.get(CONF_DEVICES) or [] + uarts = fv.full_config.get().get("usb_uart") or [] + validate_usb_clients(devices) + validate_usb_clients(uarts) + # Remove before 2027.10.0, then pool devices and uarts in one validate_usb_clients call + for device, uart in itertools.product(devices, uarts): + if not _clients_overlap(device, uart): + continue + # The device matches nothing the usb_uart entry does not already claim + redundant = all( + uart.get(key) in (wildcard, device.get(key)) + for key, wildcard in _FILTER_WILDCARDS.items() + ) + _LOGGER.warning( + "usb_host device '%s' matches the same USB device as usb_uart '%s'; %s. " + "This will be an error in 2027.10.0", + device[CONF_ID], + uart[CONF_ID], + "remove it from usb_host devices" + if redundant + else "narrow its filter so it no longer matches that device", + ) + return config + + +FINAL_VALIDATE_SCHEMA = _final_validate def _set_max_packet_size(config: dict) -> dict: @@ -91,6 +164,15 @@ CONFIG_SCHEMA = cv.All( async def register_usb_client(config: ConfigType) -> MockObj: var = cg.new_Pvariable(config[CONF_ID], config[CONF_VID], config[CONF_PID]) await cg.register_component(var, config) + # UTF-16 literals, the encoding the descriptors use, so the device compares code units + if (manufacturer := config.get(CONF_MANUFACTURER)) is not None: + cg.add( + var.set_manufacturer_filter( + cg.RawExpression(cpp_u16string_escape(manufacturer)) + ) + ) + if (product := config.get(CONF_PRODUCT)) is not None: + cg.add(var.set_product_filter(cg.RawExpression(cpp_u16string_escape(product)))) return var diff --git a/esphome/components/usb_host/usb_host.h b/esphome/components/usb_host/usb_host.h index 42869fb2a6d..aaab8e22a18 100644 --- a/esphome/components/usb_host/usb_host.h +++ b/esphome/components/usb_host/usb_host.h @@ -5,6 +5,7 @@ defined(USE_ESP32_VARIANT_ESP32S31) || defined(USE_ESP32_VARIANT_ESP32H4) #include "esphome/core/defines.h" #include "esphome/core/component.h" +#include "esphome/core/helpers.h" #include #include "usb/usb_host.h" #include @@ -12,6 +13,7 @@ #include "esphome/core/lock_free_queue.h" #include "esphome/core/event_pool.h" #include +#include namespace esphome::usb_host { @@ -117,6 +119,25 @@ struct UsbEvent { // callback function type. +// USB string descriptors hold at most 126 characters; one more for the terminator +static constexpr size_t DESC_STRING_BUF_SIZE = 128; + +/// Identity of a connected USB device, copied out of the descriptors the USB host +/// stack caches for the lifetime of the connection +struct UsbDeviceInfo { + uint16_t vendor_id; + uint16_t product_id; + uint16_t bcd_device; + char manufacturer[DESC_STRING_BUF_SIZE]; + char product[DESC_STRING_BUF_SIZE]; + char serial_number[DESC_STRING_BUF_SIZE]; +}; + +/// Copy a USB string descriptor into a NUL-terminated buffer. A missing descriptor copies as +/// an empty string. Returns false when a descriptor contains non-ASCII characters, +/// UTF-16 to UTF-8 conversion is not currently implemented. +bool copy_descriptor_string(const usb_str_desc_t *desc, std::span buffer); + enum ClientState { USB_CLIENT_INIT = 0, USB_CLIENT_OPEN, @@ -143,6 +164,25 @@ class USBClient : public Component { trq_bitmask_t get_trq_in_use() const { return trq_in_use_; } bool control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const transfer_cb_t &callback, const std::vector &data = {}); + void set_manufacturer_filter(const char16_t *manufacturer) { this->manufacturer_filter_ = manufacturer; } + void set_product_filter(const char16_t *product) { this->product_filter_ = product; } + + /// Whether a device has been opened and its setup by the subclass has finished + bool is_connected() const { return this->connection_reported_; } + + /// Copy the connected device's identity out of the cached USB descriptors. + /// Returns false when no device is connected or the host stack refused the query. + bool get_device_info(UsbDeviceInfo &info) const; + + /// Register a callback for the device this client claims being connected (true) or + /// removed (false). Fires only for a device that was fully opened, so a device another + /// client claims is never reported. Called from the main loop: connected once the device + /// has been enumerated and the subclass has finished its setup of it (whether or not that + /// setup succeeded), removed after on_disconnected() has run. This tracks the device's + /// presence, not whether a given channel is usable. + template void add_on_connection_callback(F &&callback) { + this->connection_callback_.add(std::forward(callback)); + } // Lock-free event queue and pool for USB task to main loop communication // Must be public for access from static callbacks @@ -161,6 +201,13 @@ class USBClient : public Component { TransferRequest *get_trq_(); // Lock-free allocation using atomic bitmask (multi-consumer safe) virtual void disconnect(); virtual void on_connected() {} + + /// Whether the subclass reports the device as connected itself, once its own setup of + /// the device has finished, rather than as soon as the device has been opened + virtual bool reports_connection_itself() const { return false; } + /// Report the claimed device to the connection callbacks. Idempotent; a subclass that + /// reports itself calls this once the device is ready to use. + void report_connected_(); virtual void on_disconnected() { // Reset all requests to available (all bits to 0) this->trq_in_use_.store(0); @@ -177,12 +224,18 @@ class USBClient : public Component { usb_device_handle_t device_handle_{}; int device_addr_{-1}; int state_{USB_CLIENT_INIT}; + LazyCallbackManager connection_callback_; // Lock-free pool management using atomic bitmask (no dynamic allocation) // Bit i = 1: requests_[i] is in use, Bit i = 0: requests_[i] is available // Supports multiple concurrent consumers and producers (both threads can allocate/deallocate) std::atomic trq_in_use_; + const char16_t *manufacturer_filter_{nullptr}; + const char16_t *product_filter_{nullptr}; uint16_t vid_{}; uint16_t pid_{}; + // Whether the connection callbacks were told about the current device, so a removal is + // only ever reported for a device that was reported connected + bool connection_reported_{false}; }; class USBHost final : public Component { public: diff --git a/esphome/components/usb_host/usb_host_client.cpp b/esphome/components/usb_host/usb_host_client.cpp index 7bc2b0a16b7..1b50aa75baf 100644 --- a/esphome/components/usb_host/usb_host_client.cpp +++ b/esphome/components/usb_host/usb_host_client.cpp @@ -11,6 +11,8 @@ #include #include #include +#include + namespace esphome::usb_host { #pragma GCC diagnostic ignored "-Wparentheses" @@ -143,25 +145,92 @@ static void usb_client_print_config_descriptor(const usb_config_desc_t *cfg_desc } while (next_desc != NULL); } #endif -// USB string descriptors: bLength (uint8_t, max 255) includes the 2-byte header (bLength and bDescriptorType). -// Character count = (bLength - 2) / 2, max 126 chars + null terminator. -static constexpr size_t DESC_STRING_BUF_SIZE = 128; - -static const char *get_descriptor_string(const usb_str_desc_t *desc, std::span buffer) { +// bLength (uint8_t, max 255) includes the 2-byte header (bLength and bDescriptorType), +// so character count = (bLength - 2) / 2. +bool copy_descriptor_string(const usb_str_desc_t *desc, std::span buffer) { + buffer[0] = '\0'; if (desc == nullptr || desc->bLength < 2) - return "(unspecified)"; + return true; int char_count = (desc->bLength - 2) / 2; char *p = buffer.data(); char *end = p + buffer.size() - 1; for (int i = 0; i != char_count && p < end; i++) { auto c = desc->wData[i]; - if (c < 0x100) - *p++ = static_cast(c); + // TODO: encode non-ASCII code units as UTF-8 if a device with such descriptors turns up + if (c >= 0x80) { + buffer[0] = '\0'; + return false; + } + *p++ = static_cast(c); + } + *p = '\0'; + return true; +} + +// Folds UTF-16 to Latin-1 for logging, dropping anything that does not fit +template +static const char *utf16_to_latin1(const T *data, size_t count, std::span buffer) { + char *p = buffer.data(); + char *end = p + buffer.size() - 1; + for (size_t i = 0; i != count && p < end; i++) { + if (data[i] < 0x100) + *p++ = static_cast(data[i]); } *p = '\0'; return buffer.data(); } +static const char *get_descriptor_string(const usb_str_desc_t *desc, std::span buffer) { + if (desc == nullptr || desc->bLength < 2) + return "(unspecified)"; + return utf16_to_latin1(desc->wData, (desc->bLength - 2) / 2, buffer); +} + +static const char *filter_string(const char16_t *filter, std::span buffer) { + return utf16_to_latin1(filter, std::char_traits::length(filter), buffer); +} + +// Both sides are UTF-16: the descriptor by specification, the filter because code +// generation emits it as a u"" literal +static bool descriptor_string_equals(const usb_str_desc_t *desc, const char16_t *expected) { + const int char_count = (desc == nullptr || desc->bLength < 2) ? 0 : (desc->bLength - 2) / 2; + for (int i = 0; i != char_count; i++) { + if (expected[i] == u'\0' || desc->wData[i] != expected[i]) + return false; + } + return expected[char_count] == u'\0'; +} + +bool USBClient::get_device_info(UsbDeviceInfo &info) const { + if (!this->is_connected()) + return false; + const usb_device_desc_t *desc; + esp_err_t err = usb_host_get_device_descriptor(this->device_handle_, &desc); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Device descriptor query failed: %s", esp_err_to_name(err)); + return false; + } + info.vendor_id = desc->idVendor; + info.product_id = desc->idProduct; + info.bcd_device = desc->bcdDevice; + usb_device_info_t dev_info; + err = usb_host_device_info(this->device_handle_, &dev_info); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Device info query failed: %s", esp_err_to_name(err)); + return false; + } + if (!copy_descriptor_string(dev_info.str_desc_manufacturer, info.manufacturer)) { + ESP_LOGW(TAG, "Manufacturer string descriptor is not ASCII"); + } + if (!copy_descriptor_string(dev_info.str_desc_product, info.product)) { + ESP_LOGW(TAG, "Product string descriptor is not ASCII"); + } + if (!copy_descriptor_string(dev_info.str_desc_serial_num, info.serial_number)) { + ESP_LOGW(TAG, "Serial number string descriptor is not ASCII"); + } + return true; +} + // CALLBACK CONTEXT: USB task (called from usb_host_client_handle_events in USB task) static void client_event_cb(const usb_host_client_event_msg_t *event_msg, void *ptr) { auto *client = static_cast(ptr); @@ -302,12 +371,10 @@ void USBClient::handle_open_state_() { return; } ESP_LOGD(TAG, "Device descriptor: vid %X pid %X", desc->idVendor, desc->idProduct); - if (desc->idVendor != this->vid_ || desc->idProduct != this->pid_) { - if (this->vid_ != 0 || this->pid_ != 0) { - ESP_LOGD(TAG, "Not our device, closing"); - this->disconnect(); - return; - } + if ((this->vid_ != 0 && desc->idVendor != this->vid_) || (this->pid_ != 0 && desc->idProduct != this->pid_)) { + ESP_LOGD(TAG, "Not our device, closing"); + this->disconnect(); + return; } usb_device_info_t dev_info; err = usb_host_device_info(this->device_handle_, &dev_info); @@ -316,9 +383,21 @@ void USBClient::handle_open_state_() { this->disconnect(); return; } - this->state_ = USB_CLIENT_CONNECTED; char buf_manuf[DESC_STRING_BUF_SIZE]; char buf_product[DESC_STRING_BUF_SIZE]; + const bool manufacturer_matches = + this->manufacturer_filter_ == nullptr || + descriptor_string_equals(dev_info.str_desc_manufacturer, this->manufacturer_filter_); + const bool product_matches = + this->product_filter_ == nullptr || descriptor_string_equals(dev_info.str_desc_product, this->product_filter_); + if (!manufacturer_matches || !product_matches) { + ESP_LOGD(TAG, "Device does not match filter, closing. Manuf: %s; Prod: %s", + get_descriptor_string(dev_info.str_desc_manufacturer, buf_manuf), + get_descriptor_string(dev_info.str_desc_product, buf_product)); + this->disconnect(); + return; + } + this->state_ = USB_CLIENT_CONNECTED; char buf_serial[DESC_STRING_BUF_SIZE]; ESP_LOGD(TAG, "Device connected: Manuf: %s; Prod: %s; Serial: %s", get_descriptor_string(dev_info.str_desc_manufacturer, buf_manuf), @@ -336,6 +415,18 @@ void USBClient::handle_open_state_() { usb_client_print_config_descriptor(config_desc, nullptr); #endif this->on_connected(); + // on_connected() may have rejected the device (no usable interface, say) and closed it + if (this->state_ == USB_CLIENT_CONNECTED && !this->reports_connection_itself()) { + this->report_connected_(); + } +} + +void USBClient::report_connected_() { + if (this->state_ != USB_CLIENT_CONNECTED || this->connection_reported_) { + return; + } + this->connection_reported_ = true; + this->connection_callback_.call(true); } void USBClient::on_opened(uint8_t addr) { @@ -406,6 +497,10 @@ TransferRequest *USBClient::get_trq_() { } void USBClient::disconnect() { + // Also reached for a device this client opened and then declined, or lost before it was + // ready; neither was reported as connected, so neither is reported as removed + const bool was_reported = this->connection_reported_; + this->connection_reported_ = false; this->on_disconnected(); auto err = usb_host_device_close(this->handle_, this->device_handle_); if (err != ESP_OK) { @@ -414,6 +509,9 @@ void USBClient::disconnect() { this->state_ = USB_CLIENT_INIT; this->device_handle_ = nullptr; this->device_addr_ = -1; + if (was_reported) { + this->connection_callback_.call(false); + } } // THREAD CONTEXT: Called from main loop thread only @@ -557,6 +655,13 @@ void USBClient::dump_config() { " Vendor id %04X\n" " Product id %04X", this->vid_, this->pid_); + char buf[DESC_STRING_BUF_SIZE]; + if (this->manufacturer_filter_ != nullptr) { + ESP_LOGCONFIG(TAG, " Manufacturer %s", filter_string(this->manufacturer_filter_, buf)); + } + if (this->product_filter_ != nullptr) { + ESP_LOGCONFIG(TAG, " Product %s", filter_string(this->product_filter_, buf)); + } } // THREAD CONTEXT: Called from both USB task and main loop threads // - USB task: Immediately after transfer callback completes diff --git a/esphome/components/usb_uart/__init__.py b/esphome/components/usb_uart/__init__.py index 5d0f8be1655..2898576b323 100644 --- a/esphome/components/usb_uart/__init__.py +++ b/esphome/components/usb_uart/__init__.py @@ -7,6 +7,7 @@ from esphome.components.usb_host import ( register_usb_client, usb_device_schema, ) +from esphome.config_helpers import filter_source_files_from_defines import esphome.config_validation as cv from esphome.const import ( CONF_BAUD_RATE, @@ -15,18 +16,29 @@ from esphome.const import ( CONF_DEBUG, CONF_DUMMY_RECEIVER, CONF_ID, + CONF_TYPE, ) -from esphome.core import CORE +from esphome.core import CORE, ID from esphome.cpp_types import Component from esphome.types import ConfigType AUTO_LOAD = ["uart", "usb_host", "bytebuffer"] CODEOWNERS = ["@clydebarrow"] +DOMAIN = "usb_uart" usb_uart_ns = cg.esphome_ns.namespace("usb_uart") USBUartComponent = usb_uart_ns.class_("USBUartComponent", Component) USBUartChannel = usb_uart_ns.class_("USBUartChannel", UARTComponent) + +def is_usb_uart_channel(uart_id: ID, full_config: ConfigType) -> bool: + return any( + channel[CONF_ID] == uart_id + for device in full_config.get(DOMAIN) or [] + for channel in device[CONF_CHANNELS] + ) + + UARTParityOptions = usb_uart_ns.enum("UARTParityOptions") UART_PARITY_OPTIONS = { "NONE": UARTParityOptions.UART_CONFIG_PARITY_NONE, @@ -64,6 +76,8 @@ class Type: self.vid = vid self.pid = pid self.cls = usb_uart_ns.class_(f"USBUartType{cls}", USBUartComponent) + # CDC ACM lives in usb_uart.cpp; each vendor driver has its own .cpp + self.driver = None if cls == "CdcAcm" else cls self._max_channels = max_channels self.baud_rate_required = baud_rate_required self.max_baud = max_baud @@ -121,6 +135,21 @@ uart_types = ( ), ) +_TYPES_BY_NAME = {it.name: it for it in uart_types} + + +def _driver_define(driver: str) -> str: + return f"USE_USB_UART_{driver}" + + +FILTER_SOURCE_FILES = filter_source_files_from_defines( + { + f"{it.driver.lower()}.cpp": _driver_define(it.driver) + for it in uart_types + if it.driver is not None + } +) + def channel_schema(type_: "Type") -> cv.Schema: schema = cv.Schema( @@ -211,6 +240,8 @@ async def to_code(config: list[ConfigType]) -> None: cg.add_define("USB_UART_OUTPUT_CHUNK_COUNT", output_chunk_count) for device in config: + if (driver := _TYPES_BY_NAME[device[CONF_TYPE]].driver) is not None: + cg.add_define(_driver_define(driver)) var = await register_usb_client(device) # The C++ default is true; only emit the override if not device.get(CONF_CLAIM_COMM_INTERFACE, True): diff --git a/esphome/components/usb_uart/usb_uart.cpp b/esphome/components/usb_uart/usb_uart.cpp index 3113f695f60..86dfa6d729f 100644 --- a/esphome/components/usb_uart/usb_uart.cpp +++ b/esphome/components/usb_uart/usb_uart.cpp @@ -21,8 +21,6 @@ static optional get_cdc(const usb_config_desc_t *config_desc, uint8_t in int conf_offset, ep_offset; // look for an interface with an interrupt endpoint (notify), and one with two bulk endpoints (data in/out) CdcEps eps{}; - eps.bulk_interface_number = 0xFF; - eps.interrupt_interface_number = 0xFF; for (;;) { const auto *intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx++, 0, &conf_offset); if (!intf_desc) { @@ -463,10 +461,12 @@ void USBUartTypeCdcAcm::on_connected() { void USBUartTypeCdcAcm::on_disconnected() { for (auto *channel : this->channels_) { - if (channel->cdc_dev_.in_ep != nullptr) { - usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress); - usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress); - } + // Not set up for this device: it was rejected before on_connected() ran, or it has + // fewer ports than there are channels. Nothing was claimed for it. + if (channel->cdc_dev_.in_ep == nullptr) + continue; + usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress); + usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress); if (channel->cdc_dev_.out_ep != nullptr) { usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress); usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress); @@ -494,6 +494,8 @@ void USBUartTypeCdcAcm::on_disconnected() { } } channel->initialised_.store(false); + // The descriptors these point into are freed with the device + channel->cdc_dev_ = {}; } USBClient::on_disconnected(); } @@ -660,6 +662,8 @@ bool USBUartComponent::run_config_machine_() { this->cfg_single_ = nullptr; } else if (++this->cfg_channel_idx_ >= this->channels_.size()) { this->cfg_active_ = false; + // Init is done and the line settings are on the wire: now the device is ready to use + this->report_connected_(); } // If the machine just went idle and a reload was requested while it was busy, start it now. diff --git a/esphome/components/usb_uart/usb_uart.h b/esphome/components/usb_uart/usb_uart.h index 22563209da3..c6ae0b4eef5 100644 --- a/esphome/components/usb_uart/usb_uart.h +++ b/esphome/components/usb_uart/usb_uart.h @@ -33,10 +33,11 @@ struct CdcEps { const usb_ep_desc_t *notify_ep; const usb_ep_desc_t *in_ep; const usb_ep_desc_t *out_ep; - uint8_t bulk_interface_number; + // 0xFF marks a channel that was never matched to a CDC function on the device + uint8_t bulk_interface_number{0xFF}; // Also the wIndex target for CDC class requests (SET_LINE_CODING etc.), so it // must remain valid even when the interface itself is not claimed. - uint8_t interrupt_interface_number; + uint8_t interrupt_interface_number{0xFF}; bool interrupt_interface_claimed{false}; }; @@ -155,7 +156,22 @@ class USBUartChannelBase : public uart::UARTComponent, public Parentedparity_ = parity; } + void set_parity(UARTParityOptions parity) { + this->parity_ = parity; + // Keep the base-class parity in sync so uart::UARTComponent::get_parity() reports the configured value. + // MARK/SPACE have no uart:: equivalent and report as NONE. + switch (parity) { + case UART_CONFIG_PARITY_EVEN: + uart::UARTComponent::set_parity(uart::UART_CONFIG_PARITY_EVEN); + break; + case UART_CONFIG_PARITY_ODD: + uart::UARTComponent::set_parity(uart::UART_CONFIG_PARITY_ODD); + break; + default: + uart::UARTComponent::set_parity(uart::UART_CONFIG_PARITY_NONE); + break; + } + } void set_debug(bool debug) { this->debug_ = debug; } void set_dummy_receiver(bool dummy_receiver) { this->dummy_receiver_ = dummy_receiver; } void set_debug_prefix(const char *prefix) { this->debug_prefix_ = StringRef(prefix); } @@ -167,6 +183,13 @@ class USBUartChannelBase : public uart::UARTComponent, public Parented cb) { this->rx_callback_ = std::move(cb); } + /// USB interface number a host driver binds to for this channel: the communication + /// interface of a CDC ACM function, otherwise the data interface. + uint8_t get_interface_number() const { + return this->cdc_dev_.interrupt_interface_number != 0xFF ? this->cdc_dev_.interrupt_interface_number + : this->cdc_dev_.bulk_interface_number; + } + protected: // Not directly instantiable; construct a concrete channel type instead. USBUartChannelBase(uint8_t index, uint16_t buffer_size) : input_buffer_(RingBuffer(buffer_size)), index_(index) {} @@ -252,6 +275,10 @@ class USBUartComponent : public usb_host::USBClient { // (e.g. CH34x chip detection). Same contract as config_step_(). Default: no steps. virtual bool config_device_step(uint8_t step, bool ok, const uint8_t *response) { return false; } + // The device is only usable once the config machine has applied every channel's line + // settings, so the connected report waits for run_config_machine_() to finish the init + bool reports_connection_itself() const override { return true; } + std::vector channels_{}; // Config state machine diff --git a/esphome/components/valve/__init__.py b/esphome/components/valve/__init__.py index 7df58c7a2aa..41e25d63b15 100644 --- a/esphome/components/valve/__init__.py +++ b/esphome/components/valve/__init__.py @@ -15,7 +15,6 @@ from esphome.const import ( CONF_POSITION_STATE_TOPIC, CONF_STATE, CONF_STOP, - CONF_TRIGGER_ID, CONF_WEB_SERVER, DEVICE_CLASS_EMPTY, DEVICE_CLASS_GAS, @@ -33,6 +32,7 @@ from esphome.cpp_generator import MockObjClass IS_PLATFORM_COMPONENT = True CODEOWNERS = ["@esphome/core"] +DOMAIN = "valve" DEVICE_CLASSES = [ DEVICE_CLASS_EMPTY, @@ -61,17 +61,18 @@ VALVE_OPERATIONS = { } validate_valve_operation = cv.enum(VALVE_OPERATIONS, upper=True) -# Actions -ValvePublishAction = valve_ns.class_("ValvePublishAction", automation.Action) - -# Triggers -ValveOpenTrigger = valve_ns.class_("ValveOpenTrigger", automation.Trigger.template()) -ValveClosedTrigger = valve_ns.class_( - "ValveClosedTrigger", automation.Trigger.template() -) - CONF_ON_CLOSED = "on_closed" +# The state callback carries nothing; each automation fires only in its end state. +_CALLBACK_AUTOMATIONS = ( + automation.CallbackAutomation( + CONF_ON_OPEN, "add_on_state_callback", when="{parent}->is_fully_open()" + ), + automation.CallbackAutomation( + CONF_ON_CLOSED, "add_on_state_callback", when="{parent}->is_fully_closed()" + ), +) + _VALVE_SCHEMA = ( cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) @@ -88,16 +89,8 @@ _VALVE_SCHEMA = ( cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All( cv.requires_component("mqtt"), cv.subscribe_topic ), - cv.Optional(CONF_ON_OPEN): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveOpenTrigger), - } - ), - cv.Optional(CONF_ON_CLOSED): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveClosedTrigger), - } - ), + cv.Optional(CONF_ON_OPEN): automation.validate_automation({}), + cv.Optional(CONF_ON_CLOSED): automation.validate_automation({}), } ) ) @@ -133,12 +126,7 @@ def valve_schema( async def _setup_valve_core(var, config): setup_device_class(config) - for conf in config.get(CONF_ON_OPEN, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) - for conf in config.get(CONF_ON_CLOSED, []): - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) - await automation.build_automation(trigger, [], conf) + await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS) if mqtt_id_config := config.get(CONF_MQTT_ID): mqtt_ = cg.new_Pvariable(mqtt_id_config, var) diff --git a/esphome/components/valve/automation.h b/esphome/components/valve/automation.h deleted file mode 100644 index 598bbc1678e..00000000000 --- a/esphome/components/valve/automation.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include "esphome/core/automation.h" -#include "esphome/core/component.h" -#include "valve.h" - -namespace esphome::valve { - -class ValveOpenTrigger final : public Trigger<> { - public: - ValveOpenTrigger(Valve *a_valve) : valve_(a_valve) { - a_valve->add_on_state_callback([this]() { - if (this->valve_->is_fully_open()) { - this->trigger(); - } - }); - } - - protected: - Valve *valve_; -}; - -class ValveClosedTrigger final : public Trigger<> { - public: - ValveClosedTrigger(Valve *a_valve) : valve_(a_valve) { - a_valve->add_on_state_callback([this]() { - if (this->valve_->is_fully_closed()) { - this->trigger(); - } - }); - } - - protected: - Valve *valve_; -}; - -} // namespace esphome::valve diff --git a/esphome/components/valve/valve.cpp b/esphome/components/valve/valve.cpp index d8fb18b1b72..a5ad9817df7 100644 --- a/esphome/components/valve/valve.cpp +++ b/esphome/components/valve/valve.cpp @@ -8,7 +8,7 @@ namespace esphome::valve { -static const char *const TAG = "valve"; +ESPHOME_LOG_TAG(TAG, "valve"); const float VALVE_OPEN = 1.0f; const float VALVE_CLOSED = 0.0f; diff --git a/esphome/components/vbus/__init__.py b/esphome/components/vbus/__init__.py index fd546589121..533acfbbb92 100644 --- a/esphome/components/vbus/__init__.py +++ b/esphome/components/vbus/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_ID from esphome.types import ConfigType CODEOWNERS = ["@ssieb"] +DOMAIN = "vbus" DEPENDENCIES = ["uart"] diff --git a/esphome/components/vbus/binary_sensor/vbus_binary_sensor.cpp b/esphome/components/vbus/binary_sensor/vbus_binary_sensor.cpp index ddb6b530686..60822c52833 100644 --- a/esphome/components/vbus/binary_sensor/vbus_binary_sensor.cpp +++ b/esphome/components/vbus/binary_sensor/vbus_binary_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::vbus { -static const char *const TAG = "vbus.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "vbus.binary_sensor"); void DeltaSolBSPlusBSensor::dump_config() { ESP_LOGCONFIG(TAG, "Deltasol BS Plus:"); diff --git a/esphome/components/vbus/sensor/vbus_sensor.cpp b/esphome/components/vbus/sensor/vbus_sensor.cpp index 773e1435e29..5c111c22942 100644 --- a/esphome/components/vbus/sensor/vbus_sensor.cpp +++ b/esphome/components/vbus/sensor/vbus_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::vbus { -static const char *const TAG = "vbus.sensor"; +ESPHOME_LOG_TAG(TAG, "vbus.sensor"); static inline uint16_t get_u16(std::vector &message, int start) { return (message[start + 1] << 8) + message[start]; diff --git a/esphome/components/vbus/vbus.cpp b/esphome/components/vbus/vbus.cpp index 080567e7f95..6b6a3393ee4 100644 --- a/esphome/components/vbus/vbus.cpp +++ b/esphome/components/vbus/vbus.cpp @@ -6,7 +6,7 @@ namespace esphome::vbus { -static const char *const TAG = "vbus"; +ESPHOME_LOG_TAG(TAG, "vbus"); // Maximum bytes to log in verbose hex output (16 frames * 4 bytes = 64 bytes typical) static constexpr size_t VBUS_MAX_LOG_BYTES = 64; diff --git a/esphome/components/veml3235/__init__.py b/esphome/components/veml3235/__init__.py index e69de29bb2d..57c95f46ef2 100644 --- a/esphome/components/veml3235/__init__.py +++ b/esphome/components/veml3235/__init__.py @@ -0,0 +1 @@ +DOMAIN = "veml3235" diff --git a/esphome/components/veml3235/veml3235.cpp b/esphome/components/veml3235/veml3235.cpp index b3170469b91..3a856696782 100644 --- a/esphome/components/veml3235/veml3235.cpp +++ b/esphome/components/veml3235/veml3235.cpp @@ -4,7 +4,9 @@ namespace esphome::veml3235 { -static const char *const TAG = "veml3235.sensor"; +ESPHOME_LOG_TAG(TAG, "veml3235.sensor"); + +static constexpr uint32_t REREAD_TIMEOUT_ID = 0; // ADC counts at or above this value (98% of full scale) are treated as clipped: the true light level cannot // be estimated from such a reading, so auto-gain restarts from minimum sensitivity instead @@ -71,7 +73,7 @@ void VEML3235Sensor::read_and_publish_(uint8_t adjustments_left) { const uint32_t old_integration_time_ms = this->integration_time_ms_(); if (this->adjust_sensitivity_(als_counts)) { const uint32_t wait_ms = old_integration_time_ms + 2 * this->integration_time_ms_(); - this->set_timeout("reread", wait_ms, + this->set_timeout(REREAD_TIMEOUT_ID, wait_ms, [this, adjustments_left]() { this->read_and_publish_(adjustments_left - 1); }); return; } diff --git a/esphome/components/veml7700/__init__.py b/esphome/components/veml7700/__init__.py index dd06cfffea0..9c8f1442d7d 100644 --- a/esphome/components/veml7700/__init__.py +++ b/esphome/components/veml7700/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@latonita"] +DOMAIN = "veml7700" diff --git a/esphome/components/veml7700/veml7700.cpp b/esphome/components/veml7700/veml7700.cpp index 6c609f4fe7c..236b4bb2a98 100644 --- a/esphome/components/veml7700/veml7700.cpp +++ b/esphome/components/veml7700/veml7700.cpp @@ -5,7 +5,7 @@ namespace esphome::veml7700 { -static const char *const TAG = "veml7700"; +ESPHOME_LOG_TAG(TAG, "veml7700"); static const size_t VEML_REG_SIZE = 2; static float reduce_to_zero(float a, float b) { return (a > b) ? (a - b) : 0; } diff --git a/esphome/components/version/__init__.py b/esphome/components/version/__init__.py index f70ffa95208..f9c04b25966 100644 --- a/esphome/components/version/__init__.py +++ b/esphome/components/version/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@esphome/core"] +DOMAIN = "version" diff --git a/esphome/components/version/text_sensor.py b/esphome/components/version/text_sensor.py index cad239dc415..189d18256f1 100644 --- a/esphome/components/version/text_sensor.py +++ b/esphome/components/version/text_sensor.py @@ -35,5 +35,8 @@ CONFIG_SCHEMA = ( async def to_code(config: ConfigType) -> None: var = await text_sensor.new_text_sensor(config) await cg.register_component(var, config) - cg.add(var.set_hide_hash(config[CONF_HIDE_HASH])) - cg.add(var.set_hide_timestamp(config[CONF_HIDE_TIMESTAMP])) + # Both flags default to false in C++, so only true needs a setter. + if config[CONF_HIDE_HASH]: + cg.add(var.set_hide_hash(True)) + if config[CONF_HIDE_TIMESTAMP]: + cg.add(var.set_hide_timestamp(True)) diff --git a/esphome/components/version/version_text_sensor.cpp b/esphome/components/version/version_text_sensor.cpp index 15e6b0d0883..1be7920a4c3 100644 --- a/esphome/components/version/version_text_sensor.cpp +++ b/esphome/components/version/version_text_sensor.cpp @@ -7,7 +7,7 @@ namespace esphome::version { -static const char *const TAG = "version.text_sensor"; +ESPHOME_LOG_TAG(TAG, "version.text_sensor"); void VersionTextSensor::setup() { static const char HASH_PREFIX[] PROGMEM = ESPHOME_VERSION " (config hash 0x"; diff --git a/esphome/components/version/version_text_sensor.h b/esphome/components/version/version_text_sensor.h index 7ff6ac4d352..85374350790 100644 --- a/esphome/components/version/version_text_sensor.h +++ b/esphome/components/version/version_text_sensor.h @@ -15,8 +15,8 @@ class VersionTextSensor final : public text_sensor::TextSensor, public Component void dump_config() override; protected: - bool hide_hash_{false}; - bool hide_timestamp_{false}; + bool hide_hash_{false}; // Must match the setter check in text_sensor.py + bool hide_timestamp_{false}; // Must match the setter check in text_sensor.py }; } // namespace esphome::version diff --git a/esphome/components/vl53l0x/__init__.py b/esphome/components/vl53l0x/__init__.py index e69de29bb2d..3363ba99618 100644 --- a/esphome/components/vl53l0x/__init__.py +++ b/esphome/components/vl53l0x/__init__.py @@ -0,0 +1 @@ +DOMAIN = "vl53l0x" diff --git a/esphome/components/vl53l0x/vl53l0x_sensor.cpp b/esphome/components/vl53l0x/vl53l0x_sensor.cpp index 49eb3d00a10..2e3d1c85299 100644 --- a/esphome/components/vl53l0x/vl53l0x_sensor.cpp +++ b/esphome/components/vl53l0x/vl53l0x_sensor.cpp @@ -14,7 +14,7 @@ namespace esphome::vl53l0x { -static const char *const TAG = "vl53l0x"; +ESPHOME_LOG_TAG(TAG, "vl53l0x"); std::list VL53L0XSensor::vl53_sensors; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) bool VL53L0XSensor::enable_pin_setup_complete = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -265,7 +265,7 @@ void VL53L0XSensor::setup() { void VL53L0XSensor::update() { if (this->initiated_read_ || this->waiting_for_interrupt_) { this->publish_state(NAN); - this->status_momentary_warning("update", 5000); + this->status_momentary_warning(5000); ESP_LOGW(TAG, "%s - update called before prior reading complete - initiated:%d waiting_for_interrupt:%d", this->name_.c_str(), this->initiated_read_, this->waiting_for_interrupt_); return; diff --git a/esphome/components/voice_assistant/__init__.py b/esphome/components/voice_assistant/__init__.py index 14c036de8ee..d306947ec13 100644 --- a/esphome/components/voice_assistant/__init__.py +++ b/esphome/components/voice_assistant/__init__.py @@ -19,6 +19,7 @@ AUTO_LOAD = ["audio", "ring_buffer", "socket"] DEPENDENCIES = ["api", "microphone"] CODEOWNERS = ["@jesserockz", "@kahrendt"] +DOMAIN = "voice_assistant" CONF_ON_END = "on_end" CONF_ON_INTENT_END = "on_intent_end" diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index dba9b925d00..039410abdbf 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -12,7 +12,12 @@ namespace esphome::voice_assistant { -static const char *const TAG = "voice_assistant"; +ESPHOME_LOG_TAG(TAG, "voice_assistant"); + +static constexpr uint32_t RESET_CONVERSATION_TIMEOUT_ID = 0; +static constexpr uint32_t PLAYBACK_TIMEOUT_ID = 1; +static constexpr uint32_t SPEAKER_IDLE_TIMEOUT_ID = 2; +static constexpr uint32_t TIMER_TICK_INTERVAL_ID = 3; #ifdef SAMPLE_RATE_HZ #undef SAMPLE_RATE_HZ @@ -370,7 +375,7 @@ void VoiceAssistant::loop() { break; } this->set_state_(State::STARTING_PIPELINE); - this->set_timeout("reset-conversation_id", this->conversation_timeout_, + this->set_timeout(RESET_CONVERSATION_TIMEOUT_ID, this->conversation_timeout_, [this]() { this->reset_conversation_id(); }); break; } @@ -460,10 +465,10 @@ void VoiceAssistant::loop() { if (this->speaker_bytes_received_ > RECEIVE_SIZE * 4 || end_of_stream) this->write_speaker_(); if (this->wait_for_stream_end_) { - this->cancel_timeout("playing"); + this->cancel_timeout(PLAYBACK_TIMEOUT_ID); if (end_of_stream) { ESP_LOGD(TAG, "End of audio stream received"); - this->cancel_timeout("speaker-timeout"); + this->cancel_timeout(SPEAKER_IDLE_TIMEOUT_ID); this->set_state_(State::RESPONSE_FINISHED, State::RESPONSE_FINISHED); } break; // We dont want to timeout here as the STREAM_END event will take care of that. @@ -477,7 +482,7 @@ void VoiceAssistant::loop() { if (this->media_player_response_state_ == MediaPlayerResponseState::FINISHED) { this->media_player_response_state_ = MediaPlayerResponseState::IDLE; - this->cancel_timeout("playing"); + this->cancel_timeout(PLAYBACK_TIMEOUT_ID); ESP_LOGD(TAG, "Announcement finished playing"); this->set_state_(State::RESPONSE_FINISHED, State::RESPONSE_FINISHED); @@ -507,8 +512,8 @@ void VoiceAssistant::loop() { } ESP_LOGD(TAG, "Speaker has finished outputting all audio"); this->speaker_->stop(); - this->cancel_timeout("speaker-timeout"); - this->cancel_timeout("playing"); + this->cancel_timeout(SPEAKER_IDLE_TIMEOUT_ID); + this->cancel_timeout(PLAYBACK_TIMEOUT_ID); this->clear_buffers_(); @@ -540,7 +545,7 @@ void VoiceAssistant::write_speaker_() { memmove(this->speaker_buffer_, this->speaker_buffer_ + written, this->speaker_buffer_size_ - written); this->speaker_buffer_size_ -= written; this->speaker_buffer_index_ -= written; - this->set_timeout("speaker-timeout", 5000, [this]() { this->speaker_->stop(); }); + this->set_timeout(SPEAKER_IDLE_TIMEOUT_ID, 5000, [this]() { this->speaker_->stop(); }); } else { ESP_LOGV(TAG, "Speaker buffer full, trying again next loop"); } @@ -755,8 +760,8 @@ void VoiceAssistant::signal_stop_() { } void VoiceAssistant::start_playback_timeout_() { - this->set_timeout("playing", 2000, [this]() { - this->cancel_timeout("speaker-timeout"); + this->set_timeout(PLAYBACK_TIMEOUT_ID, 2000, [this]() { + this->cancel_timeout(SPEAKER_IDLE_TIMEOUT_ID); this->set_state_(State::RESPONSE_FINISHED, State::RESPONSE_FINISHED); if (this->api_client_ == nullptr) @@ -1046,10 +1051,10 @@ void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse } if (this->timers_.empty()) { - this->cancel_interval("timer-event"); + this->cancel_interval(TIMER_TICK_INTERVAL_ID); this->timer_tick_running_ = false; } else if (!this->timer_tick_running_) { - this->set_interval("timer-event", 1000, [this]() { this->timer_tick_(); }); + this->set_interval(TIMER_TICK_INTERVAL_ID, 1000, [this]() { this->timer_tick_(); }); this->timer_tick_running_ = true; } } diff --git a/esphome/components/voltage_sampler/__init__.py b/esphome/components/voltage_sampler/__init__.py index e60918096ef..1dad6463229 100644 --- a/esphome/components/voltage_sampler/__init__.py +++ b/esphome/components/voltage_sampler/__init__.py @@ -1,4 +1,6 @@ import esphome.codegen as cg +DOMAIN = "voltage_sampler" + voltage_sampler_ns = cg.esphome_ns.namespace("voltage_sampler") VoltageSampler = voltage_sampler_ns.class_("VoltageSampler") diff --git a/esphome/components/wake_on_lan/__init__.py b/esphome/components/wake_on_lan/__init__.py index 90539e5d3c3..ad052dd85e6 100644 --- a/esphome/components/wake_on_lan/__init__.py +++ b/esphome/components/wake_on_lan/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@willwill2will54", "@clydebarrow"] +DOMAIN = "wake_on_lan" diff --git a/esphome/components/wake_on_lan/wake_on_lan.cpp b/esphome/components/wake_on_lan/wake_on_lan.cpp index fd94f56b755..52dfe98aad7 100644 --- a/esphome/components/wake_on_lan/wake_on_lan.cpp +++ b/esphome/components/wake_on_lan/wake_on_lan.cpp @@ -6,7 +6,7 @@ namespace esphome::wake_on_lan { -static const char *const TAG = "wake_on_lan.button"; +ESPHOME_LOG_TAG(TAG, "wake_on_lan.button"); static const uint8_t PREFIX[6] = {255, 255, 255, 255, 255, 255}; void WakeOnLanButton::set_macaddr(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f) { diff --git a/esphome/components/watchdog/__init__.py b/esphome/components/watchdog/__init__.py index 6fb87e45a46..2608ff7c57c 100644 --- a/esphome/components/watchdog/__init__.py +++ b/esphome/components/watchdog/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@oarcher"] +DOMAIN = "watchdog" diff --git a/esphome/components/watchdog/watchdog.cpp b/esphome/components/watchdog/watchdog.cpp index 2063faeb91e..61c0baf6863 100644 --- a/esphome/components/watchdog/watchdog.cpp +++ b/esphome/components/watchdog/watchdog.cpp @@ -16,7 +16,7 @@ namespace esphome::watchdog { -static const char *const TAG = "http_request.watchdog"; +ESPHOME_LOG_TAG(TAG, "http_request.watchdog"); WatchdogManager::WatchdogManager(uint32_t timeout_ms) : timeout_ms_(timeout_ms) { if (timeout_ms == 0) { diff --git a/esphome/components/water_heater/__init__.py b/esphome/components/water_heater/__init__.py index 6bb2b2f8fe3..b5821e55af5 100644 --- a/esphome/components/water_heater/__init__.py +++ b/esphome/components/water_heater/__init__.py @@ -18,6 +18,7 @@ from esphome.cpp_generator import MockObjClass from esphome.types import ConfigType CODEOWNERS = ["@dhoeben"] +DOMAIN = "water_heater" IS_PLATFORM_COMPONENT = True diff --git a/esphome/components/water_heater/water_heater.cpp b/esphome/components/water_heater/water_heater.cpp index 4b65e907de7..e5236a04916 100644 --- a/esphome/components/water_heater/water_heater.cpp +++ b/esphome/components/water_heater/water_heater.cpp @@ -11,7 +11,7 @@ namespace esphome::water_heater { -static const char *const TAG = "water_heater"; +ESPHOME_LOG_TAG(TAG, "water_heater"); void log_water_heater(const char *tag, const char *prefix, const char *type, WaterHeater *obj) { if (obj != nullptr) { diff --git a/esphome/components/waveshare_epaper/__init__.py b/esphome/components/waveshare_epaper/__init__.py index b410406a585..4bcfcdfda5a 100644 --- a/esphome/components/waveshare_epaper/__init__.py +++ b/esphome/components/waveshare_epaper/__init__.py @@ -1,4 +1,5 @@ CODEOWNERS = ["@clydebarrow"] +DOMAIN = "waveshare_epaper" DEPRECATED_COMPONENT = """ The 'waveshare_epaper' component is deprecated and no new models will be added to it. diff --git a/esphome/components/waveshare_epaper/waveshare_213v3.cpp b/esphome/components/waveshare_epaper/waveshare_213v3.cpp index cc9c34cb42f..5e19782f6d2 100644 --- a/esphome/components/waveshare_epaper/waveshare_213v3.cpp +++ b/esphome/components/waveshare_epaper/waveshare_213v3.cpp @@ -4,7 +4,7 @@ namespace esphome::waveshare_epaper { -static const char *const TAG = "waveshare_2.13v3"; +ESPHOME_LOG_TAG(TAG, "waveshare_2.13v3"); static const uint8_t PARTIAL_LUT[] = { 0x32, // cmd diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 93f23424c01..60d711d1cdf 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -7,7 +7,7 @@ namespace esphome::waveshare_epaper { -static const char *const TAG = "waveshare_epaper"; +ESPHOME_LOG_TAG(TAG, "waveshare_epaper"); static const uint8_t LUT_SIZE_WAVESHARE = 30; diff --git a/esphome/components/waveshare_io_ch32v003/__init__.py b/esphome/components/waveshare_io_ch32v003/__init__.py index 29a939c5236..5e53d30149b 100644 --- a/esphome/components/waveshare_io_ch32v003/__init__.py +++ b/esphome/components/waveshare_io_ch32v003/__init__.py @@ -14,6 +14,7 @@ from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@latonita"] +DOMAIN = "waveshare_io_ch32v003" AUTO_LOAD = ["gpio_expander"] DEPENDENCIES = ["i2c"] diff --git a/esphome/components/waveshare_io_ch32v003/output/waveshare_io_ch32v003_output.cpp b/esphome/components/waveshare_io_ch32v003/output/waveshare_io_ch32v003_output.cpp index 30458310cef..cef569b9058 100644 --- a/esphome/components/waveshare_io_ch32v003/output/waveshare_io_ch32v003_output.cpp +++ b/esphome/components/waveshare_io_ch32v003/output/waveshare_io_ch32v003_output.cpp @@ -4,7 +4,7 @@ namespace esphome::waveshare_io_ch32v003 { -static const char *const TAG = "waveshare_io_ch32v003.output"; +ESPHOME_LOG_TAG(TAG, "waveshare_io_ch32v003.output"); void WaveshareIOCH32V003Output::write_state(float state) { uint8_t pwm_value = static_cast(state * 255.0f); diff --git a/esphome/components/waveshare_io_ch32v003/sensor/waveshare_io_ch32v003_sensor.cpp b/esphome/components/waveshare_io_ch32v003/sensor/waveshare_io_ch32v003_sensor.cpp index 82da7451f94..71cb9ba79fd 100644 --- a/esphome/components/waveshare_io_ch32v003/sensor/waveshare_io_ch32v003_sensor.cpp +++ b/esphome/components/waveshare_io_ch32v003/sensor/waveshare_io_ch32v003_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::waveshare_io_ch32v003 { -static const char *const TAG = "waveshare_io_ch32v003.sensor"; +ESPHOME_LOG_TAG(TAG, "waveshare_io_ch32v003.sensor"); float WaveshareIOCH32V003Sensor::get_setup_priority() const { return setup_priority::DATA; } diff --git a/esphome/components/waveshare_io_ch32v003/waveshare_io_ch32v003.cpp b/esphome/components/waveshare_io_ch32v003/waveshare_io_ch32v003.cpp index 8a58c7e7bb4..159390086f5 100644 --- a/esphome/components/waveshare_io_ch32v003/waveshare_io_ch32v003.cpp +++ b/esphome/components/waveshare_io_ch32v003/waveshare_io_ch32v003.cpp @@ -10,7 +10,7 @@ static const uint8_t IO_EXTENSION_PWM_ADDR = 0x05; static const uint8_t IO_EXTENSION_ADC_ADDR = 0x06; static const uint8_t IO_EXTENSION_RTC_INT_ADDR = 0x07; -static const char *const TAG = "waveshare_io_ch32v003"; +ESPHOME_LOG_TAG(TAG, "waveshare_io_ch32v003"); void WaveshareIOCH32V003Component::setup() { this->mode_mask_ = 0xFF; // Set all pins to output mode diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 2b00a6f8de1..4f233e99655 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -48,6 +48,7 @@ from esphome.types import ConfigType _LOGGER = logging.getLogger(__name__) AUTO_LOAD = ["json", "web_server_base"] +DOMAIN = "web_server" AUTH_TYPE_BASIC = "basic" AUTH_TYPE_DIGEST = "digest" @@ -239,11 +240,11 @@ WEBSERVER_SORTING_SCHEMA = cv.Schema( { cv.OnlyWith(CONF_WEB_SERVER_ID, "web_server"): cv.use_id(WebServer), cv.Optional(CONF_SORTING_WEIGHT): cv.All( - cv.requires_component("web_server"), + cv.requires_component(DOMAIN), cv.float_, ), cv.Optional(CONF_SORTING_GROUP_ID): cv.All( - cv.requires_component("web_server"), + cv.requires_component(DOMAIN), cv.use_id(cg.int_), ), } @@ -364,12 +365,14 @@ def add_resource_as_progmem( content_encoded = content.encode("utf-8") if compress: content_encoded = gzip.compress(content_encoded) - content_encoded_size = len(content_encoded) - bytes_as_int = ", ".join(str(x) for x in content_encoded) - uint8_t = f"constexpr uint8_t ESPHOME_WEBSERVER_{resource_name}[{content_encoded_size}] PROGMEM = {{{bytes_as_int}}}" - size_t = f"constexpr size_t ESPHOME_WEBSERVER_{resource_name}_SIZE = {content_encoded_size}" - cg.add_global(cg.RawExpression(uint8_t)) - cg.add_global(cg.RawExpression(size_t)) + cg.extern_progmem_array( + f"ESPHOME_WEBSERVER_{resource_name}", cg.uint8, list(content_encoded) + ) + cg.add_global( + cg.RawExpression( + f"constexpr size_t ESPHOME_WEBSERVER_{resource_name}_SIZE = {len(content_encoded)}" + ) + ) @coroutine_with_priority(CoroPriority.WEB) @@ -413,7 +416,16 @@ async def to_code(config: ConfigType) -> None: cg.add_define("USE_WEBSERVER_PRIVATE_NETWORK_ACCESS") if (allowed_origins := config.get(CONF_ALLOWED_ORIGINS)) is not None: cg.add_define("USE_WEBSERVER_ALLOWED_ORIGINS") - cg.add(var.set_allowed_origins(allowed_origins)) + # Shared flash table ended by nullptr, so the server stores only a pointer. + cg.add( + var.set_allowed_origins( + cg.shared_progmem_array( + "web_server_allowed_origins", + cg.const_char_ptr, + [*allowed_origins, cg.nullptr], + ) + ) + ) if (auth := config.get(CONF_AUTH)) is not None: cg.add_define("USE_WEBSERVER_AUTH") # The scheme is fixed at build time so the unused Basic/Digest code path is compiled @@ -462,7 +474,7 @@ def FILTER_SOURCE_FILES() -> list[str]: files_to_filter: list[str] = [] # web_server_v1.cpp is only needed when version is 1 - config = CORE.config.get("web_server", {}) + config = CORE.config.get(DOMAIN, {}) if config.get(CONF_VERSION, 2) != 1: files_to_filter.append("web_server_v1.cpp") diff --git a/esphome/components/web_server/ota/__init__.py b/esphome/components/web_server/ota/__init__.py index 03a5c2ca9bd..937d99ede88 100644 --- a/esphome/components/web_server/ota/__init__.py +++ b/esphome/components/web_server/ota/__init__.py @@ -2,15 +2,22 @@ import logging import esphome.codegen as cg from esphome.components.esp32 import add_idf_component -from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code +from esphome.components.ota import ( + BASE_OTA_SCHEMA, + DOMAIN as OTA_DOMAIN, + OTAComponent, + ota_to_code, +) from esphome.config_helpers import merge_config import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_OTA, CONF_PLATFORM, CONF_WEB_SERVER +from esphome.const import CONF_ID, CONF_PLATFORM from esphome.core import CORE, coroutine_with_priority from esphome.coroutine import CoroPriority import esphome.final_validate as fv from esphome.types import ConfigType +from .. import DOMAIN + _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@esphome/core"] @@ -27,13 +34,13 @@ def _web_server_ota_final_validate(config: ConfigType) -> None: causing undefined behavior. Merge them into a single instance. """ full_conf = fv.full_config.get() - ota_confs = full_conf.get(CONF_OTA, []) + ota_confs = full_conf.get(OTA_DOMAIN, []) web_server_ota_configs: list[ConfigType] = [] other_ota_configs: list[ConfigType] = [] for ota_conf in ota_confs: - if ota_conf.get(CONF_PLATFORM) == CONF_WEB_SERVER: + if ota_conf.get(CONF_PLATFORM) == DOMAIN: web_server_ota_configs.append(ota_conf) else: other_ota_configs.append(ota_conf) @@ -62,7 +69,7 @@ def _web_server_ota_final_validate(config: ConfigType) -> None: # Replace OTA configs with merged web_server + other OTA platforms other_ota_configs.append(merged) - full_conf[CONF_OTA] = other_ota_configs + full_conf[OTA_DOMAIN] = other_ota_configs fv.full_config.set(full_conf) diff --git a/esphome/components/web_server/ota/ota_web_server.cpp b/esphome/components/web_server/ota/ota_web_server.cpp index 95763e2daf0..3cf1d9802ef 100644 --- a/esphome/components/web_server/ota/ota_web_server.cpp +++ b/esphome/components/web_server/ota/ota_web_server.cpp @@ -23,7 +23,7 @@ using PlatformString = String; namespace esphome::web_server { -static const char *const TAG = "web_server.ota"; +ESPHOME_LOG_TAG(TAG, "web_server.ota"); class OTARequestHandler : public AsyncWebHandler { public: diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 74641738ec3..062007b02e0 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -55,7 +55,7 @@ namespace esphome::web_server { -static const char *const TAG = "web_server"; +ESPHOME_LOG_TAG(TAG, "web_server"); // View a state LogString as a ProgmemStr so ArduinoJson serializes it PROGMEM-aware on ESP8266. [[maybe_unused]] static ProgmemStr json_state_str(const LogString *s) { return reinterpret_cast(s); } @@ -495,7 +495,8 @@ bool WebServer::is_request_origin_allowed_(AsyncWebServerRequest *request, const #ifdef USE_WEBSERVER_ALLOWED_ORIGINS // Otherwise the origin must be explicitly allowed via configuration. - for (const char *allowed_origin : this->allowed_origins_) { + for (const char *const *it = this->allowed_origins_; *it != nullptr; it++) { + const char *allowed_origin = *it; // A single "*" entry allows any origin. if (allowed_origin[0] == '*' && allowed_origin[1] == '\0') return true; @@ -2079,10 +2080,8 @@ void WebServer::infrared_json_(infrared::Infrared *obj, JsonDetail start_config, set_json_icon_state_value(root, obj, "infrared", "", 0, start_config); - auto traits = obj->get_traits(); - - root[ESPHOME_F("supports_transmitter")] = traits.get_supports_transmitter(); - root[ESPHOME_F("supports_receiver")] = traits.get_supports_receiver(); + root[ESPHOME_F("supports_transmitter")] = obj->get_supports_transmitter(); + root[ESPHOME_F("supports_receiver")] = obj->get_supports_receiver(); if (start_config == DETAIL_ALL) { this->add_sorting_info_(root, obj); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 143e9a15eae..1f9b6cb9348 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -254,9 +254,9 @@ class WebServer final : public Component, public AsyncWebHandler { * * This list is also used to authorize Private Network Access requests when that feature is enabled. * - * @param origins The list of allowed origins. + * @param origins nullptr-terminated table of allowed origins; must outlive the server. */ - void set_allowed_origins(std::initializer_list origins) { this->allowed_origins_ = origins; } + void set_allowed_origins(const char *const *origins) { this->allowed_origins_ = origins; } #endif // ========== INTERNAL METHODS ========== @@ -531,6 +531,9 @@ class WebServer final : public Component, public AsyncWebHandler { bool include_internal_{false}; protected: + // Declared here so it packs into the padding after include_internal_ + bool expose_log_{true}; + void add_sorting_info_(JsonObject &root, EntityBase *entity); #ifdef USE_LIGHT @@ -614,11 +617,10 @@ class WebServer final : public Component, public AsyncWebHandler { #ifdef USE_WEBSERVER_JS_INCLUDE const char *js_include_{nullptr}; #endif - bool expose_log_{true}; #ifdef USE_WEBSERVER_ALLOWED_ORIGINS // Extra origins allowed to make cross-origin browser requests ("*" means any origin). // Only compiled when allowed_origins is configured; same-origin is always allowed regardless. - FixedVector allowed_origins_; + const char *const *allowed_origins_{nullptr}; #endif /// Check whether the given request Origin is permitted. Same-origin (matching the Host the diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 546e10ff5e3..c94eea11902 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -10,6 +10,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] +DOMAIN = "web_server_base" def AUTO_LOAD() -> list[str]: diff --git a/esphome/components/web_server_idf/__init__.py b/esphome/components/web_server_idf/__init__.py index 5a400dfbf3e..98e067665ad 100644 --- a/esphome/components/web_server_idf/__init__.py +++ b/esphome/components/web_server_idf/__init__.py @@ -1,12 +1,13 @@ from esphome.components.esp32 import ( - add_idf_sdkconfig_option, include_builtin_idf_component, + set_idf_sdkconfig_default, ) from esphome.config_helpers import filter_source_files_from_defines import esphome.config_validation as cv from esphome.types import ConfigType CODEOWNERS = ["@dentra"] +DOMAIN = "web_server_idf" CONFIG_SCHEMA = cv.All( cv.Schema({}), @@ -16,10 +17,9 @@ CONFIG_SCHEMA = cv.All( async def to_code(config: ConfigType) -> None: # Increase the maximum supported size of headers section in HTTP request packet to be processed by the server - add_idf_sdkconfig_option("CONFIG_HTTPD_MAX_REQ_HDR_LEN", 1024) - # Re-enable esp-tls (excluded by default to save compile time); - # web_server_idf.cpp includes for digest auth - include_builtin_idf_component("esp-tls") + set_idf_sdkconfig_default("CONFIG_HTTPD_MAX_REQ_HDR_LEN", 1024) + # Re-enable ESP-IDF's HTTP server (excluded by default to save compile time). + # Basic auth uses mbedtls_base64_encode directly, so no TLS stack is needed. include_builtin_idf_component("esp_http_server") diff --git a/esphome/components/web_server_idf/multipart.cpp b/esphome/components/web_server_idf/multipart.cpp index 7744272a5cd..3b0da4fb566 100644 --- a/esphome/components/web_server_idf/multipart.cpp +++ b/esphome/components/web_server_idf/multipart.cpp @@ -8,7 +8,7 @@ namespace esphome::web_server_idf { -static const char *const TAG = "multipart"; +ESPHOME_LOG_TAG(TAG, "multipart"); // ========== MultipartReader Implementation ========== diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index 510b4e50f1a..9a6e419e439 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -12,7 +12,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esp_tls_crypto.h" +#include #include #include @@ -55,7 +55,7 @@ namespace esphome::web_server_idf { #define CRLF_STR "\r\n" #define CRLF_LEN (sizeof(CRLF_STR) - 1) -static const char *const TAG = "web_server_idf"; +ESPHOME_LOG_TAG(TAG, "web_server_idf"); // Only send_json_() may hold the JSON arena: every other frame in this file is capped below one // arena. Measured at -Os on GCC 14; newer toolchains stay checked on purpose, older ones skip it. @@ -554,14 +554,18 @@ bool AsyncWebServerRequest::authenticate(const char *username, const char *passw constexpr size_t max_digest_len = 350; char digest[max_digest_len]; size_t out; - esp_crypto_base64_encode(reinterpret_cast(digest), max_digest_len, &out, - reinterpret_cast(user_info), user_info_len); + // The buffer bound above makes failure unreachable; reject rather than + // compare against an unwritten digest if that ever changes. + if (mbedtls_base64_encode(reinterpret_cast(digest), max_digest_len, &out, + reinterpret_cast(user_info), user_info_len) != 0) { + return false; + } // Constant-time comparison to avoid timing side channels. // No early return on length mismatch — the length difference is folded // into the accumulator so any mismatch is rejected. const char *provided = auth_str + auth_prefix_len; - size_t digest_len = out; // length from esp_crypto_base64_encode + size_t digest_len = out; // Derive provided_len from the already-sized std::string rather than // rescanning with strlen (avoids attacker-controlled scan length). size_t provided_len = auth.value().size() - auth_prefix_len; @@ -761,7 +765,7 @@ void AsyncEventSource::adopt_pending_sessions_main_loop_() { this->has_pending_sessions_.store(false, std::memory_order_relaxed); } for (auto *rsp : incoming) { - // Already disconnected? Drop it; skip on_connect_/session start on a dead session. + // Already disconnected? Drop it; skip session start on a dead session. if (rsp->safe_to_delete_()) { delete rsp; // NOLINT(cppcoreguidelines-owning-memory) continue; @@ -775,12 +779,7 @@ void AsyncEventSource::adopt_pending_sessions_main_loop_() { continue; } this->sessions_.push_back(rsp); - // Prime first so on_connect_ observes a session that has already sent its - // initial ping/config/sorting_groups, matching the pre-refactor ordering. rsp->start_session_main_loop_(); - if (this->on_connect_) { - this->on_connect_(rsp); - } } } // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index 2b62d68790e..581ba8323b5 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -379,7 +379,6 @@ using AsyncEventSourceClient = AsyncEventSourceResponse; class AsyncEventSource : public AsyncWebHandler { friend class AsyncEventSourceResponse; - using connect_handler_t = std::function; public: AsyncEventSource(StringRef url, esphome::web_server::WebServer *ws) : url_(url), web_server_(ws) {} @@ -394,11 +393,6 @@ class AsyncEventSource : public AsyncWebHandler { } // NOLINTNEXTLINE(readability-identifier-naming) void handleRequest(AsyncWebServerRequest *request) override; - // Callback runs on the main loop (not the httpd task) after the session's - // initial ping/config/sorting_groups have been sent. - // NOLINTNEXTLINE(readability-identifier-naming) - void onConnect(connect_handler_t &&cb) { this->on_connect_ = std::move(cb); } - void try_send_nodefer(const char *message, size_t message_len, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0); void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator); @@ -418,7 +412,6 @@ class AsyncEventSource : public AsyncWebHandler { // Httpd-task intake; guarded by pending_mutex_, gated by has_pending_sessions_. std::vector pending_sessions_; Mutex pending_mutex_; - connect_handler_t on_connect_{}; esphome::web_server::WebServer *web_server_; std::atomic has_pending_sessions_{false}; }; diff --git a/esphome/components/weikai/__init__.py b/esphome/components/weikai/__init__.py index 8f0cf4ba334..58bfc6f815d 100644 --- a/esphome/components/weikai/__init__.py +++ b/esphome/components/weikai/__init__.py @@ -17,6 +17,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@DrCoolZic"] AUTO_LOAD = ["uart"] +DOMAIN = "weikai" MULTI_CONF = True CONF_CRYSTAL = "crystal" diff --git a/esphome/components/weikai/weikai.cpp b/esphome/components/weikai/weikai.cpp index b95d474fd38..2576856e2f6 100644 --- a/esphome/components/weikai/weikai.cpp +++ b/esphome/components/weikai/weikai.cpp @@ -8,7 +8,7 @@ namespace esphome::weikai { -static const char *const TAG = "weikai"; +ESPHOME_LOG_TAG(TAG, "weikai"); /// @brief measure the time elapsed between two calls /// @param last_time time of the previous call diff --git a/esphome/components/weikai_i2c/__init__.py b/esphome/components/weikai_i2c/__init__.py index 2c6a421a0a3..9bbe558dcf9 100644 --- a/esphome/components/weikai_i2c/__init__.py +++ b/esphome/components/weikai_i2c/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@DrCoolZic"] +DOMAIN = "weikai_i2c" diff --git a/esphome/components/weikai_i2c/weikai_i2c.cpp b/esphome/components/weikai_i2c/weikai_i2c.cpp index af5bd7f313b..bafa6c4e03a 100644 --- a/esphome/components/weikai_i2c/weikai_i2c.cpp +++ b/esphome/components/weikai_i2c/weikai_i2c.cpp @@ -6,7 +6,7 @@ #include "weikai_i2c.h" namespace esphome::weikai_i2c { -static const char *const TAG = "weikai_i2c"; +ESPHOME_LOG_TAG(TAG, "weikai_i2c"); /// @brief Display a buffer in hexadecimal format (32 hex values / line). void print_buffer(const uint8_t *data, size_t length) { diff --git a/esphome/components/weikai_spi/__init__.py b/esphome/components/weikai_spi/__init__.py index 2c6a421a0a3..2a13bda0345 100644 --- a/esphome/components/weikai_spi/__init__.py +++ b/esphome/components/weikai_spi/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@DrCoolZic"] +DOMAIN = "weikai_spi" diff --git a/esphome/components/weikai_spi/weikai_spi.cpp b/esphome/components/weikai_spi/weikai_spi.cpp index 03bf66e07be..6a44fad6f3b 100644 --- a/esphome/components/weikai_spi/weikai_spi.cpp +++ b/esphome/components/weikai_spi/weikai_spi.cpp @@ -7,7 +7,7 @@ namespace esphome::weikai_spi { using namespace weikai; -static const char *const TAG = "weikai_spi"; +ESPHOME_LOG_TAG(TAG, "weikai_spi"); /// @brief measure the time elapsed between two calls /// @param last_time time of the previous call diff --git a/esphome/components/whirlpool/__init__.py b/esphome/components/whirlpool/__init__.py index e69de29bb2d..4bc63aeba8e 100644 --- a/esphome/components/whirlpool/__init__.py +++ b/esphome/components/whirlpool/__init__.py @@ -0,0 +1 @@ +DOMAIN = "whirlpool" diff --git a/esphome/components/whirlpool/whirlpool.cpp b/esphome/components/whirlpool/whirlpool.cpp index f560917f41e..73fab843fc4 100644 --- a/esphome/components/whirlpool/whirlpool.cpp +++ b/esphome/components/whirlpool/whirlpool.cpp @@ -3,7 +3,7 @@ namespace esphome::whirlpool { -static const char *const TAG = "whirlpool.climate"; +ESPHOME_LOG_TAG(TAG, "whirlpool.climate"); const uint16_t WHIRLPOOL_HEADER_MARK = 9000; const uint16_t WHIRLPOOL_HEADER_SPACE = 4494; diff --git a/esphome/components/whynter/__init__.py b/esphome/components/whynter/__init__.py index 7316776d78d..f69112ca3d9 100644 --- a/esphome/components/whynter/__init__.py +++ b/esphome/components/whynter/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@aeonsablaze"] +DOMAIN = "whynter" diff --git a/esphome/components/whynter/whynter.cpp b/esphome/components/whynter/whynter.cpp index f5f304aa17e..5aee703feb7 100644 --- a/esphome/components/whynter/whynter.cpp +++ b/esphome/components/whynter/whynter.cpp @@ -3,7 +3,7 @@ namespace esphome::whynter { -static const char *const TAG = "climate.whynter"; +ESPHOME_LOG_TAG(TAG, "climate.whynter"); const uint16_t BITS = 32; diff --git a/esphome/components/wiegand/__init__.py b/esphome/components/wiegand/__init__.py index dd6e84ac250..93d5e4f0f06 100644 --- a/esphome/components/wiegand/__init__.py +++ b/esphome/components/wiegand/__init__.py @@ -6,6 +6,7 @@ from esphome.const import CONF_ID, CONF_ON_KEY, CONF_ON_TAG, CONF_TRIGGER_ID from esphome.types import ConfigType CODEOWNERS = ["@ssieb"] +DOMAIN = "wiegand" AUTO_LOAD = ["key_provider"] diff --git a/esphome/components/wiegand/wiegand.cpp b/esphome/components/wiegand/wiegand.cpp index df64cd48aa7..4f576e4c9d0 100644 --- a/esphome/components/wiegand/wiegand.cpp +++ b/esphome/components/wiegand/wiegand.cpp @@ -5,7 +5,7 @@ namespace esphome::wiegand { -static const char *const TAG = "wiegand"; +ESPHOME_LOG_TAG(TAG, "wiegand"); static const char *const KEYS = "0123456789*#"; void IRAM_ATTR HOT WiegandStore::d0_gpio_intr(WiegandStore *arg) { diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index fa552da9692..aa6d113e168 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -12,6 +12,7 @@ from esphome.components.esp32 import ( get_esp32_variant, only_on_variant, request_wifi, + require_mbedtls_tls, require_mbedtls_tls_extras, ) from esphome.components.network import ( @@ -60,7 +61,6 @@ from esphome.const import ( CONF_TTLS_PHASE_2, CONF_USE_ADDRESS, CONF_USERNAME, - CONF_WIFI, PLACEHOLDER_WIFI_SSID, Platform, PlatformFramework, @@ -80,6 +80,7 @@ from . import wpa2_eap _LOGGER = logging.getLogger(__name__) AUTO_LOAD = ["network"] +DOMAIN = "wifi" NO_WIFI_VARIANTS = [ const.VARIANT_ESP32H2, @@ -514,7 +515,7 @@ CONFIG_SCHEMA = cv.All( rp2="light", bk72xx="none", rtl87xx="none", - ln882x="light", + ln882x="none", ): cv.enum(WIFI_POWER_SAVE_MODES, upper=True), cv.Optional(CONF_FAST_CONNECT, default=False): _fast_connect_schema, cv.Optional(CONF_USE_ADDRESS): cv.string_strict, @@ -669,6 +670,10 @@ async def to_code(config): if (ap_timeout := conf[CONF_AP_TIMEOUT]) != cv.time_period(DEFAULT_AP_TIMEOUT): cg.add(var.set_ap_timeout(ap_timeout)) cg.add_define("USE_WIFI_AP") + # The LN882H radio cannot run the AP and STA together; the fallback AP + # takes turns with the networks instead. + if CORE.is_ln882x: + cg.add_define("USE_WIFI_AP_EXCLUSIVE") # ESP32: register the WiFi stack with the esp32 sdkconfig reconciler, which # drops SoftAP support / the LWIP DHCP server when AP mode is unused. @@ -679,10 +684,10 @@ async def to_code(config): if CORE.is_esp32: add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT", has_eap) if has_eap: - # wpa_supplicant's EAP client negotiates with whatever the RADIUS - # server offers, and a failed handshake leaves the device off the - # network, so keep every mbedTLS client feature the esp32 platform - # would otherwise trim. + # The supplicant's Kconfig select cannot override the IDF 5 TLS + # role choice; the EAP client talks to mbedTLS directly (no + # esp-tls) and needs every trimmed extra. + require_mbedtls_tls() require_mbedtls_tls_extras() # Only define USE_WIFI_MANUAL_IP if any AP uses manual IP @@ -734,6 +739,10 @@ async def to_code(config): if not config[CONF_ENABLE_ON_BOOT]: cg.add(var.set_enable_on_boot(False)) + # LN882x: hand the SDK the BSSID LibreTiny 1.13 drops (see wifi_component_libretiny.cpp); remove once fixed upstream. + if CORE.is_ln882x: + cg.add_build_flag("-Wl,--wrap=wifi_sta_connect") + # post_connect_roaming defaults to true in C++ - disable if user disabled it # or if 802.11k/v is enabled (driver handles roaming natively) if ( @@ -1079,7 +1088,7 @@ def _placeholder_wifi_credentials(config: ConfigType) -> list[str]: values still appear. Empty list means no placeholders were found. """ placeholders: list[str] = [] - wifi_conf = config.get(CONF_WIFI) + wifi_conf = config.get(DOMAIN) if not wifi_conf: return placeholders diff --git a/esphome/components/wifi/automation.h b/esphome/components/wifi/automation.h index c14341330f5..eb3d7471dc1 100644 --- a/esphome/components/wifi/automation.h +++ b/esphome/components/wifi/automation.h @@ -76,13 +76,13 @@ template class WiFiConfigureAction final : public Action, // Enable WiFi global_wifi_component->enable(); // Set timeout for the connection - this->set_timeout("wifi-connect-timeout", this->connection_timeout_.value(x...), [this, x...]() { + this->set_timeout(CONNECT_TIMEOUT_ID, this->connection_timeout_.value(x...), [this, x...]() { // If the timeout is reached, stop connecting and revert to the old AP global_wifi_component->disable(); global_wifi_component->save_wifi_sta(old_sta_.get_ssid(), old_sta_.get_password()); global_wifi_component->enable(); // Start a timeout for the fallback if the connection to the old AP fails - this->set_timeout("wifi-fallback-timeout", this->connection_timeout_.value(x...), [this]() { + this->set_timeout(FALLBACK_TIMEOUT_ID, this->connection_timeout_.value(x...), [this]() { this->connecting_ = false; this->error_trigger_.trigger(); }); @@ -97,8 +97,8 @@ template class WiFiConfigureAction final : public Action, return; if (global_wifi_component->is_connected()) { // The WiFi is connected, stop the timeout and reset the connecting flag - this->cancel_timeout("wifi-connect-timeout"); - this->cancel_timeout("wifi-fallback-timeout"); + this->cancel_timeout(CONNECT_TIMEOUT_ID); + this->cancel_timeout(FALLBACK_TIMEOUT_ID); this->connecting_ = false; char ssid_buf[SSID_BUFFER_SIZE]; if (strcmp(global_wifi_component->wifi_ssid_to(ssid_buf), this->new_sta_.get_ssid().c_str()) == 0) { @@ -112,6 +112,9 @@ template class WiFiConfigureAction final : public Action, } protected: + static constexpr uint32_t CONNECT_TIMEOUT_ID = 0; + static constexpr uint32_t FALLBACK_TIMEOUT_ID = 1; + bool connecting_{false}; WiFiAP new_sta_; WiFiAP old_sta_; diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 125139ad163..001dae98b83 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -51,7 +51,7 @@ namespace esphome::wifi { -static const char *const TAG = "wifi"; +ESPHOME_LOG_TAG(TAG, "wifi"); // CompactString implementation CompactString::CompactString(const char *str, size_t len) { @@ -380,6 +380,26 @@ static constexpr uint8_t WIFI_RETRY_COUNT_PER_AP = 1; /// Cooldown duration in milliseconds after adapter restart or repeated failures /// Allows WiFi hardware to stabilize before next connection attempt static constexpr uint32_t WIFI_COOLDOWN_DURATION_MS = 500; +#ifdef USE_WIFI_AP +/// Whether the radio runs the fallback AP on its own, without STA alongside. +#ifdef USE_WIFI_AP_EXCLUSIVE +static constexpr bool WIFI_AP_EXCLUSIVE = true; +#else +static constexpr bool WIFI_AP_EXCLUSIVE = false; +#endif +#endif +#ifdef USE_WIFI_AP_EXCLUSIVE +/// On a radio that cannot run the AP and STA together: how long the fallback +/// AP stays up while nobody uses it before it pauses so the networks can be +/// tried again, and how long new credentials give the portal to answer +/// before the AP drops. +static constexpr uint32_t WIFI_AP_EXCLUSIVE_DWELL_MS = 300000; +/// The AP pauses after this long even with clients counted, so a lost +/// disconnect event cannot keep the networks from being tried for good. +static constexpr uint32_t WIFI_AP_EXCLUSIVE_MAX_DWELL_MS = 3 * WIFI_AP_EXCLUSIVE_DWELL_MS; +static constexpr uint32_t WIFI_AP_EXCLUSIVE_HANDOVER_MS = 1000; +static constexpr uint32_t AP_HANDOVER_TIMEOUT_ID = 0; +#endif /// Cooldown duration when fallback AP is active and captive portal may be running /// Longer interval gives users time to configure WiFi without constant connection attempts @@ -642,6 +662,14 @@ void WiFiComponent::setup() { provisioning::global_provisioning_manager->add_on_closed_callback([this]() { if (this->ap_setup_) { ESP_LOGD(TAG, "Provisioning window closed; disabling AP"); +#ifdef USE_WIFI_AP_EXCLUSIVE + // The networks wait while the AP runs on its own; hand them the radio + // now rather than at the end of the dwell. + if (this->state_ == WIFI_COMPONENT_STATE_AP && this->has_sta()) { + this->pause_exclusive_ap_(); + return; + } +#endif this->wifi_mode_({}, false); } }); @@ -739,8 +767,11 @@ void WiFiComponent::start() { } #ifdef USE_CAPTIVE_PORTAL if (captive_portal::global_captive_portal != nullptr) { - this->wifi_sta_pre_setup_(); - this->start_scanning(); + // Where the radio scans alongside the AP, the portal can list networks. + if (!WIFI_AP_EXCLUSIVE) { + this->wifi_sta_pre_setup_(); + this->start_scanning(); + } captive_portal::global_captive_portal->start(); } #endif @@ -762,7 +793,7 @@ void WiFiComponent::restart_adapter() { // and check_connecting_finished() is called after cooldown without going // through start_connecting() first. Without this clear, stale errors would // trigger spurious "failed (callback)" logs. The canonical clear location - // is in start_connecting(); this is the only exception to that pattern. + // is in start_connecting() (ESP8266 also clears after wifi_station_connect()). this->error_from_callback_ = false; } @@ -862,8 +893,17 @@ void WiFiComponent::loop() { } break; } - case WIFI_COMPONENT_STATE_OFF: case WIFI_COMPONENT_STATE_AP: +#ifdef USE_WIFI_AP_EXCLUSIVE + // The networks are not tried while the AP is up, so it pauses for + // them once nobody has used it for a while. Without networks the AP + // is all there is and stays. + if (this->has_sta() && now - this->ap_exclusive_changed_ > WIFI_AP_EXCLUSIVE_DWELL_MS && + (this->ap_clients_ == 0 || now - this->ap_exclusive_changed_ > WIFI_AP_EXCLUSIVE_MAX_DWELL_MS)) + this->pause_exclusive_ap_(); + break; +#endif + case WIFI_COMPONENT_STATE_OFF: break; case WIFI_COMPONENT_STATE_DISABLED: return; @@ -879,11 +919,23 @@ void WiFiComponent::loop() { provisioning::global_provisioning_manager != nullptr && provisioning::global_provisioning_manager->closed(); #endif if (this->has_ap() && !this->ap_setup_ && !provisioning_closed) { - if (this->ap_timeout_ != 0 && (now - this->last_connected_ > this->ap_timeout_)) { + if (this->ap_timeout_ != 0 && + (now - this->last_connected_ > this->ap_timeout_) +#ifdef USE_WIFI_AP_EXCLUSIVE + // After a pause, or a start that failed, the networks get a full + // ap_timeout before the AP is tried again. + && now - this->ap_exclusive_changed_ > this->ap_timeout_ +#endif + ) { ESP_LOGI(TAG, "Starting fallback AP"); +#ifdef USE_WIFI_AP_EXCLUSIVE + this->ap_exclusive_changed_ = now; +#endif this->setup_ap_config_(); #ifdef USE_CAPTIVE_PORTAL - if (captive_portal::global_captive_portal != nullptr) { + // Where the AP runs on its own, a portal with no AP behind it would + // only stretch the cooldowns. + if (captive_portal::global_captive_portal != nullptr && (!WIFI_AP_EXCLUSIVE || this->ap_setup_)) { // Reset so we force one full scan after captive portal starts // (previous scans were filtered because captive portal wasn't active yet) this->has_completed_scan_after_captive_portal_start_ = false; @@ -972,7 +1024,13 @@ network::IPAddress WiFiComponent::get_dns_address(int num) { #ifdef USE_WIFI_AP void WiFiComponent::setup_ap_config_() { +#ifdef USE_WIFI_AP_EXCLUSIVE + // The radio cannot run both, so STA goes off while the AP is up. + if (!this->wifi_mode_(false, true)) + return; +#else this->wifi_mode_({}, true); +#endif if (this->ap_setup_) return; @@ -1006,6 +1064,12 @@ void WiFiComponent::setup_ap_config_() { } } this->ap_setup_ = this->wifi_start_ap_(this->ap_); +#ifdef USE_WIFI_AP_EXCLUSIVE + // A failed start must not leave the radio in AP mode, which would refuse + // the STA attempts until the next try. + if (!this->ap_setup_) + this->wifi_mode_({}, false); +#endif char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; ESP_LOGCONFIG(TAG, @@ -1030,11 +1094,40 @@ void WiFiComponent::setup_ap_config_() { } #endif - if (!this->has_sta()) { + // Where the AP runs on its own, the STA state machine also waits in this + // state until the AP pauses. + if (!this->has_sta() || (WIFI_AP_EXCLUSIVE && this->ap_setup_)) { this->state_ = WIFI_COMPONENT_STATE_AP; } } +#ifdef USE_WIFI_AP_EXCLUSIVE +void WiFiComponent::pause_exclusive_ap_() { + ESP_LOGI(TAG, "Pausing AP to try the networks"); + // Scanning and connecting turn STA back on. + this->disable_ap_(); + this->ap_setup_ = false; + // Clients drop with the AP without a disconnect event for each. + this->ap_clients_ = 0; + this->ap_exclusive_changed_ = App.get_loop_component_start_time(); + // A fresh connection cycle, as at boot, rather than resuming the one the + // AP cut short. + this->retry_phase_ = WiFiRetryPhase::INITIAL_CONNECT; + this->num_retried_ = 0; + this->start_initial_connection_(); +} +#endif + +void WiFiComponent::disable_ap_() { +#ifdef USE_CAPTIVE_PORTAL + if (this->is_captive_portal_active_()) { + captive_portal::global_captive_portal->end(); + } +#endif + ESP_LOGD(TAG, "Disabling AP"); + this->wifi_mode_({}, false); +} + void WiFiComponent::set_ap(const WiFiAP &ap) { this->ap_ = ap; this->has_ap_ = true; @@ -1126,6 +1219,17 @@ void WiFiComponent::save_wifi_sta(const char *ssid, const char *password) { } void WiFiComponent::connect_soon_() { +#ifdef USE_WIFI_AP_EXCLUSIVE + // New credentials from the portal: the AP pauses for them once its answer + // is out, and the STA state machine picks them up from there. + if (this->ap_setup_) { + this->set_timeout(AP_HANDOVER_TIMEOUT_ID, WIFI_AP_EXCLUSIVE_HANDOVER_MS, [this]() { + if (this->ap_setup_) + this->pause_exclusive_ap_(); + }); + return; + } +#endif // Only trigger retry if we're in cooldown - if already connecting/connected, do nothing if (this->state_ == WIFI_COMPONENT_STATE_COOLDOWN) { ESP_LOGD(TAG, "Exiting cooldown early due to new WiFi credentials"); @@ -1218,8 +1322,9 @@ void WiFiComponent::start_connecting(const WiFiAP &ap) { // Clear any stale error from previous connection attempt. // This is the canonical location for clearing the flag since all connection - // attempts go through start_connecting(). The only other clear is in - // restart_adapter() which enters COOLDOWN without calling start_connecting(). + // attempts go through start_connecting(). restart_adapter() also clears it, as it + // enters COOLDOWN without calling start_connecting(), and ESP8266 clears it again + // after wifi_station_connect(), whose callbacks fire synchronously. this->error_from_callback_ = false; if (!this->wifi_sta_connect_(ap)) { @@ -1635,15 +1740,11 @@ void WiFiComponent::check_connecting_finished(uint32_t now) { // Reset to initial phase on successful connection (don't log transition, just reset state) this->retry_phase_ = WiFiRetryPhase::INITIAL_CONNECT; this->num_retried_ = 0; +#ifdef USE_WIFI_AP if (this->has_ap()) { -#ifdef USE_CAPTIVE_PORTAL - if (this->is_captive_portal_active_()) { - captive_portal::global_captive_portal->end(); - } -#endif - ESP_LOGD(TAG, "Disabling AP"); - this->wifi_mode_({}, false); + this->disable_ap_(); } +#endif #ifdef USE_IMPROV_BLE if (this->is_improv_ble_active_()) { improv_ble::global_improv_component->stop(); diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 082c059a14a..9700e36e6f6 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -506,6 +506,9 @@ class WiFiComponent final : public Component { // (In most use cases you won't need these) /// Setup WiFi interface. void setup() override; +#ifdef USE_LN882X + void on_powerdown() override; +#endif void start(); void dump_config() override; void restart_adapter(); @@ -684,6 +687,12 @@ class WiFiComponent final : public Component { protected: #ifdef USE_WIFI_AP void setup_ap_config_(); + /// End the captive portal and turn the AP off. + void disable_ap_(); +#ifdef USE_WIFI_AP_EXCLUSIVE + /// Drop the fallback AP so the networks can be tried; it comes back after ap_timeout. + void pause_exclusive_ap_(); +#endif #endif // USE_WIFI_AP void print_connect_params_(); @@ -916,6 +925,9 @@ class WiFiComponent final : public Component { float output_power_{NAN}; uint32_t action_started_; uint32_t last_connected_{0}; +#ifdef USE_WIFI_AP_EXCLUSIVE + uint32_t ap_exclusive_changed_{0}; // When the AP was last started or paused +#endif uint32_t reboot_timeout_{900000}; // Keep in sync with DEFAULT_REBOOT_TIMEOUT in __init__.py uint32_t roaming_last_check_{0}; uint32_t roaming_scan_end_{0}; // Timestamp when last roaming scan completed @@ -1008,6 +1020,9 @@ class WiFiComponent final : public Component { bool scan_done_{false}; bool ap_setup_{false}; bool ap_started_{false}; +#ifdef USE_WIFI_AP_EXCLUSIVE + uint8_t ap_clients_{0}; // Devices joined to the AP, which keep it from pausing +#endif bool passive_scan_{false}; bool has_saved_wifi_settings_{false}; #ifdef USE_WIFI_11KV_SUPPORT diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 60ec3f9a4d5..41b51460006 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -40,7 +40,7 @@ extern "C" { namespace esphome::wifi { -static const char *const TAG = "wifi_esp8266"; +ESPHOME_LOG_TAG(TAG, "wifi_esp8266"); enum class ESP8266WiFiSTAState : uint8_t { IDLE, // Not connecting @@ -376,17 +376,17 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { this->wifi_apply_hostname_(); - // Reset flags, do this _before_ wifi_station_connect as the callback method - // may be called from wifi_station_connect - this->sta_state_ = static_cast(ESP8266WiFiSTAState::CONNECTING); - ETS_UART_INTR_DISABLE(); ret = wifi_station_connect(); ETS_UART_INTR_ENABLE(); if (!ret) { ESP_LOGV(TAG, "wifi_station_connect failed"); + this->sta_state_ = static_cast(ESP8266WiFiSTAState::ERROR_FAILED); return false; } + // Reset after, not before: wifi_station_connect() synchronously reports the attempt it replaces as failed + this->sta_state_ = static_cast(ESP8266WiFiSTAState::CONNECTING); + this->error_from_callback_ = false; #if USE_NETWORK_IPV6 bool connected = false; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index c44ee5b1de9..3769e26831b 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -44,7 +44,7 @@ namespace esphome::wifi { -static const char *const TAG = "wifi_esp32"; +ESPHOME_LOG_TAG(TAG, "wifi_esp32"); static EventGroupHandle_t s_wifi_event_group; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static esp_netif_t *s_sta_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 940f2a07830..2a9e682440e 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -28,6 +28,22 @@ extern "C" { } #endif +#ifdef USE_LN882X +#include +#undef DATA // clashes with setup_priority::DATA +// LibreTiny 1.13's LN882H reconnect() gives the SDK a NULL bssid; pass on the requested one. Remove once it passes +// info.bssid. Safe without a BSSID: begin() frees sta.bssid (resetNetworkInfo), so it stays NULL. +// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming) +// Names are mandated by the --wrap linker mechanism. +extern "C" int __real_wifi_sta_connect(wifi_sta_connect_t *connect, wifi_scan_cfg_t *scan_cfg); +extern "C" int __wrap_wifi_sta_connect(wifi_sta_connect_t *connect, wifi_scan_cfg_t *scan_cfg) { + if (connect->bssid == nullptr) + connect->bssid = static_cast(WiFi.data)->sta.bssid; + return __real_wifi_sta_connect(connect, scan_cfg); +} +// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming) +#endif + #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" @@ -36,7 +52,7 @@ extern "C" { namespace esphome::wifi { -static const char *const TAG = "wifi_lt"; +ESPHOME_LOG_TAG(TAG, "wifi_lt"); // Thread-safe event handling for LibreTiny WiFi // @@ -608,6 +624,9 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { break; } case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { +#ifdef USE_WIFI_AP_EXCLUSIVE + this->ap_clients_++; +#endif #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE auto &it = event->data.sta_connected; char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; @@ -617,6 +636,10 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) { break; } case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { +#ifdef USE_WIFI_AP_EXCLUSIVE + if (this->ap_clients_ > 0) + this->ap_clients_--; +#endif #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE auto &it = event->data.sta_disconnected; char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; @@ -770,6 +793,13 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { yield(); +#ifdef USE_LN882X + // LibreTiny's LN882H softAP() points ap.bssid at a static array and frees it + // on the next call, tripping FreeRTOS's configASSERT until the watchdog + // resets the chip. Clear it so that a second AP start frees nothing. + static_cast(WiFi.data)->ap.bssid = nullptr; +#endif + return WiFi.softAP(ap.ssid_.c_str(), ap.password_.empty() ? NULL : ap.password_.c_str(), ap.has_channel() ? ap.get_channel() : 1, ap.get_hidden()); } @@ -777,6 +807,17 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.softAPIP()}; } #endif // USE_WIFI_AP +#ifdef USE_LN882X +void WiFiComponent::on_powerdown() { + // Leave the AP so it no longer holds the association when we come back. + if (WiFi.status() != WL_CONNECTED) // associated, even before DHCP has finished + return; + ESP_LOGD(TAG, "Disconnecting before powerdown"); + wifi_sta_disconnect(); // not WiFi.disconnect(): it frees the SSID its event handlers read + delay(100); // NOLINT: once per reboot or deep sleep, lets the deauth go out +} +#endif + bool WiFiComponent::wifi_disconnect_() { // Reset state first so disconnect events aren't ignored // and wifi_sta_connect_status_() returns IDLE instead of CONNECTING diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 325bcf26529..5b05132f4f7 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -18,7 +18,7 @@ namespace esphome::wifi { -static const char *const TAG = "wifi_pico_w"; +ESPHOME_LOG_TAG(TAG, "wifi_pico_w"); // Check if STA is fully connected (WiFi joined + has IP address). // Do NOT use WiFi.status() or WiFi.connected() for this — in AP-only mode they diff --git a/esphome/components/wifi_info/__init__.py b/esphome/components/wifi_info/__init__.py index e69de29bb2d..77d5dd714a5 100644 --- a/esphome/components/wifi_info/__init__.py +++ b/esphome/components/wifi_info/__init__.py @@ -0,0 +1 @@ +DOMAIN = "wifi_info" diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.cpp b/esphome/components/wifi_info/wifi_info_text_sensor.cpp index 5d4e77eaad8..98b0cc48142 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.cpp +++ b/esphome/components/wifi_info/wifi_info_text_sensor.cpp @@ -9,7 +9,7 @@ namespace esphome::wifi_info { -static const char *const TAG = "wifi_info"; +ESPHOME_LOG_TAG(TAG, "wifi_info"); #ifdef USE_WIFI_IP_STATE_LISTENERS diff --git a/esphome/components/wifi_signal/__init__.py b/esphome/components/wifi_signal/__init__.py index e69de29bb2d..febfbf73876 100644 --- a/esphome/components/wifi_signal/__init__.py +++ b/esphome/components/wifi_signal/__init__.py @@ -0,0 +1 @@ +DOMAIN = "wifi_signal" diff --git a/esphome/components/wifi_signal/wifi_signal_sensor.cpp b/esphome/components/wifi_signal/wifi_signal_sensor.cpp index 11d816a9097..57cbf9ae9b6 100644 --- a/esphome/components/wifi_signal/wifi_signal_sensor.cpp +++ b/esphome/components/wifi_signal/wifi_signal_sensor.cpp @@ -4,7 +4,7 @@ namespace esphome::wifi_signal { -static const char *const TAG = "wifi_signal.sensor"; +ESPHOME_LOG_TAG(TAG, "wifi_signal.sensor"); void WiFiSignalSensor::dump_config() { LOG_SENSOR("", "WiFi Signal", this); } diff --git a/esphome/components/wireguard/__init__.py b/esphome/components/wireguard/__init__.py index ac62812fcf9..e9641a5957c 100644 --- a/esphome/components/wireguard/__init__.py +++ b/esphome/components/wireguard/__init__.py @@ -33,6 +33,7 @@ CONF_WIREGUARD_ID = "wireguard_id" DEPENDENCIES = ["time"] CODEOWNERS = ["@lhoracek", "@droscy", "@thomas0bernard"] +DOMAIN = "wireguard" # The key validation regex has been described by Jason Donenfeld himself # url: https://lists.zx2c4.com/pipermail/wireguard/2020-December/006222.html @@ -120,16 +121,19 @@ async def to_code(config): ) ) + # Shared flash table ended by an empty entry, so the component stores only a pointer. + entries = [ + cg.StructInitializer( + AllowedIP, + ("ip", str(ip.network_address)), + ("netmask", str(ip.netmask)), + ) + for ip in allowed_ips + ] + end = cg.StructInitializer(AllowedIP, ("ip", cg.nullptr), ("netmask", cg.nullptr)) cg.add( var.set_allowed_ips( - [ - cg.StructInitializer( - AllowedIP, - ("ip", str(ip.network_address)), - ("netmask", str(ip.netmask)), - ) - for ip in allowed_ips - ] + cg.shared_progmem_array("wireguard_allowed_ips", AllowedIP, [*entries, end]) ) ) @@ -143,12 +147,12 @@ async def to_code(config): if CORE.is_esp32: add_idf_sdkconfig_option("CONFIG_LWIP_PPP_SUPPORT", True) - # This flag is added here because the esp_wireguard library statically + # This flag is added here because the wireguard library statically # set the size of its allowed_ips list at compile time using this value; # the '+1' modifier is relative to the device's own address that will # be automatically added to the provided list. cg.add_build_flag(f"-DCONFIG_WIREGUARD_MAX_SRC_IPS={len(allowed_ips) + 1}") - cg.add_library("droscy/esp_wireguard", "0.4.5") + cg.add_library("esphome/wireguard", "0.4.8") await cg.register_component(var, config) diff --git a/esphome/components/wireguard/wireguard.cpp b/esphome/components/wireguard/wireguard.cpp index fc06569fbab..01921f17e8a 100644 --- a/esphome/components/wireguard/wireguard.cpp +++ b/esphome/components/wireguard/wireguard.cpp @@ -14,7 +14,7 @@ namespace esphome::wireguard { -static const char *const TAG = "wireguard"; +ESPHOME_LOG_TAG(TAG, "wireguard"); /* * Cannot use `static const char*` for LOGMSG_PEER_STATUS on esp8266 platform @@ -101,8 +101,13 @@ void Wireguard::update() { if (this->wg_peer_offline_time_ == 0) { ESP_LOGW(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str()); this->wg_peer_offline_time_ = millis(); + this->wg_reconnect_time_ = this->wg_peer_offline_time_; } else if (this->enabled_) { ESP_LOGD(TAG, LOGMSG_PEER_STATUS, LOGMSG_OFFLINE, latest_handshake.c_str()); + if (millis() - this->wg_reconnect_time_ >= RECONNECT_INTERVAL_MS) { + this->wg_reconnect_time_ = millis(); + this->stop_connection_(); + } this->start_connection_(); } @@ -149,8 +154,8 @@ void Wireguard::dump_config() { (this->preshared_key_ != nullptr ? preshared_key_masked : LOG_STR_LITERAL("NOT IN USE"))); // clang-format on ESP_LOGCONFIG(TAG, " Peer Allowed IPs:"); - for (const AllowedIP &allowed_ip : this->allowed_ips_) { - ESP_LOGCONFIG(TAG, " - %s/%s", allowed_ip.ip, allowed_ip.netmask); + for (const AllowedIP *it = this->allowed_ips_; it != nullptr && it->ip != nullptr; it++) { + ESP_LOGCONFIG(TAG, " - %s/%s", it->ip, it->netmask); } ESP_LOGCONFIG(TAG, " Peer Persistent Keepalive: %d%s", this->keepalive_, (this->keepalive_ > 0 ? LOG_STR_LITERAL("s") : LOG_STR_LITERAL(" (DISABLED)"))); @@ -244,8 +249,8 @@ void Wireguard::start_connection_() { ESP_LOGD(TAG, "Configuring allowed IPs list"); bool allowed_ips_ok = true; - for (const AllowedIP &ip : this->allowed_ips_) { - allowed_ips_ok &= (esp_wireguard_add_allowed_ip(&(this->wg_ctx_), ip.ip, ip.netmask) == ESP_OK); + for (const AllowedIP *it = this->allowed_ips_; it != nullptr && it->ip != nullptr; it++) { + allowed_ips_ok &= (esp_wireguard_add_allowed_ip(&(this->wg_ctx_), it->ip, it->netmask) == ESP_OK); } if (allowed_ips_ok) { diff --git a/esphome/components/wireguard/wireguard.h b/esphome/components/wireguard/wireguard.h index 68885f3a351..6125d2f52cf 100644 --- a/esphome/components/wireguard/wireguard.h +++ b/esphome/components/wireguard/wireguard.h @@ -2,7 +2,6 @@ #include "esphome/core/defines.h" #ifdef USE_WIREGUARD #include -#include #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -58,9 +57,8 @@ class Wireguard final : public PollingComponent { void set_peer_public_key(const std::string &key) = delete; void set_preshared_key(const std::string &key) = delete; - void set_allowed_ips(std::initializer_list ips) { this->allowed_ips_ = ips; } - /// Prevent accidental use of std::string which would dangle - void set_allowed_ips(std::initializer_list> ips) = delete; + /// Table ended by an entry with a null ip; must outlive the component. + void set_allowed_ips(const AllowedIP *ips) { this->allowed_ips_ = ips; } void set_keepalive(const uint16_t seconds) { this->keepalive_ = seconds; } void set_reboot_timeout(const uint32_t seconds) { this->reboot_timeout_ = seconds; } @@ -105,7 +103,7 @@ class Wireguard final : public PollingComponent { const char *peer_public_key_{nullptr}; const char *preshared_key_{nullptr}; - FixedVector allowed_ips_; + const AllowedIP *allowed_ips_{nullptr}; uint16_t peer_port_; uint16_t keepalive_; @@ -140,6 +138,7 @@ class Wireguard final : public PollingComponent { /// The last time the remote peer become offline. uint32_t wg_peer_offline_time_ = 0; + uint32_t wg_reconnect_time_ = 0; /** \brief The latest saved handshake. * @@ -159,6 +158,7 @@ void resume_wdt(); /// Size of buffer required for mask_key_to: 5 chars + "[...]=" + null = 12 static constexpr size_t MASK_KEY_BUFFER_SIZE = 12; +static constexpr uint32_t RECONNECT_INTERVAL_MS = 30000; /// Strip most part of the key only for secure printing void mask_key_to(char *buffer, size_t len, const char *key); diff --git a/esphome/components/wk2132_i2c/__init__.py b/esphome/components/wk2132_i2c/__init__.py index ecb7a5da09f..7e0999b08f8 100644 --- a/esphome/components/wk2132_i2c/__init__.py +++ b/esphome/components/wk2132_i2c/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@DrCoolZic"] DEPENDENCIES = ["i2c"] AUTO_LOAD = ["weikai", "weikai_i2c"] +DOMAIN = "wk2132_i2c" MULTI_CONF = True weikai_i2c_ns = cg.esphome_ns.namespace("weikai_i2c") diff --git a/esphome/components/wk2132_spi/__init__.py b/esphome/components/wk2132_spi/__init__.py index 94b692f1703..de482839e11 100644 --- a/esphome/components/wk2132_spi/__init__.py +++ b/esphome/components/wk2132_spi/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@DrCoolZic"] DEPENDENCIES = ["spi"] AUTO_LOAD = ["weikai", "weikai_spi"] +DOMAIN = "wk2132_spi" MULTI_CONF = True weikai_spi_ns = cg.esphome_ns.namespace("weikai_spi") diff --git a/esphome/components/wk2168_i2c/__init__.py b/esphome/components/wk2168_i2c/__init__.py index d4aacaf97db..c450717ea2c 100644 --- a/esphome/components/wk2168_i2c/__init__.py +++ b/esphome/components/wk2168_i2c/__init__.py @@ -9,6 +9,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@DrCoolZic"] DEPENDENCIES = ["i2c"] AUTO_LOAD = ["weikai", "weikai_i2c"] +DOMAIN = "wk2168_i2c" MULTI_CONF = True CONF_WK2168_I2C = "wk2168_i2c" diff --git a/esphome/components/wk2168_spi/__init__.py b/esphome/components/wk2168_spi/__init__.py index b4efaf65ac8..ef12117a2c7 100644 --- a/esphome/components/wk2168_spi/__init__.py +++ b/esphome/components/wk2168_spi/__init__.py @@ -9,6 +9,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@DrCoolZic"] DEPENDENCIES = ["spi"] AUTO_LOAD = ["weikai", "weikai_spi"] +DOMAIN = "wk2168_spi" MULTI_CONF = True CONF_WK2168_SPI = "wk2168_spi" diff --git a/esphome/components/wk2204_i2c/__init__.py b/esphome/components/wk2204_i2c/__init__.py index 1502cc6cdb0..b8882d41c11 100644 --- a/esphome/components/wk2204_i2c/__init__.py +++ b/esphome/components/wk2204_i2c/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@DrCoolZic"] DEPENDENCIES = ["i2c"] AUTO_LOAD = ["weikai", "weikai_i2c"] +DOMAIN = "wk2204_i2c" MULTI_CONF = True weikai_i2c_ns = cg.esphome_ns.namespace("weikai_i2c") diff --git a/esphome/components/wk2204_spi/__init__.py b/esphome/components/wk2204_spi/__init__.py index 930f534bb0f..2de911aebde 100644 --- a/esphome/components/wk2204_spi/__init__.py +++ b/esphome/components/wk2204_spi/__init__.py @@ -7,6 +7,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@DrCoolZic"] DEPENDENCIES = ["spi"] AUTO_LOAD = ["weikai", "weikai_spi"] +DOMAIN = "wk2204_spi" MULTI_CONF = True weikai_spi_ns = cg.esphome_ns.namespace("weikai_spi") diff --git a/esphome/components/wk2212_i2c/__init__.py b/esphome/components/wk2212_i2c/__init__.py index 00018dd735f..1f51fea3145 100644 --- a/esphome/components/wk2212_i2c/__init__.py +++ b/esphome/components/wk2212_i2c/__init__.py @@ -9,6 +9,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@DrCoolZic"] DEPENDENCIES = ["i2c"] AUTO_LOAD = ["weikai", "weikai_i2c"] +DOMAIN = "wk2212_i2c" MULTI_CONF = True CONF_WK2212_I2C = "wk2212_i2c" diff --git a/esphome/components/wk2212_spi/__init__.py b/esphome/components/wk2212_spi/__init__.py index c1a050e8ca0..a0cce3331ad 100644 --- a/esphome/components/wk2212_spi/__init__.py +++ b/esphome/components/wk2212_spi/__init__.py @@ -9,6 +9,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@DrCoolZic"] DEPENDENCIES = ["spi"] AUTO_LOAD = ["weikai", "weikai_spi"] +DOMAIN = "wk2212_spi" MULTI_CONF = True CONF_WK2212_SPI = "wk2212_spi" diff --git a/esphome/components/wl_134/__init__.py b/esphome/components/wl_134/__init__.py index e69de29bb2d..03fae78280b 100644 --- a/esphome/components/wl_134/__init__.py +++ b/esphome/components/wl_134/__init__.py @@ -0,0 +1 @@ +DOMAIN = "wl_134" diff --git a/esphome/components/wl_134/wl_134.cpp b/esphome/components/wl_134/wl_134.cpp index 858f974f2b5..745f9ea3e62 100644 --- a/esphome/components/wl_134/wl_134.cpp +++ b/esphome/components/wl_134/wl_134.cpp @@ -6,7 +6,7 @@ namespace esphome::wl_134 { -static const char *const TAG = "wl_134.sensor"; +ESPHOME_LOG_TAG(TAG, "wl_134.sensor"); static const uint8_t ASCII_CR = 0x0D; static const uint8_t ASCII_NBSP = 0xFF; static const int MAX_DATA_LENGTH_BYTES = 6; diff --git a/esphome/components/wled/__init__.py b/esphome/components/wled/__init__.py index a7135975f0f..6b3263e6a3b 100644 --- a/esphome/components/wled/__init__.py +++ b/esphome/components/wled/__init__.py @@ -6,6 +6,8 @@ from esphome.const import CONF_NAME, CONF_PORT from esphome.core import CORE, ID from esphome.types import ConfigType +DOMAIN = "wled" + wled_ns = cg.esphome_ns.namespace("wled") WLEDLightEffect = wled_ns.class_("WLEDLightEffect", AddressableLightEffect) diff --git a/esphome/components/wled/wled_light_effect.cpp b/esphome/components/wled/wled_light_effect.cpp index 5150cda2a51..da0accc9a95 100644 --- a/esphome/components/wled/wled_light_effect.cpp +++ b/esphome/components/wled/wled_light_effect.cpp @@ -25,7 +25,7 @@ enum Protocol { WLED_NOTIFIER = 0, WARLS = 1, DRGB = 2, DRGBW = 3, DNRGB = 4 }; constexpr uint32_t DEFAULT_BLANK_TIME = 1000; -static const char *const TAG = "wled_light_effect"; +ESPHOME_LOG_TAG(TAG, "wled_light_effect"); WLEDLightEffect::WLEDLightEffect(const char *name) : AddressableLightEffect(name) {} diff --git a/esphome/components/wts01/__init__.py b/esphome/components/wts01/__init__.py index e69de29bb2d..5d5a06ea058 100644 --- a/esphome/components/wts01/__init__.py +++ b/esphome/components/wts01/__init__.py @@ -0,0 +1 @@ +DOMAIN = "wts01" diff --git a/esphome/components/wts01/wts01.cpp b/esphome/components/wts01/wts01.cpp index cc7ee980790..2888fc0abb3 100644 --- a/esphome/components/wts01/wts01.cpp +++ b/esphome/components/wts01/wts01.cpp @@ -9,7 +9,7 @@ constexpr uint8_t HEADER_2 = 0x01; constexpr uint8_t HEADER_3 = 0x01; constexpr uint8_t HEADER_4 = 0x04; -static const char *const TAG = "wts01"; +ESPHOME_LOG_TAG(TAG, "wts01"); void WTS01Sensor::loop() { // Process all available data at once diff --git a/esphome/components/x9c/__init__.py b/esphome/components/x9c/__init__.py index e69de29bb2d..46a7316bd14 100644 --- a/esphome/components/x9c/__init__.py +++ b/esphome/components/x9c/__init__.py @@ -0,0 +1 @@ +DOMAIN = "x9c" diff --git a/esphome/components/x9c/x9c.cpp b/esphome/components/x9c/x9c.cpp index b0ad79e51cd..a9e9e9cdae9 100644 --- a/esphome/components/x9c/x9c.cpp +++ b/esphome/components/x9c/x9c.cpp @@ -3,7 +3,7 @@ namespace esphome::x9c { -static const char *const TAG = "x9c.output"; +ESPHOME_LOG_TAG(TAG, "x9c.output"); void X9cOutput::trim_value(int32_t change_amount) { if (change_amount == 0) { diff --git a/esphome/components/xdb401/__init__.py b/esphome/components/xdb401/__init__.py index 943139e19a3..181b38caca3 100644 --- a/esphome/components/xdb401/__init__.py +++ b/esphome/components/xdb401/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@RT530"] +DOMAIN = "xdb401" diff --git a/esphome/components/xdb401/xdb401.cpp b/esphome/components/xdb401/xdb401.cpp index 6deab23828d..5539a1318fd 100644 --- a/esphome/components/xdb401/xdb401.cpp +++ b/esphome/components/xdb401/xdb401.cpp @@ -4,7 +4,7 @@ namespace esphome::xdb401 { -static const char *const TAG = "xdb401"; +ESPHOME_LOG_TAG(TAG, "xdb401"); static const uint8_t REG_PRESSURE = 0x06; static const uint8_t REG_TEMPERATURE = 0x09; diff --git a/esphome/components/xgzp68xx/__init__.py b/esphome/components/xgzp68xx/__init__.py index 122ffaf6edf..6cebeffba1e 100644 --- a/esphome/components/xgzp68xx/__init__.py +++ b/esphome/components/xgzp68xx/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@gcormier"] +DOMAIN = "xgzp68xx" diff --git a/esphome/components/xgzp68xx/xgzp68xx.cpp b/esphome/components/xgzp68xx/xgzp68xx.cpp index 0eefd594d09..795db448f0b 100644 --- a/esphome/components/xgzp68xx/xgzp68xx.cpp +++ b/esphome/components/xgzp68xx/xgzp68xx.cpp @@ -8,7 +8,9 @@ namespace esphome::xgzp68xx { -static const char *const TAG = "xgzp68xx.sensor"; +ESPHOME_LOG_TAG(TAG, "xgzp68xx.sensor"); + +static constexpr uint32_t MEASUREMENT_TIMEOUT_ID = 0; static const uint8_t CMD_ADDRESS = 0x30; static const uint8_t SYSCONFIG_ADDRESS = 0xA5; @@ -54,7 +56,7 @@ void XGZP68XXComponent::update() { this->write_register(0x30, &READ_COMMAND, 1); // Wait 20mS per datasheet - this->set_timeout("measurement", 20, [this]() { + this->set_timeout(MEASUREMENT_TIMEOUT_ID, 20, [this]() { uint8_t data[5] = {}; uint32_t pressure_raw = 0; uint16_t temperature_raw = 0; diff --git a/esphome/components/xiaomi_ble/__init__.py b/esphome/components/xiaomi_ble/__init__.py index 16da83934e6..de7998b290a 100644 --- a/esphome/components/xiaomi_ble/__init__.py +++ b/esphome/components/xiaomi_ble/__init__.py @@ -5,6 +5,7 @@ from esphome.const import CONF_ID from esphome.types import ConfigType AUTO_LOAD = ["ble_device_base"] +DOMAIN = "xiaomi_ble" xiaomi_ble_ns = cg.esphome_ns.namespace("xiaomi_ble") XiaomiListener = xiaomi_ble_ns.class_( diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 06c3a7ab7a0..57f26b6acc7 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -21,7 +21,7 @@ namespace esphome::xiaomi_ble { -static const char *const TAG = "xiaomi_ble"; +ESPHOME_LOG_TAG(TAG, "xiaomi_ble"); // Maximum bytes to log in very verbose hex output (covers largest packet of ~24 bytes) static constexpr size_t XIAOMI_MAX_LOG_BYTES = 32; diff --git a/esphome/components/xiaomi_body_scale/__init__.py b/esphome/components/xiaomi_body_scale/__init__.py index 3d75ef24ecb..aeb29d234f3 100644 --- a/esphome/components/xiaomi_body_scale/__init__.py +++ b/esphome/components/xiaomi_body_scale/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@dckiller51"] +DOMAIN = "xiaomi_body_scale" diff --git a/esphome/components/xiaomi_body_scale/xiaomi_body_scale.cpp b/esphome/components/xiaomi_body_scale/xiaomi_body_scale.cpp index 7f9cd17d1f1..b59c662e806 100644 --- a/esphome/components/xiaomi_body_scale/xiaomi_body_scale.cpp +++ b/esphome/components/xiaomi_body_scale/xiaomi_body_scale.cpp @@ -7,7 +7,7 @@ namespace esphome::xiaomi_body_scale { -static const char *const TAG = "xiaomi_body_scale"; +ESPHOME_LOG_TAG(TAG, "xiaomi_body_scale"); // Encrypted MiBeacon frame without a MAC: frame control, device id, frame count, cipher, counter, tag static constexpr size_t DEVICE_ID_POS = 2; diff --git a/esphome/components/xiaomi_cgd1/__init__.py b/esphome/components/xiaomi_cgd1/__init__.py index e69de29bb2d..f76e529bb2a 100644 --- a/esphome/components/xiaomi_cgd1/__init__.py +++ b/esphome/components/xiaomi_cgd1/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_cgd1" diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp index 0159314f4d6..d64e66e660c 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp @@ -4,7 +4,7 @@ namespace esphome::xiaomi_cgd1 { -static const char *const TAG = "xiaomi_cgd1"; +ESPHOME_LOG_TAG(TAG, "xiaomi_cgd1"); static constexpr size_t CGD1_BINDKEY_SIZE = 16; diff --git a/esphome/components/xiaomi_cgdk2/__init__.py b/esphome/components/xiaomi_cgdk2/__init__.py index e69de29bb2d..5a9b433236a 100644 --- a/esphome/components/xiaomi_cgdk2/__init__.py +++ b/esphome/components/xiaomi_cgdk2/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_cgdk2" diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp index 01912c37782..7ac6d50e167 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp @@ -4,7 +4,7 @@ namespace esphome::xiaomi_cgdk2 { -static const char *const TAG = "xiaomi_cgdk2"; +ESPHOME_LOG_TAG(TAG, "xiaomi_cgdk2"); static constexpr size_t CGDK2_BINDKEY_SIZE = 16; diff --git a/esphome/components/xiaomi_cgg1/__init__.py b/esphome/components/xiaomi_cgg1/__init__.py index e69de29bb2d..ea01df8c579 100644 --- a/esphome/components/xiaomi_cgg1/__init__.py +++ b/esphome/components/xiaomi_cgg1/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_cgg1" diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp index 679cb76198b..64207868b74 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp @@ -4,7 +4,7 @@ namespace esphome::xiaomi_cgg1 { -static const char *const TAG = "xiaomi_cgg1"; +ESPHOME_LOG_TAG(TAG, "xiaomi_cgg1"); static constexpr size_t CGG1_BINDKEY_SIZE = 16; diff --git a/esphome/components/xiaomi_cgpr1/__init__.py b/esphome/components/xiaomi_cgpr1/__init__.py index e69de29bb2d..b17553724dc 100644 --- a/esphome/components/xiaomi_cgpr1/__init__.py +++ b/esphome/components/xiaomi_cgpr1/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_cgpr1" diff --git a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp index 019de54ad86..1515e43dea0 100644 --- a/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp +++ b/esphome/components/xiaomi_cgpr1/xiaomi_cgpr1.cpp @@ -4,7 +4,7 @@ namespace esphome::xiaomi_cgpr1 { -static const char *const TAG = "xiaomi_cgpr1"; +ESPHOME_LOG_TAG(TAG, "xiaomi_cgpr1"); void XiaomiCGPR1::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi CGPR1"); diff --git a/esphome/components/xiaomi_gcls002/__init__.py b/esphome/components/xiaomi_gcls002/__init__.py index e69de29bb2d..643a26ffe20 100644 --- a/esphome/components/xiaomi_gcls002/__init__.py +++ b/esphome/components/xiaomi_gcls002/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_gcls002" diff --git a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp index 27effd64bb1..11cb3a98d31 100644 --- a/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp +++ b/esphome/components/xiaomi_gcls002/xiaomi_gcls002.cpp @@ -3,7 +3,7 @@ namespace esphome::xiaomi_gcls002 { -static const char *const TAG = "xiaomi_gcls002"; +ESPHOME_LOG_TAG(TAG, "xiaomi_gcls002"); void XiaomiGCLS002::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi GCLS002"); diff --git a/esphome/components/xiaomi_hhccjcy01/__init__.py b/esphome/components/xiaomi_hhccjcy01/__init__.py index e69de29bb2d..e8e38dc51f7 100644 --- a/esphome/components/xiaomi_hhccjcy01/__init__.py +++ b/esphome/components/xiaomi_hhccjcy01/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_hhccjcy01" diff --git a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp index 5e2369a6d95..80de9a6cbc8 100644 --- a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp +++ b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp @@ -3,7 +3,7 @@ namespace esphome::xiaomi_hhccjcy01 { -static const char *const TAG = "xiaomi_hhccjcy01"; +ESPHOME_LOG_TAG(TAG, "xiaomi_hhccjcy01"); void XiaomiHHCCJCY01::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi HHCCJCY01"); diff --git a/esphome/components/xiaomi_hhccjcy10/__init__.py b/esphome/components/xiaomi_hhccjcy10/__init__.py index d47cef13c66..eeb0e7d7efa 100644 --- a/esphome/components/xiaomi_hhccjcy10/__init__.py +++ b/esphome/components/xiaomi_hhccjcy10/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@fariouche"] +DOMAIN = "xiaomi_hhccjcy10" diff --git a/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp b/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp index 680eb04e777..ac1541c386d 100644 --- a/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp +++ b/esphome/components/xiaomi_hhccjcy10/xiaomi_hhccjcy10.cpp @@ -4,7 +4,7 @@ namespace esphome::xiaomi_hhccjcy10 { -static const char *const TAG = "xiaomi_hhccjcy10"; +ESPHOME_LOG_TAG(TAG, "xiaomi_hhccjcy10"); void XiaomiHHCCJCY10::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi HHCCJCY10"); diff --git a/esphome/components/xiaomi_hhccpot002/__init__.py b/esphome/components/xiaomi_hhccpot002/__init__.py index e69de29bb2d..4d74c2f432b 100644 --- a/esphome/components/xiaomi_hhccpot002/__init__.py +++ b/esphome/components/xiaomi_hhccpot002/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_hhccpot002" diff --git a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp index fc8d15228d1..f502cb1f660 100644 --- a/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp +++ b/esphome/components/xiaomi_hhccpot002/xiaomi_hhccpot002.cpp @@ -3,7 +3,7 @@ namespace esphome::xiaomi_hhccpot002 { -static const char *const TAG = "xiaomi_hhccpot002"; +ESPHOME_LOG_TAG(TAG, "xiaomi_hhccpot002"); void XiaomiHHCCPOT002 ::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi HHCCPOT002"); diff --git a/esphome/components/xiaomi_jqjcy01ym/__init__.py b/esphome/components/xiaomi_jqjcy01ym/__init__.py index e69de29bb2d..e381fe61729 100644 --- a/esphome/components/xiaomi_jqjcy01ym/__init__.py +++ b/esphome/components/xiaomi_jqjcy01ym/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_jqjcy01ym" diff --git a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp index f7a1318d7c3..ad2383f107b 100644 --- a/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp +++ b/esphome/components/xiaomi_jqjcy01ym/xiaomi_jqjcy01ym.cpp @@ -3,7 +3,7 @@ namespace esphome::xiaomi_jqjcy01ym { -static const char *const TAG = "xiaomi_jqjcy01ym"; +ESPHOME_LOG_TAG(TAG, "xiaomi_jqjcy01ym"); void XiaomiJQJCY01YM::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi JQJCY01YM"); diff --git a/esphome/components/xiaomi_lywsd02/__init__.py b/esphome/components/xiaomi_lywsd02/__init__.py index e69de29bb2d..e8eb4933388 100644 --- a/esphome/components/xiaomi_lywsd02/__init__.py +++ b/esphome/components/xiaomi_lywsd02/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_lywsd02" diff --git a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp index d465f2fec08..0762d97eb09 100644 --- a/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp +++ b/esphome/components/xiaomi_lywsd02/xiaomi_lywsd02.cpp @@ -3,7 +3,7 @@ namespace esphome::xiaomi_lywsd02 { -static const char *const TAG = "xiaomi_lywsd02"; +ESPHOME_LOG_TAG(TAG, "xiaomi_lywsd02"); void XiaomiLYWSD02::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi LYWSD02"); diff --git a/esphome/components/xiaomi_lywsd02mmc/__init__.py b/esphome/components/xiaomi_lywsd02mmc/__init__.py index e69de29bb2d..2398660e95d 100644 --- a/esphome/components/xiaomi_lywsd02mmc/__init__.py +++ b/esphome/components/xiaomi_lywsd02mmc/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_lywsd02mmc" diff --git a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp index dca5f73909b..15d9f32bd55 100644 --- a/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp +++ b/esphome/components/xiaomi_lywsd02mmc/xiaomi_lywsd02mmc.cpp @@ -4,7 +4,7 @@ namespace esphome::xiaomi_lywsd02mmc { -static const char *const TAG = "xiaomi_lywsd02mmc"; +ESPHOME_LOG_TAG(TAG, "xiaomi_lywsd02mmc"); static constexpr size_t LYWSD02MMC_BINDKEY_SIZE = 16; diff --git a/esphome/components/xiaomi_lywsd03mmc/__init__.py b/esphome/components/xiaomi_lywsd03mmc/__init__.py index e69de29bb2d..3d1638fc11d 100644 --- a/esphome/components/xiaomi_lywsd03mmc/__init__.py +++ b/esphome/components/xiaomi_lywsd03mmc/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_lywsd03mmc" diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp index 356a4ffd4e1..3bf8b7328b1 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp @@ -4,7 +4,7 @@ namespace esphome::xiaomi_lywsd03mmc { -static const char *const TAG = "xiaomi_lywsd03mmc"; +ESPHOME_LOG_TAG(TAG, "xiaomi_lywsd03mmc"); static constexpr size_t LYWSD03MMC_BINDKEY_SIZE = 16; diff --git a/esphome/components/xiaomi_lywsdcgq/__init__.py b/esphome/components/xiaomi_lywsdcgq/__init__.py index e69de29bb2d..bbf235aa96c 100644 --- a/esphome/components/xiaomi_lywsdcgq/__init__.py +++ b/esphome/components/xiaomi_lywsdcgq/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_lywsdcgq" diff --git a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp index 1ddf7ec235f..1c56ed34f62 100644 --- a/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp +++ b/esphome/components/xiaomi_lywsdcgq/xiaomi_lywsdcgq.cpp @@ -3,7 +3,7 @@ namespace esphome::xiaomi_lywsdcgq { -static const char *const TAG = "xiaomi_lywsdcgq"; +ESPHOME_LOG_TAG(TAG, "xiaomi_lywsdcgq"); void XiaomiLYWSDCGQ::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi LYWSDCGQ"); diff --git a/esphome/components/xiaomi_mccgq02hl/__init__.py b/esphome/components/xiaomi_mccgq02hl/__init__.py new file mode 100644 index 00000000000..a65bad1cb14 --- /dev/null +++ b/esphome/components/xiaomi_mccgq02hl/__init__.py @@ -0,0 +1,2 @@ +CODEOWNERS = ["@ahpohl", "@morph027"] +DOMAIN = "xiaomi_mccgq02hl" diff --git a/esphome/components/xiaomi_mccgq02hl/binary_sensor.py b/esphome/components/xiaomi_mccgq02hl/binary_sensor.py new file mode 100644 index 00000000000..f410ad7f739 --- /dev/null +++ b/esphome/components/xiaomi_mccgq02hl/binary_sensor.py @@ -0,0 +1,65 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor, ble_device_base, sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_BINDKEY, + CONF_LIGHT, + CONF_MAC_ADDRESS, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_LIGHT, + DEVICE_CLASS_OPENING, + ENTITY_CATEGORY_DIAGNOSTIC, + STATE_CLASS_MEASUREMENT, + UNIT_PERCENT, +) +from esphome.types import ConfigType + +AUTO_LOAD = ["ble_device_base", "sensor"] + +xiaomi_mccgq02hl_ns = cg.esphome_ns.namespace("xiaomi_mccgq02hl") +XiaomiMCCGQ02HL = xiaomi_mccgq02hl_ns.class_( + "XiaomiMCCGQ02HL", + binary_sensor.BinarySensor, + cg.Component, + ble_device_base.ESPBTDeviceListener, +) + +CONFIG_SCHEMA = cv.All( + ble_device_base.rename_legacy_hub_id("xiaomi_mccgq02hl"), + binary_sensor.binary_sensor_schema( + XiaomiMCCGQ02HL, device_class=DEVICE_CLASS_OPENING + ) + .extend( + { + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Required(CONF_BINDKEY): cv.bind_key, + cv.Optional(CONF_LIGHT): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_LIGHT + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(ble_device_base.BLE_DEVICE_SCHEMA), +) + + +async def to_code(config: ConfigType) -> None: + var = await binary_sensor.new_binary_sensor(config) + await cg.register_component(var, config) + await ble_device_base.register_ble_device(var, config) + + cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) + cg.add(var.set_bindkey(config[CONF_BINDKEY])) + + sensors = sensor.sub_sensors(config) + await sensors(CONF_BATTERY_LEVEL, var.set_battery_level) + binary_sensors = binary_sensor.sub_binary_sensors(config) + await binary_sensors(CONF_LIGHT, var.set_light) diff --git a/esphome/components/xiaomi_mccgq02hl/xiaomi_mccgq02hl.cpp b/esphome/components/xiaomi_mccgq02hl/xiaomi_mccgq02hl.cpp new file mode 100644 index 00000000000..519358270d3 --- /dev/null +++ b/esphome/components/xiaomi_mccgq02hl/xiaomi_mccgq02hl.cpp @@ -0,0 +1,168 @@ +#include "xiaomi_mccgq02hl.h" +#include "esphome/components/ble_device_base/ble_aes_ccm.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#include + +namespace esphome::xiaomi_mccgq02hl { + +ESPHOME_LOG_TAG(TAG, "xiaomi_mccgq02hl"); + +static constexpr uint16_t PRODUCT_ID = 0x098b; + +// MiBeacon frame control, low byte (raw[0]) +static constexpr uint8_t FC_ENCRYPTED = 0x08; +static constexpr uint8_t FC_MAC_INCLUDED = 0x10; +static constexpr uint8_t FC_CAPABILITY = 0x20; +static constexpr uint8_t FC_OBJECT = 0x40; + +// Encrypted frames end in a 3-byte extended counter and a 4-byte MIC. +static constexpr size_t MIC_SIZE = 4; +static constexpr size_t ENCRYPTED_TRAILER = 3 + MIC_SIZE; +// Largest encrypted payload accepted; MCCGQ02HL objects are 4 bytes. +static constexpr size_t MAX_PAYLOAD_SIZE = 16; + +// Object ids. The door and light objects come in both the 0x00xx and the +// 0x10xx flavour depending on firmware; the semantics are identical. +static constexpr uint16_t OBJ_LIGHT = 0x0018; +static constexpr uint16_t OBJ_LIGHT_ALT = 0x1018; +static constexpr uint16_t OBJ_DOOR = 0x0019; +static constexpr uint16_t OBJ_DOOR_ALT = 0x1019; +static constexpr uint16_t OBJ_BATTERY = 0x100A; +static constexpr uint16_t OBJ_BATTERY_ALT = 0x4803; + +void XiaomiMCCGQ02HL::dump_config() { + uint8_t mac[MAC_ADDRESS_SIZE]; + ble_device_base::uint64_to_mac_msb_first(this->address_, mac); + char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE]; + ESP_LOGCONFIG(TAG, + "Xiaomi MCCGQ02HL\n" + " MAC Address: %s", + format_mac_addr_upper(mac, mac_buf)); + LOG_BINARY_SENSOR(" ", "Opening", this); + LOG_BINARY_SENSOR(" ", "Light", this->light_); + LOG_SENSOR(" ", "Battery Level", this->battery_level_); +} + +void XiaomiMCCGQ02HL::set_bindkey(const char *bindkey) { parse_hex(bindkey, this->bindkey_, sizeof(this->bindkey_)); } + +bool XiaomiMCCGQ02HL::parse_device(const ble_device_base::ESPBTDevice &device) { + if (device.address_uint64() != this->address_) + return false; + + bool success = false; + for (auto &service_data : device.get_service_datas()) { + if (!service_data.uuid.contains(0x95, 0xFE)) + continue; + + Reading reading; + if (!this->parse_service_data_(service_data.data, reading)) + continue; + + ESP_LOGD(TAG, "%s: open=%s light=%s battery=%s", this->get_name().c_str(), + reading.open.has_value() ? (*reading.open ? LOG_STR_LITERAL("yes") : LOG_STR_LITERAL("no")) + : LOG_STR_LITERAL("-"), + reading.light.has_value() ? (*reading.light ? LOG_STR_LITERAL("yes") : LOG_STR_LITERAL("no")) + : LOG_STR_LITERAL("-"), + reading.battery_level.has_value() ? LOG_STR_LITERAL("updated") : LOG_STR_LITERAL("-")); + + if (reading.open.has_value()) + this->publish_state(*reading.open); + if (reading.light.has_value() && this->light_ != nullptr) + this->light_->publish_state(*reading.light); + if (reading.battery_level.has_value() && this->battery_level_ != nullptr) + this->battery_level_->publish_state(*reading.battery_level); + success = true; + } + return success; +} + +bool XiaomiMCCGQ02HL::decrypt_(const uint8_t *frame, size_t size, size_t offset, uint8_t *plaintext) const { + uint8_t nonce[MAC_ADDRESS_SIZE + 6]; + for (size_t i = 0; i < MAC_ADDRESS_SIZE; i++) + nonce[i] = static_cast(this->address_ >> (i * 8)); // MAC, reversed + memcpy(nonce + MAC_ADDRESS_SIZE, frame + 2, 3); // product id + frame count + memcpy(nonce + MAC_ADDRESS_SIZE + 3, frame + size - ENCRYPTED_TRAILER, 3); // extended counter + static constexpr uint8_t AUTH_DATA[1] = {0x11}; + return ble_device_base::aes_ccm_auth_decrypt(this->bindkey_, nonce, sizeof(nonce), AUTH_DATA, sizeof(AUTH_DATA), + frame + offset, size - offset - ENCRYPTED_TRAILER, plaintext, + frame + size - MIC_SIZE, MIC_SIZE); +} + +bool XiaomiMCCGQ02HL::parse_service_data_(const std::vector &data, Reading &reading) { + const size_t size = data.size(); + if (size < 5) + return false; + const uint8_t *raw = data.data(); + + const uint8_t fc = raw[0]; + if (!(fc & FC_OBJECT)) + return false; + if (encode_uint16(raw[3], raw[2]) != PRODUCT_ID) { + ESP_LOGVV(TAG, "Not an MCCGQ02HL frame (product id %02X%02X).", raw[3], raw[2]); + return false; + } + + const uint8_t frame_count = raw[4]; + if (this->last_frame_count_.has_value() && *this->last_frame_count_ == frame_count) { + ESP_LOGVV(TAG, "Duplicate frame %u.", frame_count); + return false; + } + + // The bindkey is required, so plaintext frames are never trusted + if (!(fc & FC_ENCRYPTED)) { + ESP_LOGVV(TAG, "Ignoring unencrypted frame %u.", frame_count); + return false; + } + + const size_t offset = 5 + ((fc & FC_MAC_INCLUDED) ? 6 : 0) + ((fc & FC_CAPABILITY) ? 1 : 0); + if (size <= offset + ENCRYPTED_TRAILER || size - offset - ENCRYPTED_TRAILER > MAX_PAYLOAD_SIZE) { + ESP_LOGW(TAG, "Unsupported encrypted frame layout (fc=0x%02X, %u bytes).", fc, (unsigned) size); + return false; + } + uint8_t plaintext[MAX_PAYLOAD_SIZE]; + if (!this->decrypt_(raw, size, offset, plaintext)) { + ESP_LOGW(TAG, "Decryption failed (%u-byte frame) -- check the bindkey.", (unsigned) size); + return false; + } + // Only an authenticated frame may advance the duplicate filter. There is no replay protection: the + // counter is not persisted and restarts on a battery change, so a strictly increasing check could lock + // the sensor out until the next reboot. + this->last_frame_count_ = frame_count; + return this->parse_objects_(plaintext, size - offset - ENCRYPTED_TRAILER, reading); +} + +bool XiaomiMCCGQ02HL::parse_objects_(const uint8_t *payload, size_t length, Reading &reading) { + bool found = false; + // Each object: id (uint16 LE), length (uint8), value. + while (length >= 3) { + const uint16_t id = encode_uint16(payload[1], payload[0]); + const uint8_t len = payload[2]; + if (len < 1 || length < 3u + len) + break; + const uint8_t *value = payload + 3; + + if ((id == OBJ_DOOR || id == OBJ_DOOR_ALT) && len == 1) { + // 0 = open, 1 = closed, 2 = left open past timeout, 3 = device reset + if (value[0] <= 2) { + reading.open = value[0] != 1; + found = true; + } + } else if ((id == OBJ_LIGHT || id == OBJ_LIGHT_ALT) && len == 1) { + reading.light = value[0] != 0; + found = true; + } else if ((id == OBJ_BATTERY || id == OBJ_BATTERY_ALT) && len == 1) { + reading.battery_level = value[0]; + found = true; + } else { + ESP_LOGVV(TAG, "Ignoring object 0x%04X (%u bytes).", id, len); + } + + payload += 3 + len; + length -= 3 + len; + } + return found; +} + +} // namespace esphome::xiaomi_mccgq02hl diff --git a/esphome/components/xiaomi_mccgq02hl/xiaomi_mccgq02hl.h b/esphome/components/xiaomi_mccgq02hl/xiaomi_mccgq02hl.h new file mode 100644 index 00000000000..8897752b449 --- /dev/null +++ b/esphome/components/xiaomi_mccgq02hl/xiaomi_mccgq02hl.h @@ -0,0 +1,50 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/optional.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/ble_device_base/ble_device.h" + +#include + +namespace esphome::xiaomi_mccgq02hl { + +// Xiaomi Mijia Door/Window Sensor 2 (MCCGQ02HL), MiBeacon product id 0x098b. +// +// Self-contained on purpose: xiaomi_ble's parse_xiaomi_header() does not know +// this product id and parse_xiaomi_value() does not know the door object, so +// this class does its own header and object parsing and decryption. +class XiaomiMCCGQ02HL final : public Component, + public binary_sensor::BinarySensorInitiallyOff, + public ble_device_base::ESPBTDeviceListener { + public: + void set_address(uint64_t address) { this->address_ = address; } + void set_bindkey(const char *bindkey); + void set_light(binary_sensor::BinarySensor *light) { this->light_ = light; } + void set_battery_level(sensor::Sensor *battery_level) { this->battery_level_ = battery_level; } + + bool parse_device(const ble_device_base::ESPBTDevice &device) override; + void dump_config() override; + + protected: + struct Reading { + optional open; + optional light; + optional battery_level; + }; + + bool decrypt_(const uint8_t *frame, size_t size, size_t offset, uint8_t *plaintext) const; + bool parse_service_data_(const std::vector &data, Reading &reading); + bool parse_objects_(const uint8_t *payload, size_t length, Reading &reading); + + uint64_t address_{0}; + uint8_t bindkey_[16]{}; + // Per instance, unlike xiaomi_ble's function-static counter which is shared + // by every Xiaomi device on the node. + optional last_frame_count_; + binary_sensor::BinarySensor *light_{nullptr}; + sensor::Sensor *battery_level_{nullptr}; +}; + +} // namespace esphome::xiaomi_mccgq02hl diff --git a/esphome/components/xiaomi_mhoc303/__init__.py b/esphome/components/xiaomi_mhoc303/__init__.py index e7005b3957c..5ffa02332f8 100644 --- a/esphome/components/xiaomi_mhoc303/__init__.py +++ b/esphome/components/xiaomi_mhoc303/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@drug123"] +DOMAIN = "xiaomi_mhoc303" diff --git a/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp b/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp index 9706e508618..23f20f380af 100644 --- a/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp +++ b/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp @@ -3,7 +3,7 @@ namespace esphome::xiaomi_mhoc303 { -static const char *const TAG = "xiaomi_mhoc303"; +ESPHOME_LOG_TAG(TAG, "xiaomi_mhoc303"); void XiaomiMHOC303::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi MHOC303"); diff --git a/esphome/components/xiaomi_mhoc401/__init__.py b/esphome/components/xiaomi_mhoc401/__init__.py index e69de29bb2d..14fe18af57c 100644 --- a/esphome/components/xiaomi_mhoc401/__init__.py +++ b/esphome/components/xiaomi_mhoc401/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_mhoc401" diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp index d7259784187..97266c86e75 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp @@ -4,7 +4,7 @@ namespace esphome::xiaomi_mhoc401 { -static const char *const TAG = "xiaomi_mhoc401"; +ESPHOME_LOG_TAG(TAG, "xiaomi_mhoc401"); static constexpr size_t MHOC401_BINDKEY_SIZE = 16; diff --git a/esphome/components/xiaomi_miscale/__init__.py b/esphome/components/xiaomi_miscale/__init__.py index e69de29bb2d..ddda4a0a5b5 100644 --- a/esphome/components/xiaomi_miscale/__init__.py +++ b/esphome/components/xiaomi_miscale/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_miscale" diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp index 482c0ed3958..438b9b55298 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp @@ -4,7 +4,7 @@ namespace esphome::xiaomi_miscale { -static const char *const TAG = "xiaomi_miscale"; +ESPHOME_LOG_TAG(TAG, "xiaomi_miscale"); void XiaomiMiscale::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi Miscale"); diff --git a/esphome/components/xiaomi_miscale2/__init__.py b/esphome/components/xiaomi_miscale2/__init__.py index e69de29bb2d..346db024455 100644 --- a/esphome/components/xiaomi_miscale2/__init__.py +++ b/esphome/components/xiaomi_miscale2/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_miscale2" diff --git a/esphome/components/xiaomi_mjyd02yla/__init__.py b/esphome/components/xiaomi_mjyd02yla/__init__.py index e69de29bb2d..eb881ca39ac 100644 --- a/esphome/components/xiaomi_mjyd02yla/__init__.py +++ b/esphome/components/xiaomi_mjyd02yla/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_mjyd02yla" diff --git a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp index 233f5f57832..767bcd8da04 100644 --- a/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp +++ b/esphome/components/xiaomi_mjyd02yla/xiaomi_mjyd02yla.cpp @@ -4,7 +4,7 @@ namespace esphome::xiaomi_mjyd02yla { -static const char *const TAG = "xiaomi_mjyd02yla"; +ESPHOME_LOG_TAG(TAG, "xiaomi_mjyd02yla"); void XiaomiMJYD02YLA::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi MJYD02YL-A"); diff --git a/esphome/components/xiaomi_mue4094rt/__init__.py b/esphome/components/xiaomi_mue4094rt/__init__.py index e69de29bb2d..3d539b267da 100644 --- a/esphome/components/xiaomi_mue4094rt/__init__.py +++ b/esphome/components/xiaomi_mue4094rt/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_mue4094rt" diff --git a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp index eca83c09124..683e8a2a927 100644 --- a/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp +++ b/esphome/components/xiaomi_mue4094rt/xiaomi_mue4094rt.cpp @@ -3,7 +3,9 @@ namespace esphome::xiaomi_mue4094rt { -static const char *const TAG = "xiaomi_mue4094rt"; +ESPHOME_LOG_TAG(TAG, "xiaomi_mue4094rt"); + +static constexpr uint32_t MOTION_TIMEOUT_ID = 0; void XiaomiMUE4094RT::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi MUE4094RT"); @@ -40,7 +42,7 @@ bool XiaomiMUE4094RT::parse_device(const ble_device_base::ESPBTDevice &device) { } if (res->has_motion.has_value()) { this->publish_state(*res->has_motion); - this->set_timeout("motion_timeout", timeout_, [this]() { this->publish_state(false); }); + this->set_timeout(MOTION_TIMEOUT_ID, timeout_, [this]() { this->publish_state(false); }); } success = true; } diff --git a/esphome/components/xiaomi_rtcgq02lm/__init__.py b/esphome/components/xiaomi_rtcgq02lm/__init__.py index 7b289a8ee3e..71d7e0a265b 100644 --- a/esphome/components/xiaomi_rtcgq02lm/__init__.py +++ b/esphome/components/xiaomi_rtcgq02lm/__init__.py @@ -6,6 +6,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["ble_device_base", "xiaomi_ble"] CODEOWNERS = ["@jesserockz"] +DOMAIN = "xiaomi_rtcgq02lm" MULTI_CONF = True xiaomi_rtcgq02lm_ns = cg.esphome_ns.namespace("xiaomi_rtcgq02lm") diff --git a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp index f349dfa7971..f7ecf15c3bb 100644 --- a/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp +++ b/esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp @@ -4,7 +4,10 @@ namespace esphome::xiaomi_rtcgq02lm { -static const char *const TAG = "xiaomi_rtcgq02lm"; +ESPHOME_LOG_TAG(TAG, "xiaomi_rtcgq02lm"); + +static constexpr uint32_t MOTION_TIMEOUT_ID = 0; +static constexpr uint32_t BUTTON_TIMEOUT_ID = 1; static constexpr size_t RTCGQ02LM_BINDKEY_SIZE = 16; @@ -55,13 +58,13 @@ bool XiaomiRTCGQ02LM::parse_device(const ble_device_base::ESPBTDevice &device) { #ifdef USE_BINARY_SENSOR if (res->has_motion.has_value() && this->motion_ != nullptr) { this->motion_->publish_state(*res->has_motion); - this->set_timeout("motion_timeout", this->motion_timeout_, [this]() { this->motion_->publish_state(false); }); + this->set_timeout(MOTION_TIMEOUT_ID, this->motion_timeout_, [this]() { this->motion_->publish_state(false); }); } if (res->is_light.has_value() && this->light_ != nullptr) this->light_->publish_state(*res->is_light); if (res->button_press.has_value() && this->button_ != nullptr) { this->button_->publish_state(*res->button_press); - this->set_timeout("button_timeout", this->button_timeout_, [this]() { this->button_->publish_state(false); }); + this->set_timeout(BUTTON_TIMEOUT_ID, this->button_timeout_, [this]() { this->button_->publish_state(false); }); } #endif #ifdef USE_SENSOR diff --git a/esphome/components/xiaomi_wx08zm/__init__.py b/esphome/components/xiaomi_wx08zm/__init__.py index e69de29bb2d..d795931df95 100644 --- a/esphome/components/xiaomi_wx08zm/__init__.py +++ b/esphome/components/xiaomi_wx08zm/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_wx08zm" diff --git a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp index ae37d630964..420b67e7883 100644 --- a/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp +++ b/esphome/components/xiaomi_wx08zm/xiaomi_wx08zm.cpp @@ -3,7 +3,7 @@ namespace esphome::xiaomi_wx08zm { -static const char *const TAG = "xiaomi_wx08zm"; +ESPHOME_LOG_TAG(TAG, "xiaomi_wx08zm"); void XiaomiWX08ZM::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi WX08ZM"); diff --git a/esphome/components/xiaomi_xmwsdj04mmc/__init__.py b/esphome/components/xiaomi_xmwsdj04mmc/__init__.py index e69de29bb2d..98af32069cf 100644 --- a/esphome/components/xiaomi_xmwsdj04mmc/__init__.py +++ b/esphome/components/xiaomi_xmwsdj04mmc/__init__.py @@ -0,0 +1 @@ +DOMAIN = "xiaomi_xmwsdj04mmc" diff --git a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp index aba954fd915..65ab9e342db 100644 --- a/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp +++ b/esphome/components/xiaomi_xmwsdj04mmc/xiaomi_xmwsdj04mmc.cpp @@ -4,7 +4,7 @@ namespace esphome::xiaomi_xmwsdj04mmc { -static const char *const TAG = "xiaomi_xmwsdj04mmc"; +ESPHOME_LOG_TAG(TAG, "xiaomi_xmwsdj04mmc"); static constexpr size_t XMWSDJ04MMC_BINDKEY_SIZE = 16; diff --git a/esphome/components/xl9535/__init__.py b/esphome/components/xl9535/__init__.py index 5686b741738..1c6548bb5fe 100644 --- a/esphome/components/xl9535/__init__.py +++ b/esphome/components/xl9535/__init__.py @@ -17,6 +17,7 @@ CONF_XL9535 = "xl9535" DEPENDENCIES = ["i2c"] CODEOWNERS = ["@mreditor97"] +DOMAIN = "xl9535" xl9535_ns = cg.esphome_ns.namespace(CONF_XL9535) @@ -54,7 +55,7 @@ def validate_pin(pin: int) -> int: XL9535_PIN_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(XL9535GPIOPin), - cv.Required(CONF_XL9535): cv.use_id(XL9535Component), + cv.Required(CONF_XL9535): pins.use_id_or_address(XL9535Component), cv.Required(CONF_NUMBER): cv.All(cv.int_range(min=0, max=17), validate_pin), cv.Optional(CONF_MODE, default={}): cv.All( { diff --git a/esphome/components/xl9535/xl9535.cpp b/esphome/components/xl9535/xl9535.cpp index d189d9b5c71..228274b3211 100644 --- a/esphome/components/xl9535/xl9535.cpp +++ b/esphome/components/xl9535/xl9535.cpp @@ -3,7 +3,7 @@ namespace esphome::xl9535 { -static const char *const TAG = "xl9535"; +ESPHOME_LOG_TAG(TAG, "xl9535"); void XL9535Component::setup() { // Check to see if the device can read from the register diff --git a/esphome/components/xpt2046/__init__.py b/esphome/components/xpt2046/__init__.py index 3b8a925bb23..a2b1372b626 100644 --- a/esphome/components/xpt2046/__init__.py +++ b/esphome/components/xpt2046/__init__.py @@ -1,5 +1,7 @@ import esphome.config_validation as cv +DOMAIN = "xpt2046" + CONFIG_SCHEMA = cv.invalid( "This component sould now be used as platform of the Touchscreen component." ) diff --git a/esphome/components/xpt2046/touchscreen/xpt2046.cpp b/esphome/components/xpt2046/touchscreen/xpt2046.cpp index 83a73320054..afb8f6cd5c8 100644 --- a/esphome/components/xpt2046/touchscreen/xpt2046.cpp +++ b/esphome/components/xpt2046/touchscreen/xpt2046.cpp @@ -13,7 +13,7 @@ static constexpr uint8_t XPT_READ_Y = 0x90; static constexpr uint8_t XPT_ADC_ON = 0x01; static constexpr uint8_t XPT_VREF_ON = 0x02; -static const char *const TAG = "xpt2046"; +ESPHOME_LOG_TAG(TAG, "xpt2046"); void XPT2046Component::setup() { if (this->irq_pin_ != nullptr) { diff --git a/esphome/components/xxtea/__init__.py b/esphome/components/xxtea/__init__.py index 7a7bce1781a..79d31dc44a1 100644 --- a/esphome/components/xxtea/__init__.py +++ b/esphome/components/xxtea/__init__.py @@ -1,3 +1,4 @@ """ESPHome XXTEA encryption component.""" CODEOWNERS = ["@clydebarrow"] +DOMAIN = "xxtea" diff --git a/esphome/components/yashima/__init__.py b/esphome/components/yashima/__init__.py index e69de29bb2d..587acabe787 100644 --- a/esphome/components/yashima/__init__.py +++ b/esphome/components/yashima/__init__.py @@ -0,0 +1 @@ +DOMAIN = "yashima" diff --git a/esphome/components/yashima/yashima.cpp b/esphome/components/yashima/yashima.cpp index ba0a3a64040..986237b2d69 100644 --- a/esphome/components/yashima/yashima.cpp +++ b/esphome/components/yashima/yashima.cpp @@ -3,7 +3,7 @@ namespace esphome::yashima { -static const char *const TAG = "yashima.climate"; +ESPHOME_LOG_TAG(TAG, "yashima.climate"); const uint16_t YASHIMA_STATE_LENGTH = 9; const uint16_t YASHIMA_BITS = YASHIMA_STATE_LENGTH * 8; diff --git a/esphome/components/zephyr/__init__.py b/esphome/components/zephyr/__init__.py index 49654736cc2..4f69f00339e 100644 --- a/esphome/components/zephyr/__init__.py +++ b/esphome/components/zephyr/__init__.py @@ -26,6 +26,7 @@ from .const import ( ) CODEOWNERS = ["@tomaszduda23"] +DOMAIN = "zephyr" class HexValue: diff --git a/esphome/components/zephyr/gpio.cpp b/esphome/components/zephyr/gpio.cpp index 23da2cafac7..57a31e302e5 100644 --- a/esphome/components/zephyr/gpio.cpp +++ b/esphome/components/zephyr/gpio.cpp @@ -1,5 +1,6 @@ #ifdef USE_ZEPHYR #include "gpio.h" +#include "esphome/core/hal.h" #include #include #include "esphome/core/log.h" @@ -7,7 +8,7 @@ namespace esphome { namespace zephyr { -static const char *const TAG = "zephyr"; +ESPHOME_LOG_TAG(TAG, "zephyr"); static gpio_flags_t flags_to_mode(gpio::Flags flags, bool inverted, bool value) { gpio_flags_t ret = 0; @@ -79,7 +80,7 @@ ISRInternalGPIOPin ZephyrGPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } -void ZephyrGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { +void ZephyrGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { if (!device_is_ready(this->gpio_)) { ESP_LOGE(TAG, "Cannot attach interrupt: GPIO device not ready"); return; diff --git a/esphome/components/zephyr/gpio.h b/esphome/components/zephyr/gpio.h index 71d1620a677..dd26e34eb8b 100644 --- a/esphome/components/zephyr/gpio.h +++ b/esphome/components/zephyr/gpio.h @@ -1,7 +1,7 @@ #pragma once #ifdef USE_ZEPHYR -#include "esphome/core/hal.h" +#include "esphome/core/gpio_pin.h" #include #include namespace esphome::zephyr { @@ -16,7 +16,7 @@ struct ZephyrGPIOInterrupt { void *arg{nullptr}; }; -class ZephyrGPIOPin final : public InternalGPIOPin { +class ZephyrGPIOPin final : public GPIOPin { public: ZephyrGPIOPin(const device *gpio, int gpio_size, const char *pin_name_prefix) { this->gpio_ = gpio; @@ -32,14 +32,18 @@ class ZephyrGPIOPin final : public InternalGPIOPin { bool digital_read() override; void digital_write(bool value) override; size_t dump_summary(char *buffer, size_t len) const override; - void detach_interrupt() const override; - ISRInternalGPIOPin to_isr() const override; - uint8_t get_pin() const override { return this->pin_; } - bool is_inverted() const override { return this->inverted_; } + void detach_interrupt() const; + template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { + this->attach_interrupt_(reinterpret_cast(func), arg, type); + } + ISRInternalGPIOPin to_isr() const; + uint8_t get_pin() const { return this->pin_; } + bool is_inverted() const { return this->inverted_; } + bool is_internal() override { return true; } gpio::Flags get_flags() const override { return flags_; } protected: - void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const; const device *gpio_{nullptr}; const char *pin_name_prefix_{nullptr}; gpio::Flags flags_{}; @@ -48,8 +52,7 @@ class ZephyrGPIOPin final : public InternalGPIOPin { bool inverted_{}; bool value_{false}; - // attach_interrupt()/detach_interrupt() are const (matching the base class), so - // the interrupt state they manage has to be mutable. + // attach_interrupt_()/detach_interrupt() are const, so their interrupt state is mutable. mutable ZephyrGPIOInterrupt interrupt_{}; }; diff --git a/esphome/components/zephyr/hal.cpp b/esphome/components/zephyr/hal.cpp index 10e8340a409..96c7df896cb 100644 --- a/esphome/components/zephyr/hal.cpp +++ b/esphome/components/zephyr/hal.cpp @@ -23,6 +23,7 @@ static const device *const WDT = DEVICE_DT_GET(DT_ALIAS(watchdog0)); // components/zephyr/hal.h. void arch_init() { + main_loop_thread() = k_current_get(); #ifdef CONFIG_WATCHDOG if (device_is_ready(WDT)) { static wdt_timeout_cfg wdt_config{}; diff --git a/esphome/components/zephyr/hal.h b/esphome/components/zephyr/hal.h index 11994b68b7b..8384225d33a 100644 --- a/esphome/components/zephyr/hal.h +++ b/esphome/components/zephyr/hal.h @@ -17,6 +17,14 @@ namespace esphome { /// Zephyr/nRF52: not currently consulted — wake path is platform-specific. __attribute__((always_inline)) inline bool in_isr_context() { return false; } +inline k_tid_t &main_loop_thread() { + static k_tid_t thread = nullptr; + return thread; +} + +/// arch_init() stores the thread before any component runs, so no unset case exists. +__attribute__((always_inline)) inline bool is_main_loop_thread() { return k_current_get() == main_loop_thread(); } + __attribute__((always_inline)) inline void yield() { ::k_yield(); } __attribute__((always_inline)) inline void delay(uint32_t ms) { ::k_msleep(ms); } __attribute__((always_inline)) inline uint32_t micros() { return k_ticks_to_us_floor32(k_uptime_ticks()); } diff --git a/esphome/components/zephyr/preferences.cpp b/esphome/components/zephyr/preferences.cpp index ed22613625f..6be5f1ce4a5 100644 --- a/esphome/components/zephyr/preferences.cpp +++ b/esphome/components/zephyr/preferences.cpp @@ -10,7 +10,7 @@ namespace esphome::zephyr { -static const char *const TAG = "preferences"; +ESPHOME_LOG_TAG(TAG, "preferences"); bool ZephyrPreferenceBackend::save(const uint8_t *data, size_t len) { this->data.resize(len); diff --git a/esphome/components/zephyr/reset_reason.cpp b/esphome/components/zephyr/reset_reason.cpp index 24b32196db5..f3f126537df 100644 --- a/esphome/components/zephyr/reset_reason.cpp +++ b/esphome/components/zephyr/reset_reason.cpp @@ -7,7 +7,7 @@ namespace esphome::zephyr { -static const char *const TAG = "zephyr"; +ESPHOME_LOG_TAG(TAG, "zephyr"); static size_t append_reset_reason(char *buf, size_t size, size_t pos, bool set, const char *reason) { if (!set) { diff --git a/esphome/components/zephyr_ble_server/ble_server.cpp b/esphome/components/zephyr_ble_server/ble_server.cpp index 15993abcce4..dd6ecd3b20d 100644 --- a/esphome/components/zephyr_ble_server/ble_server.cpp +++ b/esphome/components/zephyr_ble_server/ble_server.cpp @@ -7,7 +7,7 @@ namespace esphome::zephyr_ble_server { -static const char *const TAG = "zephyr_ble_server"; +ESPHOME_LOG_TAG(TAG, "zephyr_ble_server"); static k_work advertise_work; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/zephyr_mcumgr/__init__.py b/esphome/components/zephyr_mcumgr/__init__.py index e69de29bb2d..945cc5807dc 100644 --- a/esphome/components/zephyr_mcumgr/__init__.py +++ b/esphome/components/zephyr_mcumgr/__init__.py @@ -0,0 +1 @@ +DOMAIN = "zephyr_mcumgr" diff --git a/esphome/components/zephyr_mcumgr/ota/__init__.py b/esphome/components/zephyr_mcumgr/ota/__init__.py index f5ab04809e4..ed7069a5403 100644 --- a/esphome/components/zephyr_mcumgr/ota/__init__.py +++ b/esphome/components/zephyr_mcumgr/ota/__init__.py @@ -13,6 +13,7 @@ from esphome.components.zephyr import ( from esphome.components.zephyr.const import ( BOOTLOADER_MCUBOOT, KEY_BOOTLOADER, + KEY_PM_STATIC, KEY_SYSBUILD, ) from esphome.components.zephyr_ble_server import request_ble_l2cap_mtu @@ -146,14 +147,15 @@ async def to_code(config: ConfigType) -> None: zephyr_add_prj_conf("MCUMGR_MGMT_NOTIFICATION_HOOKS", True) zephyr_add_prj_conf("MCUMGR_GRP_IMG_STATUS_HOOKS", True) zephyr_add_prj_conf("MCUMGR_GRP_IMG_UPLOAD_CHECK_HOOK", True) + # OS management group (reset, MCUMGR_PARAMETERS query, etc.) isn't transport-specific -- + # every mcumgr transport needs it, not just BLE. + zephyr_add_prj_conf("MCUMGR_GRP_OS", True) + zephyr_add_prj_conf("MCUMGR_GRP_OS_MCUMGR_PARAMS", True) transport = config[CONF_TRANSPORT] if transport[CONF_BLE]: zephyr_add_prj_conf("MCUMGR_TRANSPORT_BT", True) zephyr_add_prj_conf("MCUMGR_TRANSPORT_BT_REASSEMBLY", True) - zephyr_add_prj_conf("MCUMGR_GRP_OS", True) - zephyr_add_prj_conf("MCUMGR_GRP_OS_MCUMGR_PARAMS", True) - zephyr_add_prj_conf("NCS_SAMPLE_MCUMGR_BT_OTA_DFU_SPEEDUP", True) request_ble_l2cap_mtu(498) # matches NCS_SAMPLE_MCUMGR_BT_OTA_DFU_SPEEDUP if CONF_HARDWARE_UART in transport: @@ -207,10 +209,11 @@ async def to_code(config: ConfigType) -> None: bootloader = zephyr_data()[KEY_BOOTLOADER] if bootloader != BOOTLOADER_MCUBOOT: - sections = BOOTLOADER_CONFIG[bootloader] - # Derive partition addresses from the SoftDevice and bootloader sections so - # that the DTS flash map matches what the Partition Manager produces: + # Derive partition addresses from the SoftDevice and bootloader sections + # registered with the Partition Manager (moved in testing mode) so that + # the DTS flash map matches what it produces: # MCUboot sits immediately after the SoftDevice, then slot0, then slot1. + sections = zephyr_data()[KEY_PM_STATIC] mcuboot_size = 0x9000 sd_end = next(s.address + s.size for s in sections if "SoftDevice" in s.name) bl_start = next(s.address for s in sections if "Adafruit" in s.name) diff --git a/esphome/components/zephyr_mcumgr/ota/ota_zephyr_mcumgr.cpp b/esphome/components/zephyr_mcumgr/ota/ota_zephyr_mcumgr.cpp index f1eac462bc3..af126a29c7a 100644 --- a/esphome/components/zephyr_mcumgr/ota/ota_zephyr_mcumgr.cpp +++ b/esphome/components/zephyr_mcumgr/ota/ota_zephyr_mcumgr.cpp @@ -27,7 +27,7 @@ static_assert(sizeof(struct img_mgmt_upload_req) == 8, "ABI mismatch"); static_assert(offsetof(struct img_mgmt_upload_req, image) == 0, "ABI mismatch"); static_assert(offsetof(struct img_mgmt_upload_req, off) == 4, "ABI mismatch"); -static const char *const TAG = "zephyr_mcumgr"; +ESPHOME_LOG_TAG(TAG, "zephyr_mcumgr"); static OTAComponent *global_ota_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static enum mgmt_cb_return mcumgr_img_mgmt_cb(uint32_t event, enum mgmt_cb_return prev_status, int32_t *rc, diff --git a/esphome/components/zephyr_pwm/__init__.py b/esphome/components/zephyr_pwm/__init__.py index 4bcce84845a..4d3382d6171 100644 --- a/esphome/components/zephyr_pwm/__init__.py +++ b/esphome/components/zephyr_pwm/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@wiomoc"] +DOMAIN = "zephyr_pwm" diff --git a/esphome/components/zephyr_pwm/output.py b/esphome/components/zephyr_pwm/output.py index 2cb62b85b5e..604cffb0e03 100644 --- a/esphome/components/zephyr_pwm/output.py +++ b/esphome/components/zephyr_pwm/output.py @@ -4,6 +4,7 @@ from typing import Any from esphome import pins import esphome.codegen as cg from esphome.components import output +from esphome.components.output import DOMAIN as OUTPUT_DOMAIN from esphome.components.zephyr import zephyr_add_overlay_builder, zephyr_add_prj_conf import esphome.config_validation as cv from esphome.const import ( @@ -12,7 +13,6 @@ from esphome.const import ( CONF_ID, CONF_INVERTED, CONF_NUMBER, - CONF_OUTPUT, CONF_PIN, CONF_PLATFORM, ) @@ -74,7 +74,7 @@ def _allocate_blocks() -> None: full_config = fv.full_config.get() zephyr_pwm_conf = [ cfg - for cfg in full_config.get(CONF_OUTPUT, []) + for cfg in full_config.get(OUTPUT_DOMAIN, []) if cfg.get(CONF_PLATFORM) == DOMAIN ] diff --git a/esphome/components/zephyr_pwm/zephyr_pwm.cpp b/esphome/components/zephyr_pwm/zephyr_pwm.cpp index aa1393388ab..71d54e8e48c 100644 --- a/esphome/components/zephyr_pwm/zephyr_pwm.cpp +++ b/esphome/components/zephyr_pwm/zephyr_pwm.cpp @@ -6,7 +6,7 @@ namespace esphome::zephyr_pwm { -static const char *const TAG = "zephyr_pwm"; +ESPHOME_LOG_TAG(TAG, "zephyr_pwm"); void ZephyrPWMChannel::setup() { if (!device_is_ready(this->device_)) { diff --git a/esphome/components/zhlt01/__init__.py b/esphome/components/zhlt01/__init__.py index e69de29bb2d..c17009d722b 100644 --- a/esphome/components/zhlt01/__init__.py +++ b/esphome/components/zhlt01/__init__.py @@ -0,0 +1 @@ +DOMAIN = "zhlt01" diff --git a/esphome/components/zhlt01/zhlt01.cpp b/esphome/components/zhlt01/zhlt01.cpp index 2585ac1b572..afca7bf8603 100644 --- a/esphome/components/zhlt01/zhlt01.cpp +++ b/esphome/components/zhlt01/zhlt01.cpp @@ -3,7 +3,7 @@ namespace esphome::zhlt01 { -static const char *const TAG = "zhlt01.climate"; +ESPHOME_LOG_TAG(TAG, "zhlt01.climate"); void ZHLT01Climate::transmit_state() { uint8_t ir_message[12] = {0}; diff --git a/esphome/components/zigbee/__init__.py b/esphome/components/zigbee/__init__.py index 02f05ef8a2e..0dc3b4b6f71 100644 --- a/esphome/components/zigbee/__init__.py +++ b/esphome/components/zigbee/__init__.py @@ -3,7 +3,7 @@ from typing import Any from esphome import automation import esphome.codegen as cg -from esphome.components.esp32 import only_on_variant +from esphome.components.esp32 import DOMAIN as ESP32_DOMAIN, only_on_variant from esphome.components.esp32.const import ( VARIANT_ESP32C5, VARIANT_ESP32C6, @@ -42,6 +42,7 @@ from .const_zephyr import ( from .zigbee_esp32 import ( final_validate_esp32, validate_binary_sensor_esp32, + validate_number_esp32, validate_sensor_esp32, validate_switch_esp32, zigbee_require_vfs_select, @@ -56,6 +57,7 @@ from .zigbee_zephyr import ( _LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@luar123", "@tomaszduda23"] +DOMAIN = "zigbee" CONFLICTS_WITH = ["openthread"] @@ -72,19 +74,19 @@ def _get_base_schema(cluster_options: list[str] | None = None) -> cv.Schema: schema = cv.Schema( { cv.Optional(CONF_REPORT): cv.All( - cv.requires_component("zigbee"), - cv.requires_component("esp32"), + cv.requires_component(DOMAIN), + cv.requires_component(ESP32_DOMAIN), _check_report_deprecation, cv.enum(REPORT, lower=True), ), cv.Optional(CONF_ENDPOINT): cv.All( - cv.requires_component("zigbee"), - cv.requires_component("esp32"), + cv.requires_component(DOMAIN), + cv.requires_component(ESP32_DOMAIN), cv.int_range(1, CONF_MAX_EP_NUMBER), ), cv.Optional(CONF_USE_DEVICE_TYPE): cv.All( - cv.requires_component("zigbee"), - cv.requires_component("esp32"), + cv.requires_component(DOMAIN), + cv.requires_component(ESP32_DOMAIN), cv.boolean, ), } @@ -95,8 +97,8 @@ def _get_base_schema(cluster_options: list[str] | None = None) -> cv.Schema: cv.OnlyWith( CONF_CLUSTER, ["esp32", "zigbee"], default=cluster_options[0] ): cv.All( - cv.requires_component("zigbee"), - cv.requires_component("esp32"), + cv.requires_component(DOMAIN), + cv.requires_component(ESP32_DOMAIN), cv.one_of(*cluster_options, lower=True), ), } @@ -120,7 +122,9 @@ SENSOR_SCHEMA = ( SWITCH_SCHEMA = ( cv.Schema({}).extend(_get_base_schema(["generic", "on_off"])).extend(zephyr_switch) ) -NUMBER_SCHEMA = cv.Schema({}).extend(zephyr_number) +NUMBER_SCHEMA = ( + cv.Schema({}).extend(_get_base_schema(["generic"])).extend(zephyr_number) +) def _validate_router_sleepy(config: ConfigType) -> ConfigType: @@ -227,7 +231,7 @@ async def to_code(config: ConfigType) -> None: async def setup_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None: - if "zigbee" not in CORE.loaded_integrations or config.get(CONF_INTERNAL): + if DOMAIN not in CORE.loaded_integrations or config.get(CONF_INTERNAL): return if CORE.using_zephyr: if not config.get(CONF_ZIGBEE_ID): @@ -239,7 +243,7 @@ async def setup_binary_sensor(entity: cg.MockObj, config: ConfigType) -> None: async def setup_sensor(entity: cg.MockObj, config: ConfigType) -> None: - if "zigbee" not in CORE.loaded_integrations or config.get(CONF_INTERNAL): + if DOMAIN not in CORE.loaded_integrations or config.get(CONF_INTERNAL): return if CORE.using_zephyr: if not config.get(CONF_ZIGBEE_ID): @@ -251,7 +255,7 @@ async def setup_sensor(entity: cg.MockObj, config: ConfigType) -> None: async def setup_switch(entity: cg.MockObj, config: ConfigType) -> None: - if "zigbee" not in CORE.loaded_integrations or config.get(CONF_INTERNAL): + if DOMAIN not in CORE.loaded_integrations or config.get(CONF_INTERNAL): return if CORE.using_zephyr: if not config.get(CONF_ZIGBEE_ID): @@ -269,12 +273,18 @@ async def setup_number( max_value: float, step: float, ) -> None: - if not config.get(CONF_ZIGBEE_ID) or config.get(CONF_INTERNAL): + if DOMAIN not in CORE.loaded_integrations or config.get(CONF_INTERNAL): return if CORE.using_zephyr: + if not config.get(CONF_ZIGBEE_ID): + return from .zigbee_zephyr import zephyr_setup_number await zephyr_setup_number(entity, config, min_value, max_value, step) + else: + from .zigbee_esp32 import add_component as add_number + + CORE.add_job(add_number, entity, config) def consume_endpoint(config: ConfigType) -> ConfigType: @@ -293,7 +303,7 @@ def consume_endpoint(config: ConfigType) -> ConfigType: def validate_binary_sensor(config: ConfigType) -> ConfigType: - if "zigbee" not in CORE.loaded_integrations or config.get(CONF_INTERNAL): + if DOMAIN not in CORE.loaded_integrations or config.get(CONF_INTERNAL): return config if CORE.is_esp32: return validate_binary_sensor_esp32(config) @@ -301,7 +311,7 @@ def validate_binary_sensor(config: ConfigType) -> ConfigType: def validate_sensor(config: ConfigType) -> ConfigType: - if "zigbee" not in CORE.loaded_integrations or config.get(CONF_INTERNAL): + if DOMAIN not in CORE.loaded_integrations or config.get(CONF_INTERNAL): return config if CORE.is_esp32: return validate_sensor_esp32(config) @@ -309,7 +319,7 @@ def validate_sensor(config: ConfigType) -> ConfigType: def validate_switch(config: ConfigType) -> ConfigType: - if "zigbee" not in CORE.loaded_integrations or config.get(CONF_INTERNAL): + if DOMAIN not in CORE.loaded_integrations or config.get(CONF_INTERNAL): return config if CORE.is_esp32: return validate_switch_esp32(config) @@ -317,10 +327,10 @@ def validate_switch(config: ConfigType) -> ConfigType: def validate_number(config: ConfigType) -> ConfigType: - if "zigbee" not in CORE.loaded_integrations or config.get(CONF_INTERNAL): + if DOMAIN not in CORE.loaded_integrations or config.get(CONF_INTERNAL): return config if CORE.is_esp32: - return config + return validate_number_esp32(config) return consume_endpoint(config) diff --git a/esphome/components/zigbee/const.py b/esphome/components/zigbee/const.py index d6abd9d6c07..86cac665b43 100644 --- a/esphome/components/zigbee/const.py +++ b/esphome/components/zigbee/const.py @@ -145,7 +145,8 @@ class AnalogInputType(IntEnum): TIME_SECONDS = 0x0E -ANALOG_INPUT_APPTYPE = { +# Same for input and output +ANALOG_APPTYPE = { (DEVICE_CLASS_TEMPERATURE, UNIT_CELSIUS): AnalogInputType.TEMP_DEGREES_C, (DEVICE_CLASS_HUMIDITY, UNIT_PERCENT): AnalogInputType.RELATIVE_HUMIDITY_PERCENT, (DEVICE_CLASS_PRESSURE, UNIT_PASCAL): AnalogInputType.PRESSURE_PASCAL, diff --git a/esphome/components/zigbee/const_esp32.py b/esphome/components/zigbee/const_esp32.py index b8122d14417..bb6c153752b 100644 --- a/esphome/components/zigbee/const_esp32.py +++ b/esphome/components/zigbee/const_esp32.py @@ -33,6 +33,7 @@ CLUSTER_ID = { "BINARY_INPUT": cluster_id.EZB_ZCL_CLUSTER_ID_BINARY_INPUT, "BINARY_OUTPUT": cluster_id.EZB_ZCL_CLUSTER_ID_BINARY_OUTPUT, "ANALOG_INPUT": cluster_id.EZB_ZCL_CLUSTER_ID_ANALOG_INPUT, + "ANALOG_OUTPUT": cluster_id.EZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT, "ILLUMINANCE_MEASUREMENT": cluster_id.EZB_ZCL_CLUSTER_ID_ILLUMINANCE_MEASUREMENT, "TEMPERATURE_MEASUREMENT": cluster_id.EZB_ZCL_CLUSTER_ID_TEMPERATURE_MEASUREMENT, "PRESSURE_MEASUREMENT": cluster_id.EZB_ZCL_CLUSTER_ID_PRESSURE_MEASUREMENT, diff --git a/esphome/components/zigbee/time/zigbee_time_esp32.cpp b/esphome/components/zigbee/time/zigbee_time_esp32.cpp index 5567295782f..909c714c5e3 100644 --- a/esphome/components/zigbee/time/zigbee_time_esp32.cpp +++ b/esphome/components/zigbee/time/zigbee_time_esp32.cpp @@ -1,11 +1,13 @@ #include "zigbee_time_esp32.h" #if defined(USE_ZIGBEE) && defined(USE_ESP32) && defined(USE_TIME) #include "esphome/core/log.h" -#include "esphome/core/application.h" namespace esphome::zigbee { -static const char *const TAG = "zigbee.time"; +ESPHOME_LOG_TAG(TAG, "zigbee.time"); + +static constexpr uint32_t REGISTER_RETRY_TIMEOUT_ID = 0; +static constexpr uint32_t SYNC_RETRY_TIMEOUT_ID = 1; // This time standard is the number of // seconds since 0 hrs 0 mins 0 sec on 1st January 2000 UTC (Universal Coordinated Time). @@ -29,7 +31,7 @@ void ZigbeeTime::register_zb_time_() { }; ezb_err_t ret; if (!esp_zigbee_lock_acquire(10 / portTICK_PERIOD_MS)) { - this->set_timeout("zb_time_register", 100, [this]() { this->register_zb_time_(); }); + this->set_timeout(REGISTER_RETRY_TIMEOUT_ID, 100, [this]() { this->register_zb_time_(); }); return; } ret = ezb_zcl_time_server_interface_register(this->endpoint_, time_interface); @@ -69,7 +71,7 @@ void ZigbeeTime::update() { ESP_LOGW(TAG, "Could not acquire Zigbee lock to synchronize time, will retry maximum 3 times"); } if (this->retry_count_ < 3) { - this->set_timeout("zb_time_sync", 100, [this]() { this->update(); }); + this->set_timeout(SYNC_RETRY_TIMEOUT_ID, 100, [this]() { this->update(); }); this->retry_count_++; } else { ESP_LOGW(TAG, "Could not acquire Zigbee lock to synchronize time"); @@ -102,7 +104,6 @@ void ZigbeeTime::set_epoch_time(uint32_t utc) { ESP_LOGV(TAG, "Setting device time to UTC: %u", static_cast(utc)); this->synchronize_epoch_(utc); }); - App.wake_loop_threadsafe(); } void ZigbeeTime::dump_config() { diff --git a/esphome/components/zigbee/time/zigbee_time_zephyr.cpp b/esphome/components/zigbee/time/zigbee_time_zephyr.cpp index 3f14d0a62d2..7f0a5f979ce 100644 --- a/esphome/components/zigbee/time/zigbee_time_zephyr.cpp +++ b/esphome/components/zigbee/time/zigbee_time_zephyr.cpp @@ -1,11 +1,10 @@ #include "zigbee_time_zephyr.h" #if defined(USE_ZIGBEE) && defined(USE_NRF52) && defined(USE_TIME) #include "esphome/core/log.h" -#include "esphome/core/application.h" namespace esphome::zigbee { -static const char *const TAG = "zigbee.time"; +ESPHOME_LOG_TAG(TAG, "zigbee.time"); // This time standard is the number of // seconds since 0 hrs 0 mins 0 sec on 1st January 2000 UTC (Universal Coordinated Time). @@ -48,7 +47,6 @@ void ZigbeeTime::set_epoch_time(uint32_t epoch) { this->synchronize_epoch_(epoch); this->has_time_ = true; }); - App.wake_loop_threadsafe(); } void ZigbeeTime::zcl_device_cb_(zb_bufid_t bufid) { diff --git a/esphome/components/zigbee/zigbee_attribute_esp32.cpp b/esphome/components/zigbee/zigbee_attribute_esp32.cpp index acf0321b12b..b73c26a8361 100644 --- a/esphome/components/zigbee/zigbee_attribute_esp32.cpp +++ b/esphome/components/zigbee/zigbee_attribute_esp32.cpp @@ -6,7 +6,7 @@ namespace esphome::zigbee { -static const char *const TAG = "zigbee.attribute"; +ESPHOME_LOG_TAG(TAG, "zigbee.attribute"); void ZigbeeAttribute::set_attr_() { if (!this->zb_->is_started()) { diff --git a/esphome/components/zigbee/zigbee_attribute_esp32.h b/esphome/components/zigbee/zigbee_attribute_esp32.h index 2680fc2203e..305febf171a 100644 --- a/esphome/components/zigbee/zigbee_attribute_esp32.h +++ b/esphome/components/zigbee/zigbee_attribute_esp32.h @@ -23,6 +23,9 @@ #ifdef USE_SWITCH #include "esphome/components/switch/switch.h" #endif +#ifdef USE_NUMBER +#include "esphome/components/number/number.h" +#endif namespace esphome::zigbee { @@ -67,6 +70,9 @@ class ZigbeeAttribute final : public Component { #endif #ifdef USE_SWITCH template void connect(switch_::Switch *device); +#endif +#ifdef USE_NUMBER + template void connect(number::Number *device); #endif bool report_enabled = false; @@ -157,6 +163,25 @@ template void ZigbeeAttribute::connect(switch_::Switch *device) { device->add_on_state_callback([this](bool value) { this->set_attr((T) (this->scale_ * value)); }); } #endif +#ifdef USE_NUMBER +template void ZigbeeAttribute::connect(number::Number *device) { + // Add min, max and step attributes to the analog output cluster + if (this->cluster_id_ == EZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT) { + this->zb_->add_attr(this->endpoint_id_, this->cluster_id_, this->role_, 0x0045, this->max_size_, + device->traits.get_min_value()); + this->zb_->add_attr(this->endpoint_id_, this->cluster_id_, this->role_, 0x0041, this->max_size_, + device->traits.get_max_value()); + this->zb_->add_attr(this->endpoint_id_, this->cluster_id_, this->role_, 0x006A, this->max_size_, + device->traits.get_step()); + } + this->add_on_value_callback([this, device](ezb_zcl_attribute_t attribute) { + auto call = device->make_call(); + call.set_value(static_cast(*(T *) attribute.data.value) / this->scale_); + call.perform(); + }); + device->add_on_state_callback([this](float value) { this->set_attr(this->scale_value_(value)); }); +} +#endif } // namespace esphome::zigbee diff --git a/esphome/components/zigbee/zigbee_binary_sensor_zephyr.cpp b/esphome/components/zigbee/zigbee_binary_sensor_zephyr.cpp index 464cc04d620..911159d091d 100644 --- a/esphome/components/zigbee/zigbee_binary_sensor_zephyr.cpp +++ b/esphome/components/zigbee/zigbee_binary_sensor_zephyr.cpp @@ -10,7 +10,7 @@ extern "C" { } namespace esphome::zigbee { -static const char *const TAG = "zigbee.binary_sensor"; +ESPHOME_LOG_TAG(TAG, "zigbee.binary_sensor"); ZigbeeBinarySensor::ZigbeeBinarySensor(binary_sensor::BinarySensor *binary_sensor) : binary_sensor_(binary_sensor) {} diff --git a/esphome/components/zigbee/zigbee_ep_esp32.py b/esphome/components/zigbee/zigbee_ep_esp32.py index b0f3d17bd69..88f49ef3c16 100644 --- a/esphome/components/zigbee/zigbee_ep_esp32.py +++ b/esphome/components/zigbee/zigbee_ep_esp32.py @@ -78,6 +78,35 @@ ANALOG_INPUT_EP = { ], } +ANALOG_OUTPUT_EP = { + CONF_CLUSTERS: [ + { + CONF_ID: "ANALOG_OUTPUT", + ROLE: "SERVER", + CONF_ATTRIBUTES: [ + { + CONF_ATTRIBUTE_ID: 0x55, + CONF_TYPE: "SINGLE", + CONF_REPORT: cv.enum(REPORT, lower=True)("default"), + CONNECT: True, + }, + { + CONF_ATTRIBUTE_ID: 0x51, + CONF_TYPE: "BOOL", + }, + { + CONF_ATTRIBUTE_ID: 0x6F, + CONF_TYPE: "MAP8", + }, + { + CONF_ATTRIBUTE_ID: 0x1C, + CONF_TYPE: "STRING", + }, + ], + }, + ], +} + BINARY_INPUT_EP = { DEVICE_TYPE: "SIMPLE_SENSOR", CONF_CLUSTERS: [ diff --git a/esphome/components/zigbee/zigbee_esp32.cpp b/esphome/components/zigbee/zigbee_esp32.cpp index c6597ebfd69..89417e748d2 100644 --- a/esphome/components/zigbee/zigbee_esp32.cpp +++ b/esphome/components/zigbee/zigbee_esp32.cpp @@ -17,7 +17,9 @@ namespace esphome::zigbee { -static const char *const TAG = "zigbee"; +ESPHOME_LOG_TAG(TAG, "zigbee"); + +static constexpr uint32_t COMMISSIONING_RETRY_TIMEOUT_ID = 0; static ZigbeeComponent *global_zigbee = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -49,8 +51,8 @@ void ZigbeeComponent::factory_reset() { void ZigbeeComponent::esp_zigbee_alarm_bdb_commissioning(ezb_bdb_comm_mode_mask_t mode) { if (!esp_zigbee_lock_acquire(10 / portTICK_PERIOD_MS)) { - global_zigbee->set_timeout("zb_init", 100, [mode]() { ZigbeeComponent::esp_zigbee_alarm_bdb_commissioning(mode); }); - App.wake_loop_threadsafe(); + global_zigbee->set_timeout(COMMISSIONING_RETRY_TIMEOUT_ID, 100, + [mode]() { ZigbeeComponent::esp_zigbee_alarm_bdb_commissioning(mode); }); return; } if (ezb_bdb_start_top_level_commissioning(mode) != EZB_ERR_NONE) { @@ -86,10 +88,9 @@ bool ZigbeeComponent::app_signal_handler(const ezb_app_signal_t *app_signal) { } } else { ESP_LOGW(TAG, "The %s failed with status(0x%02x), please retry", ezb_app_signal_to_string(signal_type), status); - global_zigbee->set_timeout("zb_init", 1000, []() { + global_zigbee->set_timeout(COMMISSIONING_RETRY_TIMEOUT_ID, 1000, []() { ZigbeeComponent::esp_zigbee_alarm_bdb_commissioning(EZB_BDB_MODE_INITIALIZATION); }); - App.wake_loop_threadsafe(); } } break; case EZB_BDB_SIGNAL_STEERING: { @@ -107,15 +108,14 @@ bool ZigbeeComponent::app_signal_handler(const ezb_app_signal_t *app_signal) { ESP_LOGD(TAG, "Failed to join network with status(0x%02x)", status); if (steering_retry_count < 10) { steering_retry_count++; - global_zigbee->set_timeout("zb_init", 1000, []() { + global_zigbee->set_timeout(COMMISSIONING_RETRY_TIMEOUT_ID, 1000, []() { ZigbeeComponent::esp_zigbee_alarm_bdb_commissioning(EZB_BDB_MODE_NETWORK_STEERING); }); } else { - global_zigbee->set_timeout("zb_init", 600 * 1000, []() { + global_zigbee->set_timeout(COMMISSIONING_RETRY_TIMEOUT_ID, 600 * 1000, []() { ZigbeeComponent::esp_zigbee_alarm_bdb_commissioning(EZB_BDB_MODE_NETWORK_STEERING); }); } - App.wake_loop_threadsafe(); } } break; case EZB_ZDO_SIGNAL_LEAVE: { diff --git a/esphome/components/zigbee/zigbee_esp32.py b/esphome/components/zigbee/zigbee_esp32.py index f48bf9ef0be..103e901b13b 100644 --- a/esphome/components/zigbee/zigbee_esp32.py +++ b/esphome/components/zigbee/zigbee_esp32.py @@ -10,6 +10,7 @@ from esphome.components.esp32 import ( add_idf_sdkconfig_option, add_partition, include_builtin_idf_component, + require_mbedtls_ecp, require_mbedtls_tls_extras, require_vfs_select, ) @@ -36,7 +37,7 @@ import esphome.final_validate as fv from esphome.types import ConfigType from .const import ( - ANALOG_INPUT_APPTYPE, + ANALOG_APPTYPE, BACNET_UNIT_NO_UNITS, BACNET_UNITS, CONF_CLUSTER, @@ -67,6 +68,7 @@ from .const_esp32 import ( ) from .zigbee_ep_esp32 import ( ANALOG_INPUT_EP, + ANALOG_OUTPUT_EP, BINARY_INPUT_EP, BINARY_OUTPUT_EP, BINARY_SENSOR_EP_CONFIGS, @@ -229,7 +231,7 @@ def validate_sensor_esp32(config: ConfigType) -> ConfigType: attr[CONF_LAMBDA] = attr[CONF_LAMBDA][unit] else: ep = copy.deepcopy(ANALOG_INPUT_EP) - apptype = ANALOG_INPUT_APPTYPE.get((dev_class, unit)) + apptype = ANALOG_APPTYPE.get((dev_class, unit)) bacunit = BACNET_UNITS.get(unit, BACNET_UNIT_NO_UNITS) accuracy = config.get(CONF_ACCURACY_DECIMALS) if apptype is not None: @@ -261,6 +263,34 @@ def validate_sensor_esp32(config: ConfigType) -> ConfigType: return config +def validate_number_esp32(config: ConfigType) -> ConfigType: + # get application type from device class and meas unit + # if none get BACNET unit from meas unit + dev_class = config.get(CONF_DEVICE_CLASS) + unit = config.get(CONF_UNIT_OF_MEASUREMENT) + ep = copy.deepcopy(ANALOG_OUTPUT_EP) + apptype = ANALOG_APPTYPE.get((dev_class, unit)) + bacunit = BACNET_UNITS.get(unit, BACNET_UNIT_NO_UNITS) + if apptype is not None: + ep[CONF_CLUSTERS][0][CONF_ATTRIBUTES].append( + { + CONF_ATTRIBUTE_ID: 0x100, + CONF_VALUE: (apptype << 16) | 0x0100FFFF, + CONF_TYPE: "UINT32", + }, + ) + ep[CONF_CLUSTERS][0][CONF_ATTRIBUTES].append( + { + CONF_ATTRIBUTE_ID: 0x75, + CONF_VALUE: bacunit, + CONF_TYPE: "ENUM16", + }, + ) + setup_attributes(config, ep[CONF_CLUSTERS]) + add_ep(ep, config.get(CONF_ENDPOINT), config.get(CONF_USE_DEVICE_TYPE)) + return config + + def validate_binary_sensor_esp32(config: ConfigType) -> ConfigType: dev_class = config.get(CONF_DEVICE_CLASS) if config[CONF_CLUSTER] == "device_class": @@ -355,6 +385,9 @@ async def esp32_to_code(config: ConfigType) -> "MockObj": name="espressif/esp-zigbee-lib", ref="2.0.4", ) + # The esp-zigbee-lib blobs reference mbedtls_ecp_* (Zigbee Direct, install + # code ECDH); keep ECP without relying on esp_wifi's Kconfig select. + require_mbedtls_ecp() # Zigbee's crypto platform uses AES-CCM and deterministic ECDSA directly. # Keep the esp32 component from trimming them out of mbedTLS. diff --git a/esphome/components/zigbee/zigbee_helpers_esp32.c b/esphome/components/zigbee/zigbee_helpers_esp32.c index 22f014f3ef4..e52513b7408 100644 --- a/esphome/components/zigbee/zigbee_helpers_esp32.c +++ b/esphome/components/zigbee/zigbee_helpers_esp32.c @@ -72,6 +72,8 @@ ezb_zcl_cluster_desc_t esphome_zb_default_cluster_dscr_create(uint16_t cluster_i return ezb_zcl_time_create_cluster_desc(NULL, role_mask); case EZB_ZCL_CLUSTER_ID_ANALOG_INPUT: return ezb_zcl_analog_input_create_cluster_desc(NULL, role_mask); + case EZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT: + return ezb_zcl_analog_output_create_cluster_desc(NULL, role_mask); case EZB_ZCL_CLUSTER_ID_BINARY_INPUT: return ezb_zcl_binary_input_create_cluster_desc(NULL, role_mask); case EZB_ZCL_CLUSTER_ID_BINARY_OUTPUT: @@ -113,6 +115,8 @@ ezb_err_t esphome_zb_cluster_add_attr(uint16_t cluster_id, ezb_zcl_cluster_desc_ return ezb_zcl_time_cluster_desc_add_attr(cluster_desc, attr_id, value_p); case EZB_ZCL_CLUSTER_ID_ANALOG_INPUT: return ezb_zcl_analog_input_cluster_desc_add_attr(cluster_desc, attr_id, value_p); + case EZB_ZCL_CLUSTER_ID_ANALOG_OUTPUT: + return ezb_zcl_analog_output_cluster_desc_add_attr(cluster_desc, attr_id, value_p); case EZB_ZCL_CLUSTER_ID_BINARY_INPUT: return ezb_zcl_binary_input_cluster_desc_add_attr(cluster_desc, attr_id, value_p); case EZB_ZCL_CLUSTER_ID_BINARY_OUTPUT: diff --git a/esphome/components/zigbee/zigbee_number_zephyr.cpp b/esphome/components/zigbee/zigbee_number_zephyr.cpp index ceb318480cb..49b03d3ec9d 100644 --- a/esphome/components/zigbee/zigbee_number_zephyr.cpp +++ b/esphome/components/zigbee/zigbee_number_zephyr.cpp @@ -10,7 +10,7 @@ extern "C" { } namespace esphome::zigbee { -static const char *const TAG = "zigbee.number"; +ESPHOME_LOG_TAG(TAG, "zigbee.number"); void ZigbeeNumber::setup() { this->parent_->add_callback(this->endpoint_, [this](zb_bufid_t bufid) { this->zcl_device_cb_(bufid); }); diff --git a/esphome/components/zigbee/zigbee_sensor_zephyr.cpp b/esphome/components/zigbee/zigbee_sensor_zephyr.cpp index 25e1e083e00..9e16cb6a0c5 100644 --- a/esphome/components/zigbee/zigbee_sensor_zephyr.cpp +++ b/esphome/components/zigbee/zigbee_sensor_zephyr.cpp @@ -10,7 +10,7 @@ extern "C" { } namespace esphome::zigbee { -static const char *const TAG = "zigbee.sensor"; +ESPHOME_LOG_TAG(TAG, "zigbee.sensor"); ZigbeeSensor::ZigbeeSensor(sensor::Sensor *sensor) : sensor_(sensor) {} diff --git a/esphome/components/zigbee/zigbee_switch_zephyr.cpp b/esphome/components/zigbee/zigbee_switch_zephyr.cpp index 935140e9df0..286a036fc6e 100644 --- a/esphome/components/zigbee/zigbee_switch_zephyr.cpp +++ b/esphome/components/zigbee/zigbee_switch_zephyr.cpp @@ -13,7 +13,7 @@ extern "C" { namespace esphome::zigbee { -static const char *const TAG = "zigbee_on_off.switch"; +ESPHOME_LOG_TAG(TAG, "zigbee_on_off.switch"); void ZigbeeSwitch::dump_config() { ESP_LOGCONFIG(TAG, diff --git a/esphome/components/zigbee/zigbee_zephyr.cpp b/esphome/components/zigbee/zigbee_zephyr.cpp index 286c83b8f56..1b2d7392df9 100644 --- a/esphome/components/zigbee/zigbee_zephyr.cpp +++ b/esphome/components/zigbee/zigbee_zephyr.cpp @@ -1,7 +1,6 @@ #include "zigbee_zephyr.h" #if defined(USE_ZIGBEE) && defined(USE_NRF52) #include "esphome/core/log.h" -#include "esphome/core/application.h" #include #include #include "esphome/core/hal.h" @@ -16,7 +15,7 @@ extern "C" { namespace esphome::zigbee { -static const char *const TAG = "zigbee"; +ESPHOME_LOG_TAG(TAG, "zigbee"); ZigbeeComponent *global_zigbee = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -120,8 +119,6 @@ void ZigbeeComponent::zcl_device_cb(zb_bufid_t bufid) { /* Set default response value. */ p_device_cb_param->status = RET_OK; - App.wake_loop_threadsafe(); - // endpoints are enumerated from 1 if (global_zigbee->callbacks_.size() >= endpoint) { const auto &cb = global_zigbee->callbacks_[endpoint - 1]; @@ -138,7 +135,6 @@ void ZigbeeComponent::on_join_(bool factory_new) { ESP_LOGD(TAG, "Joined the network"); this->join_cb_.call(factory_new); }); - App.wake_loop_threadsafe(); } void ZigbeeComponent::on_start_() { @@ -146,7 +142,6 @@ void ZigbeeComponent::on_start_() { ESP_LOGD(TAG, "Started zigbee stack"); this->start_cb_.call(); }); - App.wake_loop_threadsafe(); } #ifdef USE_ZIGBEE_WIPE_ON_BOOT @@ -199,6 +194,7 @@ void ZigbeeComponent::setup() { zigbee_configure_sleepy_behavior(this->sleepy_); #endif zigbee_enable(); + this->disable_loop(); } #ifdef ESPHOME_LOG_HAS_CONFIG @@ -263,7 +259,10 @@ static void send_attribute_report(zb_bufid_t bufid, zb_uint16_t cmd_id) { zb_buf_free(bufid); } -void ZigbeeComponent::force_report() { this->force_report_ = true; } +void ZigbeeComponent::force_report() { + this->force_report_ = true; + this->enable_loop_soon_any_context(); +} void ZigbeeComponent::add_radio_sleep_time_ms(uint32_t ms) { this->radio_sleep_remainder_ += ms; @@ -277,6 +276,7 @@ void ZigbeeComponent::loop() { this->force_report_ = false; zb_buf_get_out_delayed_ext(send_attribute_report, 0, 0); } + this->disable_loop(); } void ZigbeeComponent::factory_reset() { diff --git a/esphome/components/zio_ultrasonic/__init__.py b/esphome/components/zio_ultrasonic/__init__.py index e69de29bb2d..7b2178dc7cd 100644 --- a/esphome/components/zio_ultrasonic/__init__.py +++ b/esphome/components/zio_ultrasonic/__init__.py @@ -0,0 +1 @@ +DOMAIN = "zio_ultrasonic" diff --git a/esphome/components/zio_ultrasonic/zio_ultrasonic.cpp b/esphome/components/zio_ultrasonic/zio_ultrasonic.cpp index 1fa995b3dcd..e03f2c5d31a 100644 --- a/esphome/components/zio_ultrasonic/zio_ultrasonic.cpp +++ b/esphome/components/zio_ultrasonic/zio_ultrasonic.cpp @@ -5,7 +5,7 @@ namespace esphome::zio_ultrasonic { -static const char *const TAG = "zio_ultrasonic"; +ESPHOME_LOG_TAG(TAG, "zio_ultrasonic"); void ZioUltrasonicComponent::dump_config() { ESP_LOGCONFIG(TAG, "Zio Ultrasonic Sensor:"); diff --git a/esphome/components/zwave_proxy/__init__.py b/esphome/components/zwave_proxy/__init__.py index 660e3f49a0e..e451fe79f4d 100644 --- a/esphome/components/zwave_proxy/__init__.py +++ b/esphome/components/zwave_proxy/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import uart +from esphome.components.wifi import DOMAIN as WIFI_DOMAIN import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_POWER_SAVE_MODE, CONF_WIFI import esphome.final_validate as fv @@ -7,6 +8,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@kbx81"] DEPENDENCIES = ["api", "uart"] +DOMAIN = "zwave_proxy" zwave_proxy_ns = cg.esphome_ns.namespace("zwave_proxy") ZWaveProxy = zwave_proxy_ns.class_("ZWaveProxy", cg.Component, uart.UARTDevice) @@ -14,7 +16,7 @@ ZWaveProxy = zwave_proxy_ns.class_("ZWaveProxy", cg.Component, uart.UARTDevice) def final_validate(config: ConfigType) -> None: full_config = fv.full_config.get() - if (wifi_conf := full_config.get(CONF_WIFI)) and ( + if (wifi_conf := full_config.get(WIFI_DOMAIN)) and ( wifi_conf.get(CONF_POWER_SAVE_MODE).lower() != "none" ): raise cv.Invalid( diff --git a/esphome/components/zwave_proxy/zwave_proxy.cpp b/esphome/components/zwave_proxy/zwave_proxy.cpp index b0d18a3e6a7..bfa16f857a0 100644 --- a/esphome/components/zwave_proxy/zwave_proxy.cpp +++ b/esphome/components/zwave_proxy/zwave_proxy.cpp @@ -12,7 +12,7 @@ namespace esphome::zwave_proxy { -static const char *const TAG = "zwave_proxy"; +ESPHOME_LOG_TAG(TAG, "zwave_proxy"); // Maximum bytes to log in very verbose hex output (168 * 3 = 504, under TX buffer size of 512) static constexpr size_t ZWAVE_MAX_LOG_BYTES = 168; diff --git a/esphome/components/zyaura/__init__.py b/esphome/components/zyaura/__init__.py index e69de29bb2d..dcabd7a6bdf 100644 --- a/esphome/components/zyaura/__init__.py +++ b/esphome/components/zyaura/__init__.py @@ -0,0 +1 @@ +DOMAIN = "zyaura" diff --git a/esphome/components/zyaura/zyaura.cpp b/esphome/components/zyaura/zyaura.cpp index 0b834de90a4..5f203b18939 100644 --- a/esphome/components/zyaura/zyaura.cpp +++ b/esphome/components/zyaura/zyaura.cpp @@ -3,7 +3,7 @@ namespace esphome::zyaura { -static const char *const TAG = "zyaura"; +ESPHOME_LOG_TAG(TAG, "zyaura"); bool IRAM_ATTR ZaDataProcessor::decode(uint32_t ms, bool data) { // check if a new message has started, based on time since previous bit diff --git a/esphome/config.py b/esphome/config.py index bc69661d8f8..28a688f15ab 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -1129,6 +1129,42 @@ class IDPassValidationStep(ConfigValidationStep): if inherits: matches.append(v[0]) + if id.match_config: + # Disambiguate among same-type candidates by comparing their own + # declared config against the requested key/value pairs, e.g. an + # I2C address, instead of requiring a single unambiguous candidate. + criteria = ", ".join(f"{k}={v}" for k, v in id.match_config.items()) + filtered = [ + m + for m in matches + if isinstance( + candidate_conf := result.get_config_for_path( + result.get_path_for_id(m)[:-1] + ), + dict, + ) + and all( + candidate_conf.get(k) == v + for k, v in id.match_config.items() + ) + ] + if len(filtered) == 1: + id.id = filtered[0].id + elif len(filtered) == 0: + result.add_str_error( + f"Couldn't find a '{id.type}' matching {criteria}. " + "Are you missing a hub declaration, or is the address wrong?", + path, + ) + else: + ids = ", ".join(f"'{m.id}'" for m in filtered) + result.add_str_error( + f"Multiple '{id.type}' instances match {criteria}: {ids}. " + "You must assign an explicit ID to the one you want to use.", + path, + ) + continue + if len(matches) == 0: result.add_str_error( f"Couldn't find any component that can be used for '{id.type}'. Are you missing a hub declaration?", diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 579da9a315e..79d7068838a 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -2872,9 +2872,14 @@ def rename_key( When ``removed_in`` is set, a deprecation warning is logged if the old key is present. Pass ``component`` (the platform/component name) alongside ``removed_in`` so the warning identifies where it originates. + + Input that is not a dictionary is returned unchanged, so the schema that + follows reports it as a normal configuration error. """ def validator(config: dict) -> dict: + if not isinstance(config, dict): + return config config = config.copy() if old_key in config: has_at_most_one_key(old_key, new_key)(config) diff --git a/esphome/const.py b/esphome/const.py index fb02d702810..7774722795d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2026.10.0-dev" +__version__ = "2026.11.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 30c03720665..1668263e337 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -385,7 +385,9 @@ class Lambda: class ID: - def __init__(self, id, is_declaration=False, type=None, is_manual=None): + def __init__( + self, id, is_declaration=False, type=None, is_manual=None, match_config=None + ): self.id = id if is_manual is None: self.is_manual = id is not None @@ -393,6 +395,10 @@ class ID: self.is_manual = is_manual self.is_declaration = is_declaration self.type: MockObjClass | None = type + # When set, an unnamed (id=None) searching ID is disambiguated among same-type + # candidates by matching these key/value pairs against each candidate's own + # declared config, instead of requiring exactly one candidate to exist. + self.match_config: dict | None = match_config def resolve(self, registered_ids): from esphome.config_validation import RESERVED_IDS @@ -431,6 +437,7 @@ class ID: is_declaration=self.is_declaration, type=self.type, is_manual=self.is_manual, + match_config=self.match_config, ) diff --git a/esphome/core/alloc_helpers.h b/esphome/core/alloc_helpers.h index 1da3162333b..6f824b5383c 100644 --- a/esphome/core/alloc_helpers.h +++ b/esphome/core/alloc_helpers.h @@ -10,12 +10,16 @@ /// Prefer the stack-based alternatives documented on each function instead. /// New code should avoid using these functions. +#include #include #include #include #include +#include #include +#include "esphome/core/helpers.h" + namespace esphome { // --- String helpers (allocating) --- @@ -70,6 +74,19 @@ std::string format_hex(const uint8_t *data, size_t length); /// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead. std::string format_hex(const std::vector &data); +/// Format an unsigned integer in lowercased hex, starting with the most significant byte. +/// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead. +template::value, int> = 0> std::string format_hex(T val) { + val = convert_big_endian(val); + return format_hex(reinterpret_cast(&val), sizeof(T)); +} + +/// Format the std::array \p data in lowercased hex. +/// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead. +template std::string format_hex(const std::array &data) { + return format_hex(data.data(), data.size()); +} + /// Format a byte array in pretty-printed, human-readable hex format. /// @warning Allocates heap memory. Use format_hex_pretty_to() with a stack buffer instead. std::string format_hex_pretty(const uint8_t *data, size_t length, char separator = '.', bool show_length = true); @@ -90,10 +107,25 @@ std::string format_hex_pretty(const std::vector &data, char separator /// @warning Allocates heap memory. Use format_hex_pretty_to() with a stack buffer instead. std::string format_hex_pretty(const std::string &data, char separator = '.', bool show_length = true); +/// Format an unsigned integer in pretty-printed, human-readable hex format. +/// @warning Allocates heap memory. Use format_hex_pretty_to() with a stack buffer instead. +template::value, int> = 0> +std::string format_hex_pretty(T val, char separator = '.', bool show_length = true) { + val = convert_big_endian(val); + return format_hex_pretty(reinterpret_cast(&val), sizeof(T), separator, show_length); +} + /// Format the byte array \p data of length \p len in binary. /// @warning Allocates heap memory. Use format_bin_to() with a stack buffer instead. std::string format_bin(const uint8_t *data, size_t length); +/// Format an unsigned integer in binary, starting with the most significant byte. +/// @warning Allocates heap memory. Use format_bin_to() with a stack buffer instead. +template::value, int> = 0> std::string format_bin(T val) { + val = convert_big_endian(val); + return format_bin(reinterpret_cast(&val), sizeof(T)); +} + // --- Base64 helpers (allocating) --- /// Encode a byte buffer to base64 string. diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 50d1c619595..2ddc97efb5f 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -36,7 +36,7 @@ namespace esphome { -static const char *const TAG = "app"; +ESPHOME_LOG_TAG(TAG, "app"); // Delay after setup() finishes before trimming the scheduler freelist of its post-boot peak. // 10 s is well past the bulk of post-setup async work (Wi-Fi/MQTT connects, first-read latency). diff --git a/esphome/core/application.h b/esphome/core/application.h index 2f399cbdcd5..b91bc187e6f 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -673,8 +673,8 @@ inline void ESPHOME_ALWAYS_INLINE Application::loop() { #if defined(USE_LWIP_FAST_SELECT) && defined(ESPHOME_THREAD_MULTI_ATOMICS) // Pairs with the TCP/IP thread's SYS_ARCH_UNPROTECT release on rcvevent so // subsequent Socket::ready() checks in this iter observe the published state - // without a per-call memw. Wake is independent (xTaskNotifyGive/ - // ulTaskNotifyTake), so non-losing. Skipped on MULTI_NO_ATOMICS (e.g. + // without a per-call memw. Wake is independent (esphome_main_task_notify/ + // esphome_main_task_wait), so non-losing. Skipped on MULTI_NO_ATOMICS (e.g. // BK72xx) — that path keeps `volatile` in esphome_lwip_socket_has_data() // instead. std::atomic_thread_fence(std::memory_order_acquire); diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 61f34d70f45..d3f1fe59e59 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -69,6 +69,70 @@ template class TemplatableFn { T (*f_)(X...){nullptr}; }; +/// Byte payload that is either a stateless lambda or a static table, which may be in PROGMEM. +/// 8 bytes on 32-bit; codegen stores constant payloads as shared flash tables. +template class TemplatableBytes { + public: + void set_template(std::vector (*func)(Ts...)) { + this->code_.func = func; + this->len_ = -1; + } + void set_static(const uint8_t *data, uint16_t len) { + this->code_.data = data; + this->len_ = len; + } + bool is_static() const { return this->len_ >= 0; } + /// Only valid when is_static(); may point to PROGMEM, so read it with progmem_memcpy or progmem_read_byte. + const uint8_t *data() const { return this->code_.data; } + /// Only valid when is_static(). + size_t size() const { return static_cast(this->len_); } + std::vector value(const Ts &...x) const { + if (this->len_ < 0) + return this->code_.func(x...); + return to_vector(this->code_.data, this->size()); + } + /// Calls fn(const uint8_t *data, size_t len) with the payload readable from RAM: a lambda's vector, a static + /// table directly, or on ESP8266 a copy of the PROGMEM table (on the stack up to N bytes). + template void visit(F &&fn, const Ts &...x) const { + if (this->len_ < 0) { + const std::vector bytes = this->code_.func(x...); + fn(bytes.data(), bytes.size()); + return; + } +#ifdef USE_ESP8266 + SmallBufferWithHeapFallback buf(this->size()); + if (this->len_ != 0) + progmem_memcpy(buf.get(), this->code_.data, this->size()); + fn(buf.get(), this->size()); +#else + fn(this->code_.data, this->size()); +#endif + } + + protected: + static std::vector to_vector(const uint8_t *data, size_t len) { + std::vector out(len); + // An empty payload is (nullptr, 0), and memcpy from nullptr is undefined even for zero bytes. + if (len != 0) + progmem_memcpy(out.data(), data, len); // byte loads from flash fault on ESP8266 + return out; + } + + union { + std::vector (*func)(Ts...); + const uint8_t *data; + } code_{}; + int32_t len_{-1}; // -1: lambda, otherwise the length of the static table +}; + +#define TEMPLATABLE_BYTES(name) \ + protected: \ + TemplatableBytes name##_{}; \ +\ + public: \ + void set_##name##_template(std::vector (*func)(Ts...)) { this->name##_.set_template(func); } \ + void set_##name##_static(const uint8_t *data, uint16_t len) { this->name##_.set_static(data, len); } + // Forward declaration for TemplatableValue (string specialization needs it) template class TemplatableValue; @@ -341,10 +405,14 @@ template class TemplatableValue { /// Check if this holds a static string (const char* stored without allocation) /// The pointer is always directly readable (RAM or flash-mapped). /// Returns false for FLASH_STRING (PROGMEM on ESP8266, requires _P functions). + // Remove before 2027.4.0 + ESPDEPRECATED("Read the value with ref_or_copy_to() instead. Removed in 2027.4.0", "2026.10.0") bool is_static_string() const { return this->type_ == STATIC_STRING; } /// Get the static string pointer (only valid if is_static_string() returns true) /// The pointer is always directly readable — FLASH_STRING uses a separate type. + // Remove before 2027.4.0 + ESPDEPRECATED("Use ref_or_copy_to() instead. Removed in 2027.4.0", "2026.10.0") const char *get_static_string() const { return this->static_str_; } /// Check if the string value is empty without allocating. diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 41dd32ea665..edcd922aebd 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -12,7 +12,7 @@ namespace esphome { -static const char *const TAG = "component"; +ESPHOME_LOG_TAG(TAG, "component"); // Global vectors for component data that doesn't belong in every instance. // Using vector instead of unordered_map for both because: @@ -292,14 +292,17 @@ void Component::status_set_warning(const LogString *message) { } void Component::status_set_error() { this->status_set_error((const LogString *) nullptr); } void Component::status_set_error(const LogString *message) { - if (!this->set_status_flag_(STATUS_LED_ERROR)) - return; - ESP_LOGE(TAG, "%s set Error flag: %s", LOG_STR_ARG(this->get_component_log_str()), - message ? LOG_STR_ARG(message) : LOG_STR_LITERAL("unspecified")); - if (message != nullptr) { + if (this->set_error_flag_(message) && message != nullptr) { store_component_error_message(this, message); } } +bool Component::set_error_flag_(const LogString *message) { + if (!this->set_status_flag_(STATUS_LED_ERROR)) + return false; + ESP_LOGE(TAG, "%s set Error flag: %s", LOG_STR_ARG(this->get_component_log_str()), + message ? LOG_STR_ARG(message) : LOG_STR_LITERAL("unspecified")); + return true; +} void Component::status_clear_warning_slow_path_() { this->component_state_ &= ~STATUS_LED_WARNING; // Clear the app-wide STATUS_LED_WARNING bit only if setup has finished @@ -329,6 +332,21 @@ void Component::status_momentary_error(const char *name, uint32_t length) { this->status_set_error(); this->set_timeout(name, length, [this]() { this->status_clear_error(); }); } +void Component::status_momentary_warning(uint32_t length) { + this->status_momentary_warning(static_cast(nullptr), length); +} +void Component::status_momentary_warning(const LogString *message, uint32_t length) { + this->status_set_warning(message); + this->set_timeout(InternalSchedulerID::STATUS_WARNING, length, [this]() { this->status_clear_warning(); }); +} +void Component::status_momentary_error(uint32_t length) { + this->status_momentary_error(static_cast(nullptr), length); +} +void Component::status_momentary_error(const LogString *message, uint32_t length) { + // Not stored: a cleared momentary error must not show up later as a FAILED reason + this->set_error_flag_(message); + this->set_timeout(InternalSchedulerID::STATUS_ERROR, length, [this]() { this->status_clear_error(); }); +} void Component::dump_config() {} // Function implementation of LOG_UPDATE_INTERVAL macro to reduce code size diff --git a/esphome/core/component.h b/esphome/core/component.h index ecaf863ecfa..0e95f8d4e90 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -67,6 +67,8 @@ inline constexpr uint32_t SCHEDULER_DONT_RUN = 4294967295UL; /// with component-level NUMERIC_ID values, even if the uint32_t values overlap. enum class InternalSchedulerID : uint32_t { POLLING_UPDATE = 0, // PollingComponent interval + STATUS_WARNING = 1, // status_momentary_warning() without a name + STATUS_ERROR = 2, // status_momentary_error() without a name }; // Forward declaration @@ -298,24 +300,24 @@ class Component { this->status_clear_error_slow_path_(); } - /** Set warning status flag and automatically clear it after a timeout. - * - * @param name Identifier for the timeout (used to cancel/replace existing timeouts with the same name). - * Must be a static string literal (stored in flash/rodata), not a temporary or dynamic string. - * This is NOT a message to display - use status_set_warning() with a message if logging is needed. - * @param length Duration in milliseconds before the warning is automatically cleared. - */ + // Remove before 2027.5.0 + ESPDEPRECATED("Use status_momentary_warning() without a name. Removed in 2027.5.0", "2026.11.0") void status_momentary_warning(const char *name, uint32_t length = 5000); - /** Set error status flag and automatically clear it after a timeout. - * - * @param name Identifier for the timeout (used to cancel/replace existing timeouts with the same name). - * Must be a static string literal (stored in flash/rodata), not a temporary or dynamic string. - * This is NOT a message to display - use status_set_error() with a message if logging is needed. - * @param length Duration in milliseconds before the error is automatically cleared. - */ + /// Set warning status flag and clear it after `length` ms. A new call restarts the timeout. + /// `message` is logged in place of "unspecified" when the flag gets set. + void status_momentary_warning(uint32_t length = 5000); + void status_momentary_warning(const LogString *message, uint32_t length = 5000); + + // Remove before 2027.5.0 + ESPDEPRECATED("Use status_momentary_error() without a name. Removed in 2027.5.0", "2026.11.0") void status_momentary_error(const char *name, uint32_t length = 5000); + /// Set error status flag and clear it after `length` ms. A new call restarts the timeout. + /// `message` is logged in place of "unspecified"; unlike status_set_error() it is not stored. + void status_momentary_error(uint32_t length = 5000); + void status_momentary_error(const LogString *message, uint32_t length = 5000); + bool has_overridden_loop() const { return (this->component_state_ & COMPONENT_HAS_LOOP) != 0; } /** Get the integration where this component was declared as a LogString for logging. @@ -356,6 +358,8 @@ class Component { /// Note: Callers often use the return value to decide whether to log a warning/error, /// so once a flag is set, subsequent (potentially different) messages may be suppressed. bool set_status_flag_(uint8_t flag); + /// Set the error flag and log it; true when it was not set before. + bool set_error_flag_(const LogString *message); /** Set an interval function with a const char* name. Empty name means no cancelling possible. * diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 97b13955537..731c0d20171 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -86,6 +86,7 @@ #define USE_IMPROV_BLE_STATE_CALLBACK #define USE_INFRARED #define USE_IR_RF +#define USE_IR_RF_TRANSMIT_COMPLETE #define USE_JSON #define USE_JSON_ARENA #define USE_RADIO_FREQUENCY @@ -255,6 +256,7 @@ #define AUDIO_FILE_MAX_FILES 4 #define USE_AUDIO_DAC #define USE_AUDIO_FLAC_SUPPORT +#define USE_AUDIO_HTTP_CA_CERTIFICATE #define USE_AUDIO_MP3_SUPPORT #define USE_AUDIO_OPUS_SUPPORT #define USE_AUDIO_WAV_SUPPORT @@ -266,7 +268,14 @@ #define USE_API_HOMEASSISTANT_SERVICES #define USE_API_HOMEASSISTANT_STATES #define USE_API_NOISE +#define USE_API_OUTGOING_CONNECTION +#define API_OUTGOING_CONNECTION_PORT 6054 +#define API_OUTGOING_CONNECTION_DELAY 60000 #define USE_API_VARINT64 +#define USE_API_WIZARD +#define USE_API_WIZARD_INPUTS +#define USE_API_WIZARD_LINKED_INPUTS +#define USE_API_WIZARD_STANDALONE_INPUTS #define USE_API_PLAINTEXT #define USE_API_USER_DEFINED_ACTIONS #define USE_API_CUSTOM_SERVICES @@ -275,6 +284,8 @@ #define USE_API_USER_DEFINED_ACTION_RESPONSES_JSON #define API_MAX_SEND_QUEUE 8 #define API_USER_ACTION_STRINGS_SCRATCH_SIZE 64 +#define API_WIZARD_DATA_SIZE 1 +#define API_WIZARD_INPUT_COUNT 1 #define MAX_API_CONNECTIONS 6 // The Improv library is not in the Zephyr tidy environment #define USE_IMPROV_SERIAL @@ -353,7 +364,6 @@ #ifdef USE_ESP32 #define USE_ESP32_CRASH_HANDLER #define USE_ESP32_VASPRINTF_STUB -#define USE_ESP32_INTERNAL_GPIO #define USE_MQTT_IDF_ENQUEUE #define USE_ESPHOME_TASK_LOG_BUFFER #define ESPHOME_TASK_LOG_BUFFER_SIZE 768 @@ -459,6 +469,16 @@ #define USB_HOST_MAX_REQUESTS 16 #define USB_HOST_MAX_PACKET_SIZE 64 #define USB_UART_OUTPUT_CHUNK_COUNT 5 +#define USE_USB_UART_CH34X +#define USE_USB_UART_CP210X +#define USE_USB_UART_FT23XX +#define USE_USB_UART_PL2303 +#define USE_GPIO_HOLD +// USB identity on serial proxy ports needs the usb_host stack +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \ + defined(USE_ESP32_VARIANT_ESP32S31) || defined(USE_ESP32_VARIANT_ESP32H4) +#define USE_SERIAL_PROXY_USB_IDENTITY +#endif #ifdef USE_ARDUINO #define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 3, 7) @@ -491,6 +511,7 @@ #define USE_ETHERNET_CONNECT_TRIGGER #define USE_ETHERNET_DISCONNECT_TRIGGER #define ESPHOME_ETHERNET_IP_STATE_LISTENERS 2 +#define ESPHOME_ETHERNET_PHY_REGISTER_COUNT 2 #endif #ifdef USE_ESP32 @@ -500,6 +521,10 @@ #define USE_OPENTHREAD #define USE_ZIGBEE #endif +#if defined(USE_ESP32_VARIANT_ESP32) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || \ + defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) +#define USE_ESPECTRE +#endif #ifndef USE_OPENTHREAD #define USE_MDNS_SUPPORTS_ENABLE_DISABLE #endif @@ -597,6 +622,7 @@ // to parse against. #ifdef USE_LN882X #define USE_LN882H_BLE_TRACKER +#define USE_WIFI_AP_EXCLUSIVE #else #define USE_BK72XX_BLE_TRACKER #endif diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index dc27c1e56a2..cbb14a37302 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -1,12 +1,13 @@ #include "esphome/core/entity_base.h" #include "esphome/core/application.h" #include "esphome/core/helpers.h" +#include "esphome/core/log.h" #include "esphome/core/progmem.h" #include "esphome/core/string_ref.h" namespace esphome { -static const char *const TAG = "entity_base"; +ESPHOME_LOG_TAG(TAG, "entity_base"); void EntityBase::configure_entity_(const char *name, uint32_t object_id_hash, uint32_t entity_fields) { this->name_ = StringRef(name); diff --git a/esphome/core/freertos_queue.h b/esphome/core/freertos_queue.h index 2f3faf818a3..1067ae07ccf 100644 --- a/esphome/core/freertos_queue.h +++ b/esphome/core/freertos_queue.h @@ -27,9 +27,7 @@ namespace esphome { template class FreeRTOSQueue { public: - FreeRTOSQueue() : dropped_count_(0) { - this->handle_ = xQueueCreateStatic(SIZE, sizeof(T *), this->storage_, &this->queue_buf_); - } + FreeRTOSQueue() : dropped_count_(0) { xQueueCreateStatic(SIZE, sizeof(T *), this->storage_, &this->queue_buf_); } // No destructor — ESPHome components are never destroyed. Intentionally // omitted to avoid pulling in vQueueDelete code on resource-constrained targets. @@ -44,7 +42,7 @@ template class FreeRTOSQueue { if (element == nullptr) return false; - if (xQueueSend(this->handle_, &element, 0) != pdPASS) { + if (xQueueSend(this->handle_(), &element, 0) != pdPASS) { this->increment_dropped_count(); return false; } @@ -53,7 +51,7 @@ template class FreeRTOSQueue { T *pop() { T *element; - if (xQueueReceive(this->handle_, &element, 0) != pdTRUE) { + if (xQueueReceive(this->handle_(), &element, 0) != pdTRUE) { return nullptr; } return element; @@ -80,18 +78,24 @@ template class FreeRTOSQueue { portEXIT_CRITICAL(); } - bool empty() const { return uxQueueMessagesWaiting(this->handle_) == 0; } + bool empty() const { return uxQueueMessagesWaiting(this->handle_()) == 0; } - bool full() const { return uxQueueSpacesAvailable(this->handle_) == 0; } + bool full() const { return uxQueueSpacesAvailable(this->handle_()) == 0; } - size_t size() const { return uxQueueMessagesWaiting(this->handle_); } + size_t size() const { return uxQueueMessagesWaiting(this->handle_()); } protected: // Static storage for the queue — lives in BSS, no heap allocation uint8_t storage_[SIZE * sizeof(T *)]; StaticQueue_t queue_buf_; - QueueHandle_t handle_; uint16_t dropped_count_; + + // xQueueCreateStatic() returns the static queue structure itself as the handle; + // its only other checks are asserts, and a zero length is the one we could hit + static_assert(SIZE > 0, "FreeRTOSQueue needs at least one slot"); + QueueHandle_t handle_() const { + return reinterpret_cast(const_cast(&this->queue_buf_)); + } }; } // namespace esphome diff --git a/esphome/core/gpio.h b/esphome/core/gpio.h index 43db3b7c0c5..8ed0d70f6be 100644 --- a/esphome/core/gpio.h +++ b/esphome/core/gpio.h @@ -1,141 +1,44 @@ #pragma once -#include -#include +#include "esphome/core/gpio_pin.h" -#include "esphome/core/helpers.h" -#include "esphome/core/log.h" +// Each platform has exactly one internal pin class, so InternalGPIOPin is an alias for +// it instead of an abstract base class: calls through the alias are direct and can be inlined. +#if defined(USE_ESP32) +#include "esphome/components/esp32/gpio.h" +namespace esphome { +using InternalGPIOPin = esp32::ESP32InternalGPIOPin; +} +#elif defined(USE_ESP8266) +#include "esphome/components/esp8266/gpio.h" +namespace esphome { +using InternalGPIOPin = esp8266::ESP8266GPIOPin; +} +#elif defined(USE_LIBRETINY) +#include "esphome/components/libretiny/gpio_arduino.h" +namespace esphome { +using InternalGPIOPin = libretiny::ArduinoInternalGPIOPin; +} +#elif defined(USE_RP2) +#include "esphome/components/rp2/gpio.h" +namespace esphome { +using InternalGPIOPin = rp2::RP2GPIOPin; +} +#elif defined(USE_HOST) +#include "esphome/components/host/gpio.h" +namespace esphome { +using InternalGPIOPin = host::HostGPIOPin; +} +#elif defined(USE_ZEPHYR) +#include "esphome/components/zephyr/gpio.h" +namespace esphome { +using InternalGPIOPin = zephyr::ZephyrGPIOPin; +} +#else +#error "gpio.h: no internal pin class for this platform" +#endif namespace esphome { - -/// Maximum buffer size for dump_summary output -inline constexpr size_t GPIO_SUMMARY_MAX_LEN = 48; - -#ifdef USE_ESP8266 -#define LOG_PIN(prefix, pin) log_pin(TAG, F(prefix), pin) -#else -#define LOG_PIN(prefix, pin) log_pin(TAG, prefix, pin) -#endif - -// put GPIO flags in a namespace to not pollute esphome namespace -namespace gpio { - -enum Flags : uint8_t { - // Can't name these just INPUT because of Arduino defines :( - FLAG_NONE = 0x00, - FLAG_INPUT = 0x01, - FLAG_OUTPUT = 0x02, - FLAG_OPEN_DRAIN = 0x04, - FLAG_PULLUP = 0x08, - FLAG_PULLDOWN = 0x10, -}; - -class FlagsHelper { - public: - constexpr FlagsHelper(Flags val) : val_(val) {} - constexpr operator Flags() const { return val_; } - - protected: - Flags val_; -}; -constexpr FlagsHelper operator&(Flags lhs, Flags rhs) { - return static_cast(static_cast(lhs) & static_cast(rhs)); -} -constexpr FlagsHelper operator|(Flags lhs, Flags rhs) { - return static_cast(static_cast(lhs) | static_cast(rhs)); -} - -enum InterruptType : uint8_t { - INTERRUPT_RISING_EDGE = 1, - INTERRUPT_FALLING_EDGE = 2, - INTERRUPT_ANY_EDGE = 3, - INTERRUPT_LOW_LEVEL = 4, - INTERRUPT_HIGH_LEVEL = 5, -}; - -} // namespace gpio - -class GPIOPin { - public: - virtual void setup() = 0; - - virtual void pin_mode(gpio::Flags flags) = 0; - - /** - * @brief Retrieve GPIO pin flags. - * - * @return The GPIO flags describing the pin mode and properties. - */ - virtual gpio::Flags get_flags() const = 0; - - virtual bool digital_read() = 0; - - virtual void digital_write(bool value) = 0; - - /// Write a summary of this pin to the provided buffer. - /// @param buffer The buffer to write to - /// @param len The size of the buffer (must be > 0) - /// @return The number of characters that would be written (excluding null terminator), - /// which may exceed len-1 if truncation occurred (snprintf semantics) - virtual size_t dump_summary(char *buffer, size_t len) const; - - virtual bool is_internal() { return false; } -}; - -/// Copy of GPIOPin that is safe to use from ISRs (with no virtual functions) -class ISRInternalGPIOPin { - public: - ISRInternalGPIOPin() = default; - ISRInternalGPIOPin(void *arg) : arg_(arg) {} - bool digital_read(); - void digital_write(bool value); - void clear_interrupt(); - void pin_mode(gpio::Flags flags); - - protected: - void *arg_{nullptr}; -}; - -class InternalGPIOPin : public GPIOPin { - public: - template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { - this->attach_interrupt(reinterpret_cast(func), arg, type); - } - - virtual void detach_interrupt() const = 0; - - virtual ISRInternalGPIOPin to_isr() const = 0; - - virtual uint8_t get_pin() const = 0; - - bool is_internal() override { return true; } - - virtual bool is_inverted() const = 0; - - protected: - virtual void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const = 0; -}; - -// Inline default implementation for GPIOPin::dump_summary. -// Writes an empty summary; subclasses override to provide pin details. -inline size_t GPIOPin::dump_summary(char *buffer, size_t len) const { - if (len > 0) - buffer[0] = '\0'; - return 0; -} - -// Inline helper for log_pin - allows compiler to inline into log_pin in gpio.cpp -inline void log_pin_with_prefix(const char *tag, const char *prefix, GPIOPin *pin) { - char buffer[GPIO_SUMMARY_MAX_LEN]; - size_t len = pin->dump_summary(buffer, sizeof(buffer)); - len = std::min(len, sizeof(buffer) - 1); - esp_log_printf_(ESPHOME_LOG_LEVEL_CONFIG, tag, __LINE__, "%s%.*s", prefix, (int) len, buffer); -} - -// log_pin function declarations - implementation in gpio.cpp -#ifdef USE_ESP8266 -void log_pin(const char *tag, const __FlashStringHelper *prefix, GPIOPin *pin); -#else -void log_pin(const char *tag, const char *prefix, GPIOPin *pin); -#endif - +static_assert(InternalGPIOPinContract, + "The platform's internal pin class is missing part of the InternalGPIOPin surface " + "(esphome/core/gpio_pin.h)"); } // namespace esphome diff --git a/esphome/core/gpio_pin.h b/esphome/core/gpio_pin.h new file mode 100644 index 00000000000..6c9acfa8d6a --- /dev/null +++ b/esphome/core/gpio_pin.h @@ -0,0 +1,147 @@ +#pragma once +#include +#include +#include +#include + +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { + +/// Maximum buffer size for dump_summary output +inline constexpr size_t GPIO_SUMMARY_MAX_LEN = 48; + +#ifdef USE_ESP8266 +#define LOG_PIN(prefix, pin) log_pin(TAG, F(prefix), pin) +#else +#define LOG_PIN(prefix, pin) log_pin(TAG, prefix, pin) +#endif + +// put GPIO flags in a namespace to not pollute esphome namespace +namespace gpio { + +enum Flags : uint8_t { + // Can't name these just INPUT because of Arduino defines :( + FLAG_NONE = 0x00, + FLAG_INPUT = 0x01, + FLAG_OUTPUT = 0x02, + FLAG_OPEN_DRAIN = 0x04, + FLAG_PULLUP = 0x08, + FLAG_PULLDOWN = 0x10, + FLAG_HOLD = 0x20, +}; + +class FlagsHelper { + public: + constexpr FlagsHelper(Flags val) : val_(val) {} + constexpr operator Flags() const { return val_; } + + protected: + Flags val_; +}; +constexpr FlagsHelper operator&(Flags lhs, Flags rhs) { + return static_cast(static_cast(lhs) & static_cast(rhs)); +} +constexpr FlagsHelper operator|(Flags lhs, Flags rhs) { + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +enum InterruptType : uint8_t { + INTERRUPT_RISING_EDGE = 1, + INTERRUPT_FALLING_EDGE = 2, + INTERRUPT_ANY_EDGE = 3, + INTERRUPT_LOW_LEVEL = 4, + INTERRUPT_HIGH_LEVEL = 5, +}; + +} // namespace gpio + +class GPIOPin { + public: + virtual void setup() = 0; + + virtual void pin_mode(gpio::Flags flags) = 0; + + /** + * @brief Retrieve GPIO pin flags. + * + * @return The GPIO flags describing the pin mode and properties. + */ + virtual gpio::Flags get_flags() const = 0; + + virtual bool digital_read() = 0; + + virtual void digital_write(bool value) = 0; + + /// Write a summary of this pin to the provided buffer. + /// @param buffer The buffer to write to + /// @param len The size of the buffer (must be > 0) + /// @return The number of characters that would be written (excluding null terminator), + /// which may exceed len-1 if truncation occurred (snprintf semantics) + virtual size_t dump_summary(char *buffer, size_t len) const; + + /// Only the platform pin class behind the InternalGPIOPin alias may return true: + /// callers static_cast to InternalGPIOPin on it. + virtual bool is_internal() { return false; } +#ifdef USE_GPIO_HOLD + bool get_hold() const { return this->get_flags() & gpio::FLAG_HOLD; } + virtual bool is_held() const { return false; } +#else + bool get_hold() const { return false; } + bool is_held() const { return false; } +#endif +}; + +/// Copy of GPIOPin that is safe to use from ISRs (with no virtual functions) +class ISRInternalGPIOPin { + public: + ISRInternalGPIOPin() = default; + ISRInternalGPIOPin(void *arg) : arg_(arg) {} + bool digital_read(); + void digital_write(bool value); + void clear_interrupt(); + void pin_mode(gpio::Flags flags); + + protected: + void *arg_{nullptr}; +}; + +// What callers of the InternalGPIOPin alias (bound in esphome/core/gpio.h) rely on beyond +// GPIOPin. The class must be final so calls to the GPIOPin virtuals through the alias are direct. +// int is arbitrary: any T * exercises the public attach_interrupt template. +template +concept InternalGPIOPinContract = std::derived_from && std::is_final_v && + requires(const T pin, void (*func)(int *), int *arg, gpio::InterruptType type) { + { pin.get_pin() } -> std::same_as; + { pin.is_inverted() } -> std::same_as; + { pin.to_isr() } -> std::same_as; + { pin.detach_interrupt() } -> std::same_as; + { pin.attach_interrupt(func, arg, type) } -> std::same_as; +}; + +// Inline default implementation for GPIOPin::dump_summary. +// Writes an empty summary; subclasses override to provide pin details. +inline size_t GPIOPin::dump_summary(char *buffer, size_t len) const { + if (len > 0) + buffer[0] = '\0'; + return 0; +} + +// Inline helper for log_pin - allows compiler to inline into log_pin in gpio.cpp +inline void log_pin_with_prefix(const char *tag, const char *prefix, GPIOPin *pin) { + char buffer[GPIO_SUMMARY_MAX_LEN]; + size_t len = pin->dump_summary(buffer, sizeof(buffer)); + len = std::min(len, sizeof(buffer) - 1); + esp_log_printf_(ESPHOME_LOG_LEVEL_CONFIG, tag, __LINE__, "%s%.*s", prefix, (int) len, buffer); +} + +// log_pin function declarations - implementation in gpio.cpp +#ifdef USE_ESP8266 +void log_pin(const char *tag, const __FlashStringHelper *prefix, GPIOPin *pin); +#else +void log_pin(const char *tag, const char *prefix, GPIOPin *pin); +#endif + +} // namespace esphome diff --git a/esphome/core/hal.h b/esphome/core/hal.h index 4c5a19c6d1b..dd3618f6237 100644 --- a/esphome/core/hal.h +++ b/esphome/core/hal.h @@ -4,9 +4,17 @@ #include #include "gpio.h" #include "esphome/core/defines.h" + #include "esphome/core/time_64.h" #include "esphome/core/time_conversion.h" +/// Address tables in ESP8266 flash must be constant-initialized: a runtime initializer would write to flash. +#if defined(USE_ESP8266) && defined(__GNUC__) && !defined(__clang__) +#define ESPHOME_FLASH_CONSTINIT constinit +#else +#define ESPHOME_FLASH_CONSTINIT +#endif + // Per-platform HAL bits (IRAM_ATTR / PROGMEM macros, in_isr_context(), // inline yield/delay/micros/millis/millis_64 wrappers, ESP8266 progmem // helpers) live next to each platform component as components//hal.h diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 313daacbd0f..1b332515f66 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include #include @@ -29,7 +28,7 @@ std::string progmem_string(ProgmemStr str) { } #endif -static const char *const TAG = "helpers"; +ESPHOME_LOG_TAG(TAG, "helpers"); __attribute__((noinline, cold)) void *callback_manager_grow(void *data, uint16_t size, uint16_t &capacity, size_t elem_size) { @@ -262,7 +261,6 @@ bool str_contains_ignore_case_p(const char *haystack, PGM_P needle) { } #endif // USE_ESP8266 -// str_truncate, str_until, str_lower_case, str_upper_case, str_snake_case moved to alloc_helpers.cpp char *str_sanitize_to(char *buffer, size_t buffer_size, const char *str) { if (buffer_size == 0) { return buffer; @@ -275,8 +273,6 @@ char *str_sanitize_to(char *buffer, size_t buffer_size, const char *str) { return buffer; } -// str_sanitize, str_snprintf, str_sprintf moved to alloc_helpers.cpp - size_t make_name_with_suffix_to(char *buffer, size_t buffer_size, const char *name, size_t name_len, char sep, const char *suffix_ptr, size_t suffix_len) { size_t total_len = name_len + 1 + suffix_len; @@ -310,8 +306,6 @@ size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) { return chars; } -// format_mac_address_pretty moved to alloc_helpers.cpp - // Internal helper for hex formatting - base is 'a' for lowercase or 'A' for uppercase. // When separator is set, it is written unconditionally after each byte and the last // one is overwritten with '\0', eliminating the per-byte `i < length - 1` check. @@ -429,8 +423,6 @@ const char *json_escape_into_buffer(std::span buf, StringRef value, bool s return buf.data(); } -// format_hex (std::string returning overloads) moved to alloc_helpers.cpp - char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator) { return format_hex_internal(buffer, buffer_size, data, length, separator, 'A'); } @@ -466,8 +458,6 @@ char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint16_t *dat return buffer; } -// format_hex_pretty (all std::string returning overloads) moved to alloc_helpers.cpp - char *format_bin_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) { if (buffer_size == 0) { return buffer; @@ -489,8 +479,6 @@ char *format_bin_to(char *buffer, size_t buffer_size, const uint8_t *data, size_ return buffer; } -// format_bin moved to alloc_helpers.cpp - ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { if (on == nullptr && ESPHOME_strcasecmp_P(str, ESPHOME_PSTR("on")) == 0) return PARSE_ON; @@ -538,8 +526,6 @@ static inline void normalize_accuracy_decimals(float &value, int8_t &accuracy_de } } -// value_accuracy_to_string moved to alloc_helpers.cpp - // Fast float-to-string for accuracy_decimals 0-3 (covers virtually all sensor usage). // Avoids snprintf("%.*f") which pulls in heavy float formatting machinery. // Caller must guarantee value is finite and |value| * mult fits in uint32_t. @@ -621,15 +607,11 @@ int8_t step_to_accuracy_decimals(float step) { return decimals; } -// Map a base64/base64url character to its 6-bit value (0-63) arithmetically. -// No lookup table: a table would occupy RAM on ESP8266 (.rodata lives in DRAM there). -// Supports both standard base64 (+/) and base64url (-_) alphabets. -// NOTE: This returns 0 for both 'A' (valid base64 char at index 0) and invalid characters. -// This is safe because is_base64() is ALWAYS checked before calling this function, -// preventing invalid characters from ever reaching here. The base64_decode function -// stops processing at the first invalid character due to the is_base64() check in its -// while loop condition, making this edge case harmless in practice. -static inline uint8_t base64_find_char(char c) { +static constexpr uint8_t INVALID_BASE64_CHAR = 0xFF; + +// 6-bit value of a base64 or base64url char, or INVALID_BASE64_CHAR. +// No lookup table: .rodata lives in DRAM on ESP8266. +static constexpr uint8_t base64_char_value(uint8_t c) { if (c >= 'A' && c <= 'Z') return c - 'A'; if (c >= 'a' && c <= 'z') @@ -641,75 +623,36 @@ static inline uint8_t base64_find_char(char c) { return 62; if (c == '/' || c == '_') return 63; - return 0; + return INVALID_BASE64_CHAR; } -// Check if character is valid base64 or base64url -static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c == '/') || (c == '-') || (c == '_')); } - -// base64_encode (both overloads) moved to alloc_helpers.cpp - size_t base64_decode(const std::string &encoded_string, uint8_t *buf, size_t buf_len) { return base64_decode(reinterpret_cast(encoded_string.data()), encoded_string.size(), buf, buf_len); } -// Decode 4 base64 characters to up to 'count' output bytes, returns true if truncated. -static inline bool base64_decode_quad(uint8_t *char_array_4, int count, uint8_t *buf, size_t buf_len, size_t &out) { - for (int i = 0; i < 4; i++) - char_array_4[i] = base64_find_char(char_array_4[i]); - - uint8_t char_array_3[3]; - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - bool truncated = false; - for (int j = 0; j < count; j++) { - if (out < buf_len) { - buf[out++] = char_array_3[j]; - } else { - truncated = true; - } - } - return truncated; -} - size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *buf, size_t buf_len) { - size_t in_len = encoded_len; - int i = 0; - size_t in = 0; size_t out = 0; - uint8_t char_array_4[4]; - bool truncated = false; - - // SAFETY: The loop condition checks is_base64() before processing each character. - // This ensures base64_find_char() is only called on valid base64 characters, - // preventing the edge case where invalid chars would return 0 (same as 'A'). - while (in_len-- && (encoded_data[in] != '=') && is_base64(encoded_data[in])) { - char_array_4[i++] = encoded_data[in]; - in++; - if (i == 4) { - truncated |= base64_decode_quad(char_array_4, 3, buf, buf_len, out); - i = 0; + uint32_t accum = 0; + uint32_t bits = 0; + // Stops at '=' or any non-alphabet char; leftover bits of a partial group are dropped. + for (size_t in = 0; in < encoded_len; in++) { + uint8_t value = base64_char_value(encoded_data[in]); + if (value == INVALID_BASE64_CHAR) + break; + accum = (accum << 6) | value; + bits += 6; + if (bits >= 8) { + bits -= 8; + if (out == buf_len) { + ESP_LOGW(TAG, "Base64 decode: buffer too small, truncating"); + return out; + } + buf[out++] = static_cast(accum >> bits); } } - - if (i) { - for (int j = i; j < 4; j++) - char_array_4[j] = 0; - - truncated |= base64_decode_quad(char_array_4, i - 1, buf, buf_len, out); - } - - if (truncated) { - ESP_LOGW(TAG, "Base64 decode: buffer too small, truncating"); - } - return out; } -// base64_decode (vector-returning overload) moved to alloc_helpers.cpp - /// Decode base64/base64url string directly into vector of little-endian int32 values /// @param base64 Base64 or base64url encoded string (both +/ and -_ accepted) /// @param out Output vector (cleared and filled with decoded int32 values) @@ -830,8 +773,6 @@ void HighFrequencyLoopRequester::stop() { this->started_ = false; } -// get_mac_address, get_mac_address_pretty moved to alloc_helpers.cpp - void get_mac_address_into_buffer(std::span buf) { uint8_t mac[MAC_ADDRESS_SIZE]; get_mac_address_raw(mac); diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 6d00e187999..a2ede273413 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -26,12 +26,6 @@ #include "esphome/core/optional.h" #include "esphome/core/time_conversion.h" -// Backward compatibility re-export of heap-allocating helpers. -// These functions have moved to alloc_helpers.h. External components should -// update their includes to use #include "esphome/core/alloc_helpers.h" directly. -// This re-export will be removed in 2026.11.0. -#include "esphome/core/alloc_helpers.h" - #ifdef USE_ESP8266 #include #include @@ -129,23 +123,82 @@ template<> constexpr int64_t byteswap(int64_t n) { return __builtin_bswap64(n); /// @name Container utilities ///@{ -/// Lightweight read-only view over a const array stored in RODATA (will typically be in flash memory) -/// Avoids copying data from flash to RAM by keeping a pointer to the flash data. -/// Similar to std::span but with minimal overhead for embedded systems. - -template class ConstVector { +/// Lightweight read-only view over a const array stored in RODATA (will typically be in flash memory). +/// Iterators are raw pointers like FixedVector. With Owning = true it can also hold a heap copy it +/// owns (see the specialization below); the default view never frees and has no extra cost. +template class ConstVector { public: + using value_type = T; + + constexpr ConstVector() = default; constexpr ConstVector(const T *data, size_t size) : data_(data), size_(size) {} - const constexpr T &operator[](size_t i) const { return data_[i]; } - constexpr size_t size() const { return size_; } - constexpr bool empty() const { return size_ == 0; } + const T *begin() const { return this->data_; } + const T *end() const { return this->data_ + this->size_; } + const T *data() const { return this->data_; } + constexpr size_t size() const { return this->size_; } + constexpr bool empty() const { return this->size_ == 0; } + const constexpr T &operator[](size_t i) const { return this->data_[i]; } + const T &at(size_t i) const { return this->data_[i]; } protected: - const T *data_; - size_t size_; + const T *data_{nullptr}; + size_t size_{0}; }; +/// Owning variant: a codegen table that outlives it, or a heap copy of a runtime list it owns. +/// Ownership is the top bit of the size; it is not copyable, so a copy can never outlive the owner. +/// Elements must be whole words so ESP8266 can read a codegen table from flash. +template class ConstVector { + static_assert(std::is_trivially_copyable_v && sizeof(T) % sizeof(uint32_t) == 0, + "ConstVector elements must be whole words so ESP8266 can read them from flash"); + + public: + using value_type = T; + + constexpr ConstVector() = default; + constexpr ConstVector(const T *data, size_t size) : data_(data), size_(size) {} + ConstVector(const ConstVector &) = delete; + ConstVector &operator=(const ConstVector &) = delete; + ~ConstVector() { this->release_(); } + + const T *begin() const { return this->data_; } + const T *end() const { return this->data_ + this->size(); } + const T *data() const { return this->data_; } + size_t size() const { return this->size_ & ~OWNED_BIT; } + bool empty() const { return this->size() == 0; } + const T &operator[](size_t index) const { return this->data_[index]; } + const T &at(size_t index) const { return this->data_[index]; } + + /// Codegen only: call before any runtime copy; it does not free a previous owned copy + /// (generated setup() runs before any lambda or automation can call set_options). + void assign_static(const T *data, size_t size) { + this->data_ = data; + this->size_ = size; + } + /// Copies the list into a heap array this owns, freeing a previous owned copy. + void assign_copy(const T *data, size_t size) { + auto *table = new T[size]; // NOLINT(cppcoreguidelines-owning-memory) + std::copy(data, data + size, table); + this->release_(); + this->data_ = table; + this->size_ = size | OWNED_BIT; + } + + protected: + static constexpr size_t OWNED_BIT = size_t{1} << (sizeof(size_t) * 8 - 1); + + void release_() { + if (this->size_ & OWNED_BIT) + delete[] this->data_; // NOLINT(cppcoreguidelines-owning-memory) + } + + const T *data_{nullptr}; + size_t size_{0}; // top bit set when data_ is an owned heap copy +}; +static_assert(sizeof(ConstVector) == 2 * sizeof(void *), + "ConstVector must stay a pointer and a size"); + /// Small buffer optimization - stores data inline when small, heap-allocates for large data /// This avoids heap fragmentation for common small allocations while supporting arbitrary sizes. /// Memory management is encapsulated - callers just use set() and data(). @@ -1046,13 +1099,8 @@ inline bool str_contains_ignore_case(const char *haystack, const char *needle) { } #endif // USE_ESP8266 -// str_truncate moved to alloc_helpers.h - remove this include before 2026.11.0 - -// str_until, str_lower_case, str_upper_case moved to alloc_helpers.h - remove this comment before 2026.11.0 - /// Convert a single char to snake_case: lowercase and space to underscore. constexpr char to_snake_case_char(char c) { return (c == ' ') ? '_' : (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; } -// str_snake_case moved to alloc_helpers.h - remove this comment before 2026.11.0 /// Sanitize a single char: keep alphanumerics, dashes, underscores; replace others with underscore. constexpr char to_sanitized_char(char c) { @@ -1075,8 +1123,6 @@ template inline char *str_sanitize_to(char (&buffer)[N], const char *s return str_sanitize_to(buffer, N, str); } -// str_sanitize moved to alloc_helpers.h - remove this comment before 2026.11.0 - /// Calculate FNV-1 hash of a string while applying snake_case + sanitize transformations. /// This computes object_id hashes directly from names without creating an intermediate buffer. /// IMPORTANT: Must match Python fnv1_hash_object_id() in esphome/helpers.py. @@ -1091,8 +1137,6 @@ inline uint32_t fnv1_hash_object_id(const char *str, size_t len) { return hash; } -// str_snprintf, str_sprintf moved to alloc_helpers.h - remove this comment before 2026.11.0 - #ifdef USE_ESP8266 // ESP8266: Use vsnprintf_P to keep format strings in flash (PROGMEM) // Format strings must be wrapped with PSTR() macro @@ -1546,32 +1590,6 @@ inline void format_mac_addr_lower_no_sep(const uint8_t *mac, char *output) { format_hex_to(output, MAC_ADDRESS_BUFFER_SIZE, mac, MAC_ADDRESS_SIZE); } -// format_mac_address_pretty, format_hex (all overloads) moved to alloc_helpers.h -// Remove this comment and the template overloads below before 2026.11.0 - -/// Format an unsigned integer in lowercased hex, starting with the most significant byte. -/// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead. -template::value, int> = 0> std::string format_hex(T val) { - val = convert_big_endian(val); - return format_hex(reinterpret_cast(&val), sizeof(T)); -} -/// Format the std::array \p data in lowercased hex. -/// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead. -template std::string format_hex(const std::array &data) { - return format_hex(data.data(), data.size()); -} - -// format_hex_pretty (all overloads) moved to alloc_helpers.h -// Remove this comment and the template overload below before 2026.11.0 - -/// Format an unsigned integer in pretty-printed, human-readable hex format. -/// @warning Allocates heap memory. Use format_hex_pretty_to() with a stack buffer instead. -template::value, int> = 0> -std::string format_hex_pretty(T val, char separator = '.', bool show_length = true) { - val = convert_big_endian(val); - return format_hex_pretty(reinterpret_cast(&val), sizeof(T), separator, show_length); -} - /// Calculate buffer size needed for format_bin_to: "01234567...\0" = bytes * 8 + 1 constexpr size_t format_bin_size(size_t byte_count) { return byte_count * 8 + 1; } @@ -1625,15 +1643,6 @@ inline char *format_bin_to(char (&buffer)[N], T val) { return format_bin_to(buffer, reinterpret_cast(&val), sizeof(T)); } -// format_bin moved to alloc_helpers.h - remove this comment and template overload before 2026.11.0 - -/// Format an unsigned integer in binary, starting with the most significant byte. -/// @warning Allocates heap memory. Use format_bin_to() with a stack buffer instead. -template::value, int> = 0> std::string format_bin(T val) { - val = convert_big_endian(val); - return format_bin(reinterpret_cast(&val), sizeof(T)); -} - /// Return values for parse_on_off(). enum ParseOnOffState : uint8_t { PARSE_NONE = 0, @@ -1644,8 +1653,6 @@ enum ParseOnOffState : uint8_t { /// Parse a string that contains either on, off or toggle. ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const char *off = nullptr); -// value_accuracy_to_string moved to alloc_helpers.h - remove this comment before 2026.11.0 - /// Maximum buffer size for value_accuracy formatting (float ~15 chars + space + UOM ~40 chars + null) static constexpr size_t VALUE_ACCURACY_MAX_LEN = 64; @@ -1658,8 +1665,6 @@ size_t value_accuracy_with_uom_to_buf(std::span bu /// Derive accuracy in decimals from an increment step. int8_t step_to_accuracy_decimals(float step); -// base64_encode (both overloads), base64_decode (vector overload) moved to alloc_helpers.h -// Remove this comment before 2026.11.0 size_t base64_decode(std::string const &encoded_string, uint8_t *buf, size_t buf_len); size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *buf, size_t buf_len); @@ -2118,8 +2123,6 @@ class HighFrequencyLoopRequester { /// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes). void get_mac_address_raw(uint8_t *mac); // NOLINT(readability-non-const-parameter) -// get_mac_address, get_mac_address_pretty moved to alloc_helpers.h - remove this comment before 2026.11.0 - /// Get the device MAC address into the given buffer, in lowercase hex notation. /// Assumes buffer length is MAC_ADDRESS_BUFFER_SIZE (12 digits for hexadecimal representation followed by null /// terminator). diff --git a/esphome/core/log.h b/esphome/core/log.h index 362ed83b915..145ba3a6f8a 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -190,4 +190,12 @@ struct LogString; #endif +// Declares a static log tag at namespace scope in a .cpp file; on ESP8266 it lives in flash, so read it with +// the _P functions, never strcmp/strlen. +#ifdef USE_ESP8266 +#define ESPHOME_LOG_TAG(name, tag) static const char name[] PROGMEM = tag +#else +#define ESPHOME_LOG_TAG(name, tag) static constexpr const char *const name = tag +#endif + } // namespace esphome diff --git a/esphome/core/lwip_fast_select.c b/esphome/core/lwip_fast_select.c index 2042c438044..8e68594a030 100644 --- a/esphome/core/lwip_fast_select.c +++ b/esphome/core/lwip_fast_select.c @@ -99,10 +99,10 @@ // ESP32-C3/C6/H2) cross-core visibility is not an issue. // // FreeRTOS task notification value: -// Written by TCP/IP thread (xTaskNotifyGive in callback) and background tasks -// (xTaskNotifyGive in wake_main_loop). Read by main loop (ulTaskNotifyTake). +// Written by TCP/IP thread (esphome_main_task_notify in callback) and background tasks +// (esphome_main_task_notify in wake_main_loop). Read by main loop (esphome_main_task_wait). // Safe: FreeRTOS notification APIs are thread-safe by design (use internal -// critical sections). Multiple concurrent xTaskNotifyGive calls are safe — +// critical sections). Multiple concurrent notify calls are safe — // the notification count simply increments. // USE_LWIP_FAST_SELECT is set via -D build flag (not cg.add_define) so it is @@ -178,7 +178,7 @@ static void esphome_socket_event_callback(struct netconn *conn, enum netconn_evt // s_original_callback is always valid here: hook_socket() sets it before swapping // the callback pointer, so this wrapper cannot run until it's initialized. s_original_callback(conn, evt, len); - // Wake the main loop task if sleeping in ulTaskNotifyTake(). + // Wake the main loop task if sleeping in esphome_main_task_wait(). // Only notify on receive events to avoid spurious wakeups from send-ready events. // NETCONN_EVT_ERROR is deliberately omitted: LwIP signals errors via RCVPLUS // (rcvevent++ with a NULL pbuf or error in recvmbox), so error conditions @@ -186,7 +186,7 @@ static void esphome_socket_event_callback(struct netconn *conn, enum netconn_evt if (evt == NETCONN_EVT_RCVPLUS) { #ifdef USE_OTA_PLATFORM_ESPHOME // Mark OTA pending-enable only for events on its listen socket. MUST happen - // before xTaskNotifyGive so the flags are visible when the main task wakes. + // before the main task is notified so the flags are visible when it wakes. if (conn == s_ota_listener_conn) { esphome_wake_ota_component_any_context(); } diff --git a/esphome/core/lwip_fast_select.h b/esphome/core/lwip_fast_select.h index 4ba2606d76c..69798fd8eb4 100644 --- a/esphome/core/lwip_fast_select.h +++ b/esphome/core/lwip_fast_select.h @@ -33,9 +33,9 @@ struct lwip_sock *esphome_lwip_get_sock(int fd); /// per-call `memw` that volatile would emit on Xtensa under default /// -mserialize-volatile. Without atomics (e.g. BK72xx), the fence is skipped /// and the volatile load provides ordering on its own. -/// Stale reads are harmless either way: the hooked event_callback -/// xTaskNotifyGives on RCVPLUS, so the next iteration re-snapshots and -/// ulTaskNotifyTake never loses a wake. +/// Stale reads are harmless either way: the hooked event_callback calls +/// esphome_main_task_notify() on RCVPLUS, so the next iteration re-snapshots and +/// esphome_main_task_wait() never loses a wake. /// The offset and size are verified at compile time in lwip_fast_select.c. static inline bool esphome_lwip_socket_has_data(struct lwip_sock *sock) { #ifdef ESPHOME_THREAD_MULTI_ATOMICS @@ -46,7 +46,7 @@ static inline bool esphome_lwip_socket_has_data(struct lwip_sock *sock) { } /// Hook a socket's netconn callback to notify the main loop task on receive events. -/// Wraps the original event_callback with one that also calls xTaskNotifyGive(). +/// Wraps the original event_callback with one that also calls esphome_main_task_notify(). /// Must be called from the main loop after socket creation. /// The sock pointer must have been obtained from esphome_lwip_get_sock(). void esphome_lwip_hook_socket(struct lwip_sock *sock); diff --git a/esphome/core/main_task.h b/esphome/core/main_task.h index 3aa8669e445..836d13d8f77 100644 --- a/esphome/core/main_task.h +++ b/esphome/core/main_task.h @@ -19,12 +19,25 @@ extern "C" { extern TaskHandle_t esphome_main_task_handle; +#ifdef USE_ESP32 +/// ESP-IDF waits on index 0 from the main task (e.g. pthread_join() uses an unfiltered +/// xTaskNotifyWait), so main-loop wakes on index 0 would end those waits early. +enum { ESPHOME_MAIN_TASK_NOTIFY_INDEX = 1 }; +#if configTASK_NOTIFICATION_ARRAY_ENTRIES < 2 +#error "CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES must be at least 2" +#endif +#endif + /// Wake the main loop task from another FreeRTOS task. NOT ISR-safe. /// always_inline so callers placed in IRAM do not reference a flash-resident copy. __attribute__((always_inline)) static inline void esphome_main_task_notify() { TaskHandle_t task = esphome_main_task_handle; if (task != NULL) { +#ifdef USE_ESP32 + xTaskNotifyGiveIndexed(task, ESPHOME_MAIN_TASK_NOTIFY_INDEX); +#else xTaskNotifyGive(task); +#endif } } @@ -33,10 +46,23 @@ __attribute__((always_inline)) static inline void esphome_main_task_notify_from_ BaseType_t *px_higher_priority_task_woken) { TaskHandle_t task = esphome_main_task_handle; if (task != NULL) { +#ifdef USE_ESP32 + vTaskNotifyGiveIndexedFromISR(task, ESPHOME_MAIN_TASK_NOTIFY_INDEX, px_higher_priority_task_woken); +#else vTaskNotifyGiveFromISR(task, px_higher_priority_task_woken); +#endif } } +/// Block the main loop task until it is woken or the timeout expires. +__attribute__((always_inline)) static inline void esphome_main_task_wait(TickType_t ticks) { +#ifdef USE_ESP32 + ulTaskNotifyTakeIndexed(ESPHOME_MAIN_TASK_NOTIFY_INDEX, pdTRUE, ticks); +#else + ulTaskNotifyTake(pdTRUE, ticks); +#endif +} + #ifdef __cplusplus } #endif diff --git a/esphome/core/ring_buffer.h b/esphome/core/ring_buffer.h deleted file mode 100644 index fbb089e9066..00000000000 --- a/esphome/core/ring_buffer.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#ifdef USE_ESP32 - -// Deprecated: include "esphome/components/ring_buffer/ring_buffer.h" and use -// esphome::ring_buffer::RingBuffer. This shim will be removed in 2026.11.0. -#if __has_include("esphome/components/ring_buffer/ring_buffer.h") -#include "esphome/components/ring_buffer/ring_buffer.h" -#else -#error \ - "esphome/components/ring_buffer/ring_buffer.h not found. Add 'ring_buffer' to your component's AUTO_LOAD list to use esphome::ring_buffer::RingBuffer." -#endif -#include "esphome/core/helpers.h" // for ESPDEPRECATED - -namespace esphome { - -using RingBuffer ESPDEPRECATED("Use esphome::ring_buffer::RingBuffer instead. Removed in 2026.11.0.", - "2026.5.0") = ring_buffer::RingBuffer; - -} // namespace esphome - -#endif diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index afb323f78ef..5d0b96c38f2 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -12,7 +12,7 @@ namespace esphome { -static const char *const TAG = "scheduler"; +ESPHOME_LOG_TAG(TAG, "scheduler"); // Maximum number of logically deleted (cancelled) items before forcing cleanup. // Empirically chosen to balance cleanup overhead against tombstone accumulation in items_. @@ -138,79 +138,84 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type } // Take lock early to protect scheduler_item_pool_head_ access - LockGuard guard{this->lock_}; + { + LockGuard guard{this->lock_}; - // Create and populate the scheduler item - SchedulerItem *item = this->get_item_from_pool_locked_(); - // SELF_POINTER items store the source name (owning script) in the union slot instead of a component. - if (name_type == NameType::SELF_POINTER) { - item->source_name = source; - } else { - item->component = component; - } - item->set_name(name_type, static_name, hash_or_id); - item->type = type; - // Use destroy + placement-new instead of move-assignment. - // GCC's std::function::operator=(function&&) does a full swap dance even when the - // target is empty. Since recycled/new items always have an empty callback, we can - // destroy the empty one (no-op) and move-construct directly, saving ~40 bytes of - // swap/destructor code on Xtensa. - item->callback.~function(); - new (&item->callback) std::function(std::move(func)); - // Reset remove flag - recycled items may have been cancelled (remove=true) in previous use - this->set_item_removed_(item, false); + // Create and populate the scheduler item + SchedulerItem *item = this->get_item_from_pool_locked_(); + // SELF_POINTER items store the source name (owning script) in the union slot instead of a component. + if (name_type == NameType::SELF_POINTER) { + item->source_name = source; + } else { + item->component = component; + } + item->set_name(name_type, static_name, hash_or_id); + item->type = type; + // Use destroy + placement-new instead of move-assignment. + // GCC's std::function::operator=(function&&) does a full swap dance even when the + // target is empty. Since recycled/new items always have an empty callback, we can + // destroy the empty one (no-op) and move-construct directly, saving ~40 bytes of + // swap/destructor code on Xtensa. + item->callback.~function(); + new (&item->callback) std::function(std::move(func)); + // Reset remove flag - recycled items may have been cancelled (remove=true) in previous use + this->set_item_removed_(item, false); - // Determine target container: defer_queue_ for deferred items, to_add_ for everything else. - // Using a pointer lets both paths share the cancel + push_back epilogue. - auto *target = &this->to_add_; + // Determine target container: defer_queue_ for deferred items, to_add_ for everything else. + // Using a pointer lets both paths share the cancel + push_back epilogue. + auto *target = &this->to_add_; #ifndef ESPHOME_THREAD_SINGLE - // Special handling for defer() (delay = 0, type = TIMEOUT) - // Single-core platforms don't need thread-safe defer handling - if (delay == 0 && type == SchedulerItem::TIMEOUT) { - // Put in defer queue for guaranteed FIFO execution - target = &this->defer_queue_; - } else + // Special handling for defer() (delay = 0, type = TIMEOUT) + // Single-core platforms don't need thread-safe defer handling + if (delay == 0 && type == SchedulerItem::TIMEOUT) { + // Put in defer queue for guaranteed FIFO execution + target = &this->defer_queue_; + } else #endif /* not ESPHOME_THREAD_SINGLE */ - { - // Only non-defer items need a timestamp for scheduling - const uint64_t now_64 = millis_64(); + { + // Only non-defer items need a timestamp for scheduling + const uint64_t now_64 = millis_64(); - // Type-specific setup - if (type == SchedulerItem::INTERVAL) { - item->interval = delay; - // first execution happens immediately after a random smallish offset - uint32_t offset = this->calculate_interval_offset_(delay); - item->set_next_execution(now_64 + offset); + // Type-specific setup + if (type == SchedulerItem::INTERVAL) { + item->interval = delay; + // first execution happens immediately after a random smallish offset + uint32_t offset = this->calculate_interval_offset_(delay); + item->set_next_execution(now_64 + offset); #ifdef ESPHOME_LOG_HAS_VERBOSE - SchedulerNameLog name_log; - ESP_LOGV(TAG, "Scheduler interval for %s is %" PRIu32 "ms, offset %" PRIu32 "ms", - name_log.format(name_type, static_name, hash_or_id), delay, offset); + SchedulerNameLog name_log; + ESP_LOGV(TAG, "Scheduler interval for %s is %" PRIu32 "ms, offset %" PRIu32 "ms", + name_log.format(name_type, static_name, hash_or_id), delay, offset); #endif - } else { - item->interval = 0; - item->set_next_execution(now_64 + delay); - } + } else { + item->interval = 0; + item->set_next_execution(now_64 + delay); + } #ifdef ESPHOME_DEBUG_SCHEDULER - this->debug_log_timer_(item, name_type, static_name, hash_or_id, delay, now_64); + this->debug_log_timer_(item, name_type, static_name, hash_or_id, delay, now_64); #endif /* ESPHOME_DEBUG_SCHEDULER */ - } + } - // Common epilogue: atomic cancel-and-add (unless skip_cancel is true or anonymous) - // Anonymous items (STATIC_STRING with nullptr) can never match anything, so skip the scan. - if (!skip_cancel && (name_type != NameType::STATIC_STRING || static_name != nullptr)) { - this->cancel_item_locked_(component, name_type, static_name, hash_or_id, type, /* find_first= */ true); - } - target->push_back(item); - if (target == &this->to_add_) { - this->to_add_count_increment_locked_(); - } + // Common epilogue: atomic cancel-and-add (unless skip_cancel is true or anonymous) + // Anonymous items (STATIC_STRING with nullptr) can never match anything, so skip the scan. + if (!skip_cancel && (name_type != NameType::STATIC_STRING || static_name != nullptr)) { + this->cancel_item_locked_(component, name_type, static_name, hash_or_id, type, /* find_first= */ true); + } + target->push_back(item); + if (target == &this->to_add_) { + this->to_add_count_increment_locked_(); + } #ifndef ESPHOME_THREAD_SINGLE - else { - this->defer_count_increment_locked_(); - } + else { + this->defer_count_increment_locked_(); + } #endif + } + // A background insertion may shorten a sleep whose deadline was already computed. + if (!is_main_loop_thread()) [[unlikely]] + wake_scheduler_threadsafe(); } void HOT Scheduler::set_timeout(Component *component, const char *name, uint32_t timeout, diff --git a/esphome/core/time_64.cpp b/esphome/core/time_64.cpp index 25076228d5e..1b0a49acaf7 100644 --- a/esphome/core/time_64.cpp +++ b/esphome/core/time_64.cpp @@ -17,7 +17,7 @@ namespace esphome { #ifdef ESPHOME_DEBUG_SCHEDULER -static const char *const TAG = "time_64"; +ESPHOME_LOG_TAG(TAG, "time_64"); #endif #ifdef ESPHOME_THREAD_SINGLE diff --git a/esphome/core/wake/wake_esp8266.h b/esphome/core/wake/wake_esp8266.h index 73b7a38a35e..2f7ffc292fe 100644 --- a/esphome/core/wake/wake_esp8266.h +++ b/esphome/core/wake/wake_esp8266.h @@ -11,10 +11,7 @@ namespace esphome { /// Inline implementation — IRAM callers inline this directly. -inline void ESPHOME_ALWAYS_INLINE wake_loop_impl() { - // Set the wake-requested flag BEFORE esp_schedule so the consumer is - // guaranteed to see it on its next gate check. - wake_request_set(); +inline void ESPHOME_ALWAYS_INLINE wake_scheduler_impl() { // Skip the post when a wake was already signalled and not yet consumed by // wakeable_delay(): esp_schedule() -> ets_post() can enter SDK WiFi pm code, // which must not be poked per-byte from the software serial RX ISR (see @@ -26,11 +23,19 @@ inline void ESPHOME_ALWAYS_INLINE wake_loop_impl() { esp_schedule(); } +inline void ESPHOME_ALWAYS_INLINE wake_loop_impl() { + // Set the wake-requested flag BEFORE esp_schedule so the consumer is + // guaranteed to see it on its next gate check. + wake_request_set(); + wake_scheduler_impl(); +} + /// IRAM_ATTR entry point for ISR callers — defined in wake_esp8266.cpp. void wake_loop_any_context(); /// Non-ISR: always inline. inline void wake_loop_threadsafe() { wake_loop_impl(); } +inline void wake_scheduler_threadsafe() { wake_scheduler_impl(); } /// ISR-safe: no task_woken arg because ESP8266 has no FreeRTOS. Caller must be IRAM_ATTR. inline void ESPHOME_ALWAYS_INLINE wake_loop_isrsafe() { wake_loop_impl(); } diff --git a/esphome/core/wake/wake_freertos.cpp b/esphome/core/wake/wake_freertos.cpp index 458ef51f89f..02c628718e6 100644 --- a/esphome/core/wake/wake_freertos.cpp +++ b/esphome/core/wake/wake_freertos.cpp @@ -30,9 +30,6 @@ void IRAM_ATTR wake_loop_any_context() { wake_main_task_any_context(); } } // namespace esphome -extern "C" void esphome_wake_loop_threadsafe() { - esphome::wake_request_set(); - esphome_main_task_notify(); -} +extern "C" void esphome_wake_loop_threadsafe() { esphome::wake_loop_threadsafe(); } #endif // USE_ESP32 || USE_LIBRETINY diff --git a/esphome/core/wake/wake_freertos.h b/esphome/core/wake/wake_freertos.h index 167a422c614..1df7d387656 100644 --- a/esphome/core/wake/wake_freertos.h +++ b/esphome/core/wake/wake_freertos.h @@ -34,24 +34,26 @@ __attribute__((always_inline)) inline void wake_main_task_any_context() { void wake_loop_isrsafe(BaseType_t *px_higher_priority_task_woken); void wake_loop_any_context(); +inline void wake_scheduler_threadsafe() { esphome_main_task_notify(); } + inline void wake_loop_threadsafe() { wake_request_set(); - esphome_main_task_notify(); + wake_scheduler_threadsafe(); } namespace internal { inline void ESPHOME_ALWAYS_INLINE wakeable_delay(uint32_t ms) { // Fast path (with USE_LWIP_FAST_SELECT): FreeRTOS task notifications posted by the lwip // event_callback wrapper (see lwip_fast_select.c) are the single source of truth for - // socket wake-ups. Every NETCONN_EVT_RCVPLUS posts an xTaskNotifyGive, so any notification - // that lands between wakes keeps the counter non-zero (next ulTaskNotifyTake returns - // immediately) or wakes a blocked Take directly. Additional wake sources: + // socket wake-ups. Every NETCONN_EVT_RCVPLUS calls esphome_main_task_notify(), so any notification + // that lands between wakes keeps the counter non-zero (next esphome_main_task_wait() returns + // immediately) or wakes a blocked wait directly. Additional wake sources: // wake_loop_threadsafe() from background tasks, and the ms timeout. if (ms == 0) [[unlikely]] { yield(); return; } - ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(ms)); + esphome_main_task_wait(pdMS_TO_TICKS(ms)); } } // namespace internal diff --git a/esphome/core/wake/wake_host.cpp b/esphome/core/wake/wake_host.cpp index 8cb382a77e0..9fc1c717032 100644 --- a/esphome/core/wake/wake_host.cpp +++ b/esphome/core/wake/wake_host.cpp @@ -123,13 +123,12 @@ void wakeable_delay(uint32_t ms) { if (ms == 0) [[unlikely]] { yield(); } - // A socket woke select() early — open the component-phase gate so the - // owning component's loop() drains the data on this tick rather than - // waiting up to loop_interval_ ms. Idempotent if wake_loop_threadsafe() - // already set the flag (wake socket fired); required when an application - // socket fired and nothing else set the flag. + // Application sockets need the component phase to drain queued work. + // The internal wake socket may only be signaling new scheduler work. if (ret > 0) { - wake_request_set(); + const bool only_wake_socket = ret == 1 && g_wake_socket_fd >= 0 && FD_ISSET(g_wake_socket_fd, &g_read_fds); + if (!only_wake_socket) + wake_request_set(); } return; } @@ -146,16 +145,20 @@ void wakeable_delay(uint32_t ms) { } } // namespace internal -void wake_loop_threadsafe() { - // Set flag before sending so the consumer's gate check on the next loop() - // entry observes the wake regardless of select() scheduling. - wake_request_set(); +void wake_scheduler_threadsafe() { if (internal::g_wake_socket_fd >= 0) { const char dummy = 1; ::send(internal::g_wake_socket_fd, &dummy, 1, 0); } } +void wake_loop_threadsafe() { + // Set flag before sending so the consumer's gate check on the next loop() + // entry observes the wake regardless of select() scheduling. + wake_request_set(); + wake_scheduler_threadsafe(); +} + void wake_setup() { // Create UDP socket for wake notifications. internal::g_wake_socket_fd = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); diff --git a/esphome/core/wake/wake_host.h b/esphome/core/wake/wake_host.h index 9756ed4c39b..8c58797df25 100644 --- a/esphome/core/wake/wake_host.h +++ b/esphome/core/wake/wake_host.h @@ -13,6 +13,7 @@ namespace esphome { /// Host: wakes select() via UDP loopback socket. Defined in wake_host.cpp. void wake_loop_threadsafe(); +void wake_scheduler_threadsafe(); /// Register a socket file descriptor with the host select() loop. Not /// thread-safe — main loop only. Returns false if fd is invalid or diff --git a/esphome/core/wake/wake_rp2.h b/esphome/core/wake/wake_rp2.h index 715e5aca0c8..4f5439dbb8c 100644 --- a/esphome/core/wake/wake_rp2.h +++ b/esphome/core/wake/wake_rp2.h @@ -11,12 +11,16 @@ namespace esphome { +inline void wake_scheduler_threadsafe() { + g_main_loop_woke = true; + __sev(); +} + inline void wake_loop_any_context() { // Set the wake-requested flag BEFORE the SEV so the consumer is guaranteed // to see it on its next gate check. wake_request_set(); - g_main_loop_woke = true; - __sev(); + wake_scheduler_threadsafe(); } inline void wake_loop_threadsafe() { wake_loop_any_context(); } diff --git a/esphome/core/wake/wake_zephyr.cpp b/esphome/core/wake/wake_zephyr.cpp index 577d53f5d9d..2c46fbc9512 100644 --- a/esphome/core/wake/wake_zephyr.cpp +++ b/esphome/core/wake/wake_zephyr.cpp @@ -21,9 +21,11 @@ K_SEM_DEFINE(esphome_wake_sem, 0, 1); // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) volatile uint8_t g_wake_requested = 0; +void wake_scheduler_threadsafe() { k_sem_give(&esphome_wake_sem); } + void wake_loop_threadsafe() { wake_request_set(); - k_sem_give(&esphome_wake_sem); + wake_scheduler_threadsafe(); } namespace internal { diff --git a/esphome/core/wake/wake_zephyr.h b/esphome/core/wake/wake_zephyr.h index c89cfc68e94..04cbfb230b1 100644 --- a/esphome/core/wake/wake_zephyr.h +++ b/esphome/core/wake/wake_zephyr.h @@ -11,6 +11,7 @@ namespace esphome { /// Zephyr: wakes the main loop via k_sem_give(). Thread- and ISR-safe. /// Defined in wake_zephyr.cpp. void wake_loop_threadsafe(); +void wake_scheduler_threadsafe(); inline void wake_loop_any_context() { wake_loop_threadsafe(); } diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index a5ffaefe0be..9c9fed66012 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -10,6 +10,7 @@ from esphome.core import ( ID, Define, EnumValue, + EsphomeError, HexInt, Lambda, Library, @@ -269,6 +270,13 @@ class FlashStringLiteral(Literal): return f"ESPHOME_F({cpp_string_escape(self.string)})" +def progmem_string(value: str) -> Expression: + """A ``std::string`` argument from a literal that stays in flash on ESP8266.""" + if CORE.is_esp8266: + return RawExpression(f"progmem_string({FlashStringLiteral(value)})") + return safe_exp(value) + + class IntLiteral(Literal): __slots__ = ("i",) @@ -440,13 +448,16 @@ class LineComment(Statement): class ProgmemAssignmentExpression(AssignmentExpression): - __slots__ = () + __slots__ = ("constexpr",) - def __init__(self, type_, name, rhs): + def __init__(self, type_, name, rhs, constexpr: bool = True): super().__init__(type_, "", name, rhs) + self.constexpr = constexpr def __str__(self): - return f"static constexpr {self.type} {self.name}[] PROGMEM = {self.rhs}" + if self.constexpr: + return f"static constexpr {self.type} {self.name}[] PROGMEM = {self.rhs}" + return f"ESPHOME_FLASH_CONSTINIT static {self.type} const {self.name}[] PROGMEM = {self.rhs}" class StaticConstAssignmentExpression(AssignmentExpression): @@ -468,21 +479,52 @@ def progmem_array(id_, rhs) -> "MockObj": return obj +class ExternProgmemAssignmentExpression(ProgmemAssignmentExpression): + __slots__ = () + + def __str__(self): + return f"const {self.type} {self.name}[] PROGMEM = {self.rhs}" + + +def extern_progmem_array( + qualified_name: str, type_: "MockObjClass", rhs: SafeExpType +) -> "MockObj": + """Emit an externally linked PROGMEM table that a component declares extern and reads itself.""" + CORE.add_global( + ExternProgmemAssignmentExpression(type_, qualified_name, safe_exp(rhs)) + ) + return MockObj(qualified_name, ".") + + def shared_progmem_array( - name: str, type_: "MockObjClass", rhs: SafeExpType, *, share: bool = True + name: str, + type_: "MockObjClass", + rhs: SafeExpType, + *, + share: bool = True, + constexpr: bool = True, ) -> "MockObj": """Emit a global PROGMEM array once per distinct type and contents; later calls reuse it. - The array is ``static constexpr``, so elements must be constant expressions and lambdas - must be captureless. Its name is made unique against every config id and variable. + The array is ``static constexpr`` by default, so elements must be constant expressions and + lambdas must be captureless. Its name is made unique against every config id and variable. ``share=False`` always emits a new array, e.g. for lambdas that may keep static state. + ``constexpr=False`` is for tables of generated object pointers: each top level variable + element must pass ``is_static_pointer`` or ``EsphomeError`` is raised. """ from esphome.config import iter_ids from esphome.config_validation import RESERVED_IDS arrays: dict[str, MockObj] = CORE.data.setdefault("shared_progmem_array", {}) rhs = safe_exp(rhs) - key = f"{type_} {rhs}" + if not constexpr and isinstance(rhs, ArrayInitializer): + for arg in rhs.args: + if isinstance(arg, MockObj) and not is_static_pointer(arg): + raise EsphomeError( + f"'{arg}' must be created with cg.new_Pvariable so its address is known " + "at compile time" + ) + key = f"{type_} {rhs} {constexpr}" if share and (array := arrays.get(key)) is not None: return array used = {str(i) for i, _ in iter_ids(CORE.config)} @@ -490,7 +532,7 @@ def shared_progmem_array( used |= set(RESERVED_IDS) | CORE.loaded_integrations id_ = ID(ensure_unique_string(name, used), is_declaration=True, type=type_) # Global, so any scope can use it; anything a lambda references is already declared. - CORE.add_global(ProgmemAssignmentExpression(type_, id_, rhs)) + CORE.add_global(ProgmemAssignmentExpression(type_, id_, rhs, constexpr)) array = MockObj(id_, ".") CORE.register_variable(id_, array) if share: @@ -679,6 +721,7 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": ) placement_new = CallExpression(f"new({id_.id}) {actual_type}", *call_expr.args) CORE.add(ExpressionStatement(placement_new)) + CORE.data.setdefault(_STATIC_POINTER_IDS, set()).add(id_.id) else: decl = VariableDeclarationExpression(id_.type, "*", id_, static=True) CORE.add_global(decl) @@ -688,6 +731,20 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": return obj +_STATIC_POINTER_IDS = "static_pointer_ids" + + +def is_static_pointer(obj: SafeExpType) -> bool: + """True if ``obj`` is a Pvariable whose object was placement constructed in static storage. + + Its pointer is then an address known at compile time and may appear in a ``constexpr=False`` + PROGMEM table; a pointer assigned in ``setup()`` may not. + """ + return isinstance(obj, MockObj) and str(obj.base) in CORE.data.get( + _STATIC_POINTER_IDS, () + ) + + def new_Pvariable(id_: ID, *args: SafeExpType) -> "MockObj": """Declare a new pointer variable in the code generation by calling it's constructor with the given arguments. diff --git a/esphome/espidf/_tool_resolution.py b/esphome/espidf/_tool_resolution.py new file mode 100644 index 00000000000..624a07fa25b --- /dev/null +++ b/esphome/espidf/_tool_resolution.py @@ -0,0 +1,67 @@ +"""Shared tool resolution for the sibling idf_tools-backed scripts. + +Importable because ``python