diff --git a/.github/scripts/auto-label-pr/index.js b/.github/scripts/auto-label-pr/index.js index 8b0c821503..1d071b3866 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 5eb96af59b..586263c651 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 @@ -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/sync-dependency-versions.yml b/.github/workflows/sync-dependency-versions.yml index 5599691ed1..73a3f7dbaa 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/AGENTS.md b/AGENTS.md index 6a60fc47b1..8194cd5040 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 930bdc08d2..a851e62a3e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -188,6 +188,7 @@ 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 @@ -287,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 @@ -517,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 @@ -657,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 1619371323..d9a33a23b7 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 9f29062e09..9edbd6e4f9 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.21.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 6c984050f4..905339d2bc 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 @@ -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 b731b74675..16fd5d6282 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 c8d0e4c574..859ec2f1f3 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,6 +257,13 @@ 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)})" + + @dataclass(frozen=True) class ApplyCall: """One statement from config keys, e.g. ``"set_range({}, {})"`` with ``((CONF_LOW, cg.float_), ...)``. @@ -339,9 +370,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 +411,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,7 +489,7 @@ 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 @@ -470,6 +508,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) + + def register_apply_condition( name: str, schema: cv.Schema, check: str | ApplyCall, id_key: str = CONF_ID ) -> None: @@ -495,22 +554,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 +699,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 +729,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 +1038,77 @@ 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, +) -> 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. + """ + members: list[tuple[Any, Any, Any]] = [] + if when is not None: + call = when if isinstance(when, ApplyCall) else ApplyCall(when) + members = call.members + # A trigger callback has no parent for a str type to name. + if any(isinstance(t, str) and "{parent}" in t for _, t, _ in members): + raise ValueError(f"trigger filter {call.target!r}: a type 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, + 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 +1120,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 +1131,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 + ) + 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 +1189,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 +1213,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 5b14af5a1d..44369136a0 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 e7a2dc1536..759d9a0bef 100644 --- a/esphome/build_gen/espidf.py +++ b/esphome/build_gen/espidf.py @@ -237,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: @@ -300,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 @@ -327,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 = ( @@ -554,10 +582,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 8e72b36b3f..a2ee244a29 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 daf53db43f..d57ec39aab 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -31,6 +31,7 @@ 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_template, @@ -38,6 +39,7 @@ from esphome.cpp_generator import ( # noqa: F401 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 4c84847fb6..45206b66bc 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 6111af2b7e..8c97169cbf 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 6724dbb970..f6a28dfa18 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 2832334ef1..20e8f6c7b5 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 e69de29bb2..55797725d6 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 d8fc6752f3..4af9e8baef 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 8f113b48f6..5cfae55a40 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 3137a59fad..36b6534c0d 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 e69de29bb2..7025ecff72 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 477962a040..e126a3d52a 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 543b476085..fd26e6bccd 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 afdfefaba6..caf66167b1 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 bf6849acaf..e3510d380e 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 c397e746b0..6966a86f6b 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 70211000c3..dcb4138201 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 c9887cea7c..6bab6fc469 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 77a192e025..db231f7598 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 dfa545b395..2ea9da935f 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 ce665e8501..5756ac038a 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 bf45059740..c2f4aedd49 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 684147752d..125b4113c5 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 ef0db4730a..bbe95d03be 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 01b6a3f2b8..3cc3f0c056 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 e69de29bb2..28a33c66d8 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 4cbcb3324b..6a3df025e6 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 e69de29bb2..f3dd325f5c 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 0f4189ad90..e131980ebb 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 d3078a0b67..512ac1a3f3 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 71250ac94e..808de99984 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 1adf44f8f7..21a16c5081 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 d3078a0b67..691ceb0274 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 252e55ee5c..cc0fb7e80d 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 d3078a0b67..71ad260db2 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 c2d85231d6..e7c7bac97f 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 b42ee918c5..1b8405322f 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 58704bf7c6..b75e1b7304 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 8086d97231..776346171b 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 956b9a0c1f..98129ecbea 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 0a07193bfe..c21dc2d3f0 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 383a3d25fc..2b3d68d1f4 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 37f34d06df..8d2bbd6fb4 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 377677ccdb..2db39a206d 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 e69de29bb2..888d442fdf 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 cc90abfc3a..83ab55e657 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 e69de29bb2..c7f7f00960 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 0ba960fd70..892a6c56ac 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 44534b80e9..48cebad5fb 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 f2625a7832..5f0e2eb3ec 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 2cec58934d..035142193a 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 5fa59f22fd..f4dfaaee32 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 022f35b4cf..64513a448d 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 f487e9dbc0..54dbc21a6d 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 e26bfd471b..904aa922ba 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 80fe081b57..4b23cb685c 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 78206ff3f8..ac12835433 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 fc72c13ce3..6f23b1aca8 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 0c43cd555a..667cae42a0 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 7cd320c80f..551edbeb7f 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 92b00d87cb..ce81ca4025 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 2398e4fa23..a0ffa96f5e 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 8980a8dfc3..d34061889b 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 e69de29bb2..34e57d220c 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 5445ab3898..94053ab5dc 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 f21a15ce0a..fa3caae506 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 4b096983a4..6fb8c0c7eb 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 ddc3eadae9..1a30af03ae 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 9ae2df986d..c01f3b40e5 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 d25c10021c..c5061365a2 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 e69de29bb2..4fdd8de72c 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 b0769bb622..28e2da93e9 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 3dc8fcf5ff..b22aecf6d2 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 57a502ca42..daf6bb6b33 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 7ac1e5eb32..8c19eec5ca 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 da8029b4ee..ef5d43e2cb 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 9b65f052cc..f538c0b5e5 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -8,6 +8,7 @@ from esphome.automation import Condition import esphome.codegen as cg 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 @@ -51,12 +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.cpp_generator import Expression, MockObj, TemplateArgsType import esphome.final_validate as fv -from esphome.helpers import fnv1_hash +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 @@ -91,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 @@ -389,6 +393,7 @@ 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, @@ -510,7 +515,7 @@ def _validate_outgoing_host_ipv6(config: ConfigType) -> ConfigType: or host.version != 6 ): return config - network_conf = fv.full_config.get().get("network") or {} + 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 " @@ -520,9 +525,15 @@ def _validate_outgoing_host_ipv6(config: ConfigType) -> ConfigType: 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, ) @@ -678,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( @@ -760,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: @@ -794,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), @@ -809,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. @@ -830,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") @@ -911,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, } ) @@ -932,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( @@ -987,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" @@ -1106,6 +1139,7 @@ _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 ed7b8e515c..76b82810a4 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) {} } @@ -233,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 { @@ -389,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; @@ -398,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 { @@ -2692,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 @@ -2706,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; @@ -2715,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 @@ -2885,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; @@ -2906,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 0a2fe13e6a..940c8847ed 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 } @@ -1391,6 +1405,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, @@ -1517,8 +1565,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 @@ -1528,6 +1578,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; } @@ -1540,13 +1591,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 @@ -1554,6 +1630,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 @@ -1645,6 +1722,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()) { @@ -1816,7 +1909,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()); @@ -2029,6 +2122,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); } @@ -2243,7 +2342,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); @@ -2287,8 +2392,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 @@ -2686,6 +2790,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 dacca57369..9f784185b8 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 *); @@ -416,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_(); @@ -751,15 +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 5fbe171b52..9b05da76e5 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 41d1230aaa..de50eb109f 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_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index 21ef085c53..47772fe590 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") diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index d4e3354fa0..dc00d8ec1e 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 66295b3d53..8e8fa99833 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 index c86107cef0..b38976e8cd 100644 --- a/esphome/components/api/api_outgoing_connection.cpp +++ b/esphome/components/api/api_outgoing_connection.cpp @@ -15,7 +15,7 @@ namespace esphome::api { -static const char *const TAG = "api.outgoing"; +ESPHOME_LOG_TAG(TAG, "api.outgoing"); #ifndef API_OUTGOING_CONNECTION_HOST static constexpr uint32_t OUTGOING_TARGET_PREF_HASH = 629847102UL; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index fe8992337d..b59663aeb5 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -27,9 +27,8 @@ void HelloRequest::decode_field(void *self, uint32_t tag, const uint8_t *data, p #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); @@ -54,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; } @@ -67,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; @@ -83,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); @@ -101,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); @@ -118,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); @@ -157,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); @@ -176,7 +170,7 @@ 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 @@ -257,9 +251,8 @@ uint32_t DeviceInfoResponse::calc_size_msg(const void *self) { 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; @@ -273,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; } @@ -287,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; @@ -302,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; } @@ -337,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); @@ -378,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); @@ -402,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); @@ -444,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); @@ -503,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); @@ -551,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); @@ -625,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); @@ -679,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]] { @@ -833,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); @@ -877,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); @@ -905,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); @@ -941,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 @@ -983,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); @@ -1017,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); @@ -1054,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_); @@ -1085,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; } @@ -1099,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; @@ -1113,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 @@ -1196,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); @@ -1285,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 @@ -1311,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 @@ -1415,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); @@ -1439,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); @@ -1471,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); @@ -1507,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); @@ -1611,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]] { @@ -1746,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); @@ -1800,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); @@ -1875,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); @@ -1925,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); @@ -1968,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); @@ -2008,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); @@ -2049,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); @@ -2093,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 @@ -2153,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); @@ -2193,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 @@ -2238,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); @@ -2288,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); @@ -2309,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); @@ -2321,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); @@ -2351,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]] { @@ -2430,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); @@ -2489,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); @@ -2517,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]); @@ -2539,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]); @@ -2549,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; @@ -2571,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; @@ -2602,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; } @@ -2623,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; } @@ -2648,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_); @@ -2731,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_); @@ -2748,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) { @@ -2772,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); @@ -2788,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; @@ -2802,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; @@ -2817,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); @@ -2834,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); @@ -2851,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); @@ -2870,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)); @@ -2911,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]] { @@ -2929,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; } @@ -3004,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); @@ -3065,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; } @@ -3077,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) { @@ -3139,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); @@ -3180,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); @@ -3219,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 @@ -3262,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); @@ -3302,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); @@ -3343,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); @@ -3375,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); @@ -3426,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); @@ -3458,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); @@ -3509,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); @@ -3551,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 @@ -3573,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); @@ -3613,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); @@ -3662,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); @@ -3694,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]] { @@ -3737,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); @@ -3771,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); @@ -3840,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; } @@ -3867,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; @@ -3881,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; @@ -3897,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); @@ -3934,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); @@ -3966,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 @@ -3994,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); @@ -4066,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; @@ -4119,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)); @@ -4147,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)); @@ -4178,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, @@ -4203,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; @@ -4218,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 @@ -4329,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 d94cd709d8..421c3fccb3 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 @@ -448,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); } @@ -471,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); } @@ -528,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); } @@ -547,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); } @@ -566,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); } @@ -644,9 +664,9 @@ class DeviceInfoResponse final : public ProtoMessage { #ifdef USE_API_OUTGOING_CONNECTION bool api_outgoing_connection_supported{false}; #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); } @@ -661,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); } @@ -678,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); } @@ -696,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); } @@ -712,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 @@ -728,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); } @@ -740,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; @@ -763,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); } @@ -784,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); } @@ -810,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); } @@ -832,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); } @@ -880,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); } @@ -904,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); } @@ -956,9 +1060,9 @@ class ListEntitiesLightResponse final : public InfoResponseProtoMessage { 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); + 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); } @@ -987,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); } @@ -1056,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); } @@ -1077,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); } @@ -1100,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); } @@ -1121,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); } @@ -1161,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); } @@ -1182,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); } @@ -1228,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); } @@ -1268,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); } @@ -1286,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); } @@ -1319,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); } @@ -1369,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); } @@ -1480,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); } @@ -1506,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); } @@ -1576,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); } @@ -1597,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); } @@ -1623,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); } @@ -1682,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); } @@ -1715,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); } @@ -1779,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); } @@ -1805,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); } @@ -1855,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); } @@ -1876,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); } @@ -1916,9 +2020,9 @@ class ListEntitiesSelectResponse final : public InfoResponseProtoMessage { const LogString *message_name() const override { return LOG_STR("list_entities_select_response"); } #endif const std::span *options{}; - 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); } @@ -1937,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); } @@ -1979,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); } @@ -1999,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); } @@ -2049,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); } @@ -2069,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); } @@ -2111,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); } @@ -2149,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); } @@ -2170,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); } @@ -2192,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); } @@ -2271,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); } @@ -2317,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); } @@ -2352,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); } @@ -2371,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); } @@ -2389,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); } @@ -2410,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); } @@ -2430,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); } @@ -2476,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); } @@ -2585,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); } @@ -2607,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); } @@ -2629,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); } @@ -2650,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); } @@ -2671,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); } @@ -2693,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); } @@ -2715,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); } @@ -2737,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); } @@ -2761,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); } @@ -2817,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); } @@ -2841,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); } @@ -2920,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); } @@ -2985,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); } @@ -3002,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); } @@ -3061,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); } @@ -3103,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); } @@ -3123,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); } @@ -3167,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); } @@ -3188,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); } @@ -3227,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); } @@ -3250,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); } @@ -3291,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); } @@ -3314,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); } @@ -3357,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); } @@ -3377,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); } @@ -3402,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); } @@ -3423,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); } @@ -3464,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); } @@ -3485,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); } @@ -3525,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); } @@ -3553,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); } @@ -3597,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); } @@ -3623,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); } @@ -3645,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); } @@ -3668,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); } @@ -3681,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; @@ -3721,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); } @@ -3746,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); } @@ -3797,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); } @@ -3876,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); } @@ -3918,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); } @@ -3949,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 { @@ -3982,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 b884507618..3c6f5302b5 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 { @@ -1052,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 @@ -1075,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(); @@ -2720,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 @@ -2749,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 { @@ -2832,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 { @@ -2850,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_service.cpp b/esphome/components/api/api_pb2_service.cpp index 172062be63..683bb84c30 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 a4dfd6a366..7f33e5668c 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 600ff2a2fe..7dad4e390e 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) @@ -504,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{}; @@ -517,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 diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index fc1ca7df13..09b08de4e3 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -203,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 diff --git a/esphome/components/api/api_wizard.cpp b/esphome/components/api/api_wizard.cpp new file mode 100644 index 0000000000..10a5cbc70c --- /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 0000000000..7d5a566b55 --- /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 9e0faf9881..e7798a5d26 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 fd1a9519dc..e808e8a9ec 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 214d9f417f..0fdda5067e 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" @@ -231,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 @@ -263,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; } @@ -288,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 @@ -313,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. @@ -476,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); @@ -575,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 @@ -704,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; } @@ -960,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 0000000000..3ab80a063e --- /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 17d434294a..02c0bd2228 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 e78f301b30..c242ee3913 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 bd02d22d1b..b7b5b09c62 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 a1d74c78de..00ac7ef365 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 83924de760..e1d2d89ec5 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 b3d015114f..06f816182e 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 332a51c7a9..9313705ab6 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 026fde2f21..f966126357 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 780712c3bd..3cc5ed5cc1 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 da1add5458..fc768b383b 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 ba295659a1..88884805a0 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 e69de29bb2..bdbe78a4ec 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 32094ddf10..359636a499 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 31007022d5..b67202db59 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 853ff206c2..03191f6e37 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 a29e166745..50c9080c2d 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 575a9afd40..618e4bee5b 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 e69de29bb2..3d517583e8 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 22cb2b3150..95b482c840 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 ac441a9c2d..b1047e9290 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 46948be47d..8ec611357c 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 766807872b..d25f5c1ce9 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 23701e7834..0c4f095ab5 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 e120e45364..0387149521 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 b882aaa6b7..1480faa8c3 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 051395606c..13dea3f9bc 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 9f2960620e..ea3c82e637 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 { diff --git a/esphome/components/audio_adc/__init__.py b/esphome/components/audio_adc/__init__.py index 7d4b51db04..ba6544b50b 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 fbaa72014f..346f1be798 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/media_source/audio_file_media_source.cpp b/esphome/components/audio_file/media_source/audio_file_media_source.cpp index 0cda1eca9e..0aebcc176f 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_http/__init__.py b/esphome/components/audio_http/__init__.py index e69de29bb2..0126fa0e8b 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 fb8620f7d9..ce1f254310 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 a97025e53e..e75dcf7a67 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 14543957e9..3c23517b92 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 3246dbed24..bed826cf8b 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 3869224b91..1d6354549c 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 e69de29bb2..147319ba95 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 e0ae824b0d..8bed59b3db 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 e69de29bb2..d0d8d1b3b2 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 eebc970795..1553f33618 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 71a87b6ae5..6536dc2c4f 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 a1104aa1b2..1546df9f10 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 1b967e665a..61dc73c73d 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 e69de29bb2..940eabbeb6 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 0cf970b3cc..dcaae6d8d4 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 e69de29bb2..67b3f5ba5a 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 ab952895a8..2d9709f422 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 e69de29bb2..b5b6c5eac9 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 4f746a17d4..6b7f1a3586 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 3f6a45d28a..6244d89b8d 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 118d87c09d..4cc0b145a2 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 f5cde2c8e6..60e20aa193 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 65c7dbbdb6..d4b4906c30 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 7596975a68..43915c4e17 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 8b882212c8..32b25f7211 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 e69de29bb2..3ac51b590e 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 3185f15697..4f00a85c8d 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 e64237c95a..1db8f5d54f 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 38cba56c62..44c66919bb 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 3192fc79d7..05b94e17f9 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 7a4efff455..49167c3000 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 55013d4087..abef3c7752 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 0939e1259f..7e7f04135e 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 b077792604..b840c0ee99 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 9a27cffd04..38edb6ed91 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 9bd4598dd2..a2c2dd4c7b 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 9ae3360e3a..b0b9bb1c17 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 066c2818b6..953824be51 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 642368d93f..a8b470de7e 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 a0b41b9a5b..5598818f54 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 6f054443a2..d382a12e69 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 38b68d84b5..89141daae3 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 e952df21be..7d0eff8c9d 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 810f4b66e3..b783ef1c15 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 25001c8f74..eb24cdeeb4 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 5d53c59708..479bfefd59 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 715298e592..69e7450dc2 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 5dbb7e42ed..1ddd50c307 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 5baca2adcf..1edd42760e 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 ed2b0a63a0..f6df7b4336 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 43ec736727..e8283457c6 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/ble_device.cpp b/esphome/components/ble_device_base/ble_device.cpp index 23ca6b1dbd..f7fa488aac 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 40983c4dbb..54f111e4af 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 b566122f8a..64a36c468f 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 e69de29bb2..0be32a6e95 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 bc169623ce..91947ccd26 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 e69de29bb2..2d31b9aec2 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 7c7c7b2148..eb049313a8 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 e69de29bb2..e687d58e65 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 3d7a301793..42a691b1d9 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 a001729083..a99a39bc9e 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 d3af02780f..7b66c67183 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 ddab4812cc..5e358175bf 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 eec2c8c318..c6e2703c48 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 c87ad7f595..6332ea6dc1 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 cb37057cd4..e4b9487074 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 20254a8b00..c3d1fd8cf6 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 d911301c9d..df1cf96d69 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 4b079def64..8d54cbed2a 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 11c796352a..96bb56f345 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 e69de29bb2..d57004f5fb 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 e69de29bb2..5ca2ba4595 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 e69de29bb2..1ebff77202 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 bac8ed8a5a..6bacb71ff2 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 35df2a7ea3..190994b2f8 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 8e16a28e33..3f0082e1af 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 d4ac57d750..f5a674d6b9 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 1da3bdbd6c..8c50f6e165 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 2a2c5e2bcc..8672e4f6ff 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 49b6d0252a..75386e9857 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 442ee183bf..a90d490ae1 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 0e67e41a0e..499e88b5b1 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 acb93158d4..a1709c1fa5 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 e69de29bb2..51355f5bc0 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 f42875b208..b989ed877b 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 e69de29bb2..99130dd4e1 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 a956ed34f6..68d9145378 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 34e1d67101..9cb023c688 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 e69de29bb2..38fc9582e5 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 e69de29bb2..2b67436333 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 e69de29bb2..076eafa8e7 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 6d0618e136..de9b227aac 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 aa5e799db1..b10a7a7a95 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 e69de29bb2..87cf0e5261 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 704d415993..8d8f3122a9 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 e69de29bb2..6e04124ccb 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 5657626c2d..741f656c07 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 e69de29bb2..ad0da1d8f7 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 5d031a8208..18c18a2e9b 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 7a627eee03..29f67fadc5 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 e69de29bb2..a5c70d3d42 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 e69de29bb2..d8f525adce 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 01435880f0..129128026c 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 b45272d2ea..76b3d427a4 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 95a6b3be45..c7dc61c6f0 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 fa4e8a231b..bd6b6ca110 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 3cd841b2cd..5789acdcb9 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 ed0cbaa9e1..fe78162c65 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 51f5d866a4..c055c43041 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 342f451509..f27475bdca 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 2a2a645132..e85ce60df6 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 3c7c695118..df0c76338c 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 a19f7707af..0cb916ac49 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 344248fcf3..f63c316126 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 55a3f0b96c..84d5a9117d 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 b7de235dd1..51bda0c75d 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 c5ba59a645..828eb77d1c 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 1bc4d6e345..8685c026b0 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 eff0a05163..24f6e627d2 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 125a4aba51..d6d97329db 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 f5e4e7cf20..7554ef848b 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 import esphome.config_validation as cv from esphome.const import ( CONF_AP, @@ -32,6 +33,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) @@ -61,7 +63,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 18fb08da8b..0aa48bc3ea 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -10,7 +10,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/cc1101/__init__.py b/esphome/components/cc1101/__init__.py index 5de39a16ce..8b9e91f833 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 f7b90b91cf..94d89b899d 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 499f77a809..d7065c6048 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 e69de29bb2..4a86948d39 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 afaeac55aa..126ed0049d 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 5f7778e186..655b8db819 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 c275aca75f..b0a042e868 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 516af84856..9d5c0a3e95 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/cdc_acm_uart_bridge.cpp b/esphome/components/cdc_acm_uart/bridge/cdc_acm_uart_bridge.cpp index 042688bfe6..a1bf0a1972 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 7f0c5bb95e..5110503bf8 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 85603f46f4..28b355a4f1 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 9fbf3ea515..3997f9581f 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 8424d130b4..b7e9d224fa 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 80f3a0b33e..bbdc870773 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 e64092d30e..17aeb1467d 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 d08952115b..790307cec1 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 f5a31b31c4..f4b43e3a52 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 47143e6e60..622aff243c 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 e69de29bb2..b62e9d17fb 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 bb612eda7b..042d1dcb21 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 fa3c3f1925..24203304ae 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 2e3352b895..a25a753f75 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 70240eff07..49978c29c2 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 e69de29bb2..2c38fe6fc6 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 e69de29bb2..7625bf2d02 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 8ef0976e3b..0488fbb6ab 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 34e9b99ce8..608ebac4d6 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 6e98162baa..b54d797f09 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 e69de29bb2..e916a94238 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 56eb43cd82..00a7b8a854 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 7594894650..917991b2fc 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 b084b0080a..80093a4a96 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 297e1b0c94..c9441500a9 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 3412784cc7..71bdef667e 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 6eed787b05..1e721332be 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 5fdb9a757b..d8b0b17f84 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 a10bd209f4..7ddc7da170 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 e4f97f3c16..2315514ac5 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 9df23ab92d..17eee92c12 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 555f0030a5..51850f898a 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 5ec3ba05b7..fe8e1a010a 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 97182e8a61..96a0aa32b5 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 c58ce8a01e..a85a88d6e6 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 22adb49cca..c2f6688b2d 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 0f5aa9b450..c570f3d4ca 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 dc2db3bf32..30e7f70b02 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 e69de29bb2..2ea88409a9 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 1f9233841a..efc0cc9fa1 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)); @@ -149,13 +151,13 @@ void CS5460AComponent::schedule_next_check_() { this->status_momentary_warning("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_(); }); diff --git a/esphome/components/cse7761/__init__.py b/esphome/components/cse7761/__init__.py index e69de29bb2..3db7e9a1d4 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 103bc84452..695b7f33d6 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 e69de29bb2..df45f6395c 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 30f1b7a867..9276901156 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 847e44dbda..5bccdc1cba 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 c14da2b176..a755c3d09c 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 49d61cbfbc..849cff4a49 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 374df64898..6b10398969 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 b58f4b4b9f..2010f09751 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 5e1a2ebf72..946eab1b17 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 674a80b7c1..8d6e15abc5 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 f97c8944ef..e3de3243e0 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 e69de29bb2..f7ab2905b2 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 613c3428be..6488c468c3 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 e7f41154b7..0ef871eeed 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 d15b310a37..0474397571 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 e69de29bb2..4eb4f32c12 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 982153414d..395f140d6c 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 e69de29bb2..2da0e50fa0 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 e69de29bb2..45595aada3 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 f7a0c08ec4..e15a5959cb 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 668cc87cec..4ee9f1f541 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 15575583d9..d1a076733a 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 e69de29bb2..cc5617d4d5 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 d45586ba2d..7a1310b9ac 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 f9164fb02b..39d99e9da0 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 e31f72dfb9..e79135b185 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 69002c015f..c9352db073 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 1b085013f1..5d8ef52dc0 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 6c2a9d830e..7d55ae9129 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 3f73044ca8..6a7af3ad4d 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 c418362ced..45101e4981 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 c0d7d0aa62..01d0eef857 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 45d4db4972..a486037266 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 906e9762bf..aca0901d2b 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 3a33e5e004..de79c05a9f 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 diff --git a/esphome/components/datetime/date_entity.cpp b/esphome/components/datetime/date_entity.cpp index b99b89259f..795d54afd9 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_entity.cpp b/esphome/components/datetime/datetime_entity.cpp index 8f180fd081..6aa1416343 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 da4c9eb31e..ea1a97256c 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 898d7d6c0e..889cfaaa68 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 97f4522c62..9c7622d116 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:"); diff --git a/esphome/components/debug/debug_esp32.cpp b/esphome/components/debug/debug_esp32.cpp index 8e1a67224e..1ffc0a2eb6 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 acce28818c..5b62eb6190 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 55b29310a1..a1166758e2 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 4ace4be0a3..7cedac287c 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 e23a0f668a..cec2ab0be8 100644 --- a/esphome/components/debug/debug_zephyr.cpp +++ b/esphome/components/debug/debug_zephyr.cpp @@ -1,6 +1,7 @@ #include "debug_component.h" #ifdef USE_ZEPHYR #include +#include "esphome/core/alloc_helpers.h" #include "esphome/core/log.h" #include #include @@ -12,7 +13,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; diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 06286933b9..2b820c9be6 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 a955095875..47ebd3641e 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 d33102bf4f..f363a0f0ac 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 20297028b2..b71a4fa138 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 +#if !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 2b98f4b855..408d5a6fe6 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 cadf7bf42d..aff5c0e9c7 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 0a81eb2da7..ba66077c84 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 fab2d68347..eec4d7f55b 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 75feaa65af..8f2d5ded20 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_cover.h b/esphome/components/demo/demo_cover.h index aa12c885f4..eeb188a463 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 3b852436c3..ab61f55f0b 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 04ac305e3d..7012bcbb75 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 296afcd318..d4ec9d6590 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 1195f595c8..c45e895b38 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 6334e10ac5..d4e7aed91d 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 570bfef943..f5aee54943 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 42cfd5dff6..9c092982e5 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 71a87b6ae5..9def421103 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 2196f3a982..12b8b0feba 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 e69de29bb2..0078da3300 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 78f9140929..e099b65d5b 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 66fadf12ec..49cd2fc7c6 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 d564ea67bd..4558092702 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 3ecf6d1cf1..8eb9288a5a 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 bb493ae8d3..9d046dbc19 100644 --- a/esphome/components/display_menu_base/__init__.py +++ b/esphome/components/display_menu_base/__init__.py @@ -25,6 +25,7 @@ from esphome.const import ( ) CODEOWNERS = ["@numo68"] +DOMAIN = "display_menu_base" display_menu_base_ns = cg.esphome_ns.namespace("display_menu_base") diff --git a/esphome/components/dlms_meter/__init__.py b/esphome/components/dlms_meter/__init__.py index 00a1694cc3..eed702b6f1 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 a15e58b957..2a51ad0990 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 e672fb200f..32d1817e0e 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 fc4721843f..2d7898f4d3 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 8ded150cd0..15b9deb012 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 c2ff0779ee..4e2e48487a 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 e69de29bb2..52f2a4105e 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 3b4693139d..080f101747 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 e69de29bb2..fa275ded95 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 1d04b52168..4ddef04f41 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 e69de29bb2..81c65f4fd3 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 b0b0ef8175..7762a593f1 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 3d9f24ff19..f101dedc01 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 69103ccbd2..5519848196 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 a2e2a87ed0..3a2cb34747 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 c8f7395119..db104b3d15 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 ed5b05bab7..201261d4bf 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 96a1e75668..07dbf55831 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 e69de29bb2..8d11f04488 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 fd0a48b935..69a41eb548 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 b708cee80b..258253e4eb 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 697a4e96f3..646c2febc6 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 3b1eb99e60..dbe9b1e45c 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 061af4c4c0..09c854c649 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 6157eba4e9..c8940052fd 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 afed2abe31..e3f2e3940a 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 e69de29bb2..82043233c3 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 2dd7405ff5..71ce5b0c70 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 e69de29bb2..03f83e2a58 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 51532548c1..81434c9db6 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 6cd122fb96..024ff3c893 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 bb041bfd6d..6a21b4e7b8 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 bb5eea21f3..bcc8c01c4f 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 e69de29bb2..ec6d2835b0 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 82991aeac7..5cf0fd8ae1 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 7eae375cd6..0262f8681f 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 8efc2de753..ae92b73731 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/emontx_sensor.cpp b/esphome/components/emontx/sensor/emontx_sensor.cpp index 142df0150e..d6f3200e99 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 e69de29bb2..c064448409 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 b3c210a8d8..25b5b54209 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 e69de29bb2..e59458427d 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 6da0d10af9..b41575c717 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 20c67c3450..b06182e59c 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 e69de29bb2..605acf0f53 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 1f02ddb718..b65855b373 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 e69de29bb2..88ad91e632 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 41ab8298db..fe46c22c60 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 e69de29bb2..58f5d4f25f 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 11b73afe37..5815fc9038 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 f70ffa9520..da3685c263 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 3fa504539b..487e8e5ef4 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 1fab038c28..0c1d9b0faf 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 0040c867b2..96755a404e 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 820dc3c77a..0f7f7549d8 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 fc0d674246..60e3245def 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 b81542b6c1..5bffba35c9 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 1cd1087c6b..0de044e8d7 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 fffb2b5e84..b6653038c3 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 f47d37d550..a2fe9bd641 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 e845a5d585..a9245fa1c2 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 18fbb016f3..34120c908b 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 6fb7e1ac1a..9b6acf412a 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 95d1fcb484..bd5a4c9b6e 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 2a4ff2969a..328cd83329 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 7a7b4e22d3..d4c7ba5fdb 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 ad2021ed64..90cf6af7b2 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/es7210/__init__.py b/esphome/components/es7210/__init__.py index e69de29bb2..0038d29040 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 5afc22aec4..0354f719ef 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 e69de29bb2..6968b84c93 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 fc3cba7ae4..1bfa6933dd 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 e69de29bb2..f1e7f4d4e7 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 305aa92125..ebba75e70d 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 03d9713df9..467402e704 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 e69de29bb2..975291c4a7 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 9a4da3744a..96b644ee78 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 e69de29bb2..e8e2e361ba 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 0b97240230..040fd7fe26 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) \ diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index ce1a66a4d2..cfa79e3cf1 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 @@ -76,6 +76,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 +106,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 +239,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 @@ -812,6 +814,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, @@ -1847,6 +1940,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" @@ -1862,12 +1956,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" @@ -1906,6 +1995,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 @@ -1931,33 +2023,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: @@ -1970,18 +2066,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: @@ -2135,6 +2227,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, @@ -2500,34 +2593,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: @@ -2764,6 +2926,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") @@ -2913,6 +3080,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 @@ -3150,6 +3321,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 @@ -3220,38 +3395,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) @@ -3261,12 +3404,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. @@ -3299,6 +3444,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 diff --git a/esphome/components/esp32/atexit_stubs.cpp b/esphome/components/esp32/atexit_stubs.cpp new file mode 100644 index 0000000000..1230a56681 --- /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 a0c9809c50..33751ea7d5 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/crash_handler.cpp b/esphome/components/esp32/crash_handler.cpp index b72a2777c7..182bc5f2d6 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 a00c984f76..6e4de6aa51 100644 --- a/esphome/components/esp32/gpio.cpp +++ b/esphome/components/esp32/gpio.cpp @@ -23,7 +23,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)}; diff --git a/esphome/components/esp32/gpio.h b/esphome/components/esp32/gpio.h index aeff5af51c..9c661b4d9e 100644 --- a/esphome/components/esp32/gpio.h +++ b/esphome/components/esp32/gpio.h @@ -31,6 +31,14 @@ class ESP32InternalGPIOPin final : public InternalGPIOPin { bool is_inverted() const override { return this->pin_flags_.inverted; } 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 + 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; diff --git a/esphome/components/esp32/hal.h b/esphome/components/esp32/hal.h index 2180f07f6c..c01dcc858e 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 91b4241211..88fccea0ca 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 f3d5844cd7..02978ae0f7 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 ef95243bc1..48f2e3c946 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 a44886b8c8..b5051c63e1 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 68704e49e2..30d21ff9f4 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 e9c44284e4..7498228f4c 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 47e4b7ebb2..7481d2c612 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 55619f1fc0..dbb409b17e 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 e830702f11..fceca5f558 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 88454f7bdb..dde47b0c5e 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 695f468c5b..40e9f81d23 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 9355d58859..75a099a3c4 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 4d364b4655..75a3399379 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 3dcac3691c..228313e2d8 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 e34a57cf37..650a765b6a 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 ac8d7fa003..c8f4ecbf33 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/bluedroid_stubs.cpp b/esphome/components/esp32_ble_tracker/bluedroid_stubs.cpp index 5cedc22d06..cffa57a518 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 9bb90026e3..8d3148c2fc 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 3c41d22903..2d4e1ed7e9 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 03fdbc4de7..ef29fc9b25 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 d54d5c6937..c063eb88be 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 bee231d132..36c1cdfa45 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 e69de29bb2..eb2b5b2aaa 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 e04405c63c..083c97f8cf 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 e69de29bb2..6aa72c6456 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 54b89c46ad..3517941de1 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 e69de29bb2..a8e97842ae 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 d78941ea4c..f84f9c6464 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 ad29b208fe..03b03fd983 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 c35301a685..7b9056bc80 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 a213a78778..9d3562ed7b 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 e69de29bb2..a33110a7d3 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 7cac1dfb41..420ff64ddc 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 ede6beb9b6..bdd16bc2ef 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 54bbbe52ed..ed7d0bd7cb 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 e4d8dfc492..d95cbd239b 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 bf4dd1a358..fb50b6a5ca 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 dc79043f21..1ba96eaebb 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 66ac122f00..ae09bdb91c 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -5,7 +5,7 @@ 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 = diff --git a/esphome/components/esp8266/hal.h b/esphome/components/esp8266/hal.h index f3b33da692..1a54065541 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 d954ae4a0f..b731187f9c 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 a650935a5e..c18814d4d8 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 e69de29bb2..741b06bb8b 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 b5b3d5073a..c01a69ce10 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 b3c9a59865..e978dd12b0 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 index a46d6507fd..4f10db996c 100644 --- a/esphome/components/espectre/__init__.py +++ b/esphome/components/espectre/__init__.py @@ -10,8 +10,9 @@ from esphome.components.esp32.const import ( 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, CONF_WIFI +from esphome.const import CONF_ID from esphome.core import CORE from esphome.types import ConfigType @@ -162,7 +163,7 @@ async def to_code(config: ConfigType) -> None: 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[CONF_WIFI].get(wifi.CONF_BAND_MODE, "AUTO") + band = CORE.config[WIFI_DOMAIN].get(wifi.CONF_BAND_MODE, "AUTO") cg.add( var.set_wifi_band_policy( { diff --git a/esphome/components/espectre/espectre.cpp b/esphome/components/espectre/espectre.cpp index 0cce6a3b94..c758a3b246 100644 --- a/esphome/components/espectre/espectre.cpp +++ b/esphome/components/espectre/espectre.cpp @@ -13,7 +13,7 @@ namespace esphome::espectre { -static const char *const TAG = "espectre"; +ESPHOME_LOG_TAG(TAG, "espectre"); struct TrafficModeOption { const char *name; diff --git a/esphome/components/espectre/sensor/espectre_diagnostics.cpp b/esphome/components/espectre/sensor/espectre_diagnostics.cpp index 0edc3c0728..bb6024a233 100644 --- a/esphome/components/espectre/sensor/espectre_diagnostics.cpp +++ b/esphome/components/espectre/sensor/espectre_diagnostics.cpp @@ -7,7 +7,7 @@ namespace esphome::espectre { -static const char *const TAG = "espectre.sensor"; +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) { diff --git a/esphome/components/esphome/__init__.py b/esphome/components/esphome/__init__.py new file mode 100644 index 0000000000..8bcc59dd2d --- /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 0cb2657ea1..dc4fc5b47e 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: @@ -182,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 c399c0901c..318e1e5282 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 { diff --git a/esphome/components/esphome/ota/ota_esphome_inflate_session.cpp b/esphome/components/esphome/ota/ota_esphome_inflate_session.cpp index f3005cac48..38b58b0b03 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 b3d8501f10..a271ef9d39 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 c04cf1ed59..aef8fd17b5 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 3c9c6e8be9..851b8345c9 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 ecf0f79e4a..3fd468764a 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) diff --git a/esphome/components/espnow/packet_transport/espnow_transport.cpp b/esphome/components/espnow/packet_transport/espnow_transport.cpp index b7686f23d6..7053229db4 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 ff05caca83..58915ff96d 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), @@ -672,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") @@ -913,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_esp32.cpp b/esphome/components/ethernet/ethernet_component_esp32.cpp index e9281453c9..1568d37b11 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; diff --git a/esphome/components/ethernet/ethernet_component_rp2.cpp b/esphome/components/ethernet/ethernet_component_rp2.cpp index 94d84cc891..ecb238419e 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 312688d3fd..3f72edbc7d 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 15ef6a1f20..e75d3eb9ae 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 d02863b9ee..c40130634f 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 00bb9497f9..3170c7a8dd 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 index c58ce8a01e..7a389785e7 100644 --- a/esphome/components/exponential_moving_average/__init__.py +++ b/esphome/components/exponential_moving_average/__init__.py @@ -1 +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 index 243e2cf31e..ff4eded9cf 100644 --- a/esphome/components/exponential_moving_average/exponential_moving_average_sensor.cpp +++ b/esphome/components/exponential_moving_average/exponential_moving_average_sensor.cpp @@ -6,7 +6,7 @@ namespace esphome::exponential_moving_average { -static const char *const TAG = "exponential_moving_average"; +ESPHOME_LOG_TAG(TAG, "exponential_moving_average"); const LogString *time_weighting_to_string(TimeWeighting weighting) { switch (weighting) { diff --git a/esphome/components/exposure_notifications/__init__.py b/esphome/components/exposure_notifications/__init__.py index 4f7e698e23..adcfa83717 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 4f4b93b59c..063d7ba678 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 504b1ae679..8994805432 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 e69de29bb2..60408a40ee 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 1157ef1fb0..aed071e8cf 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 21307fcd7a..8db57670ae 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 a9064eb18f..e2376c217c 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 efce998d9d..8ef7dc67eb 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 bceaf6e40f..1de2e3f532 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 65404a35d5..5f38a44654 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 526499fe07..ac5469fc42 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 65521e63d5..866f5eb0cd 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 e2fc8578cd..8936627aaa 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 da4dbf2ed7..38c594a7c7 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 e69de29bb2..05af618c2e 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 e69de29bb2..26451add65 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 9ae2df986d..cfac77e797 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 4baffc74f8..fb6c3929b0 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 f70ffa9520..e98751dac5 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 0266a9caad..0e522ced25 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 07630f121a..99e88af5b7 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/font.cpp b/esphome/components/font/font.cpp index 2592370b6a..8f7ad6e71f 100644 --- a/esphome/components/font/font.cpp +++ b/esphome/components/font/font.cpp @@ -6,7 +6,7 @@ #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}; diff --git a/esphome/components/fs3000/__init__.py b/esphome/components/fs3000/__init__.py index e69de29bb2..c1a6b39d9b 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 a89b166182..81d6647642 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 dceea71dd0..9770f30e43 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 24d3529fb4..ac5f4d8933 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 b6d7d3580e..565da54fe1 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 dd8f5ba629..85729c17f6 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 e69de29bb2..fdb21576be 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 9995a3abbd..9a928dac01 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 122ffaf6ed..9df2024e2a 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 e84c51bde8..fcb9e832ea 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 f98af3f863..e0ede6136d 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 bc0ef70578..3c7e8d2161 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 e69de29bb2..23c5e19b18 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 75f0c69a7b..6e2844eb9e 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 8adf4e983c..a35e228045 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 e69de29bb2..36c643103c 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 0dd4a13a21..ac98a01fd5 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 17c88b6875..583b1d1568 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 7e93bb6295..b82c32c053 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 7a22a280ac..92872f72f2 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 07ebb64cd2..e529b2276d 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 9d044dca2d..87985f189c 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 f445efeca3..62b197d13c 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 ab242c643f..645dd61f80 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 402ad03c49..b407ea3f4b 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 496afd131b..06e3647482 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 d231b3d77a..450a97eb19 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 0c7199b6df..cf6be6e8e0 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 94a36a0afa..cc77dff496 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 4b8abd219b..ec54ac9748 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 3859983ceb..958d0645d9 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 1b99491f9c..0aee597334 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 9ceb2f2ba0..e09eb385e3 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 668a0d74d1..59e8f66c50 100644 --- a/esphome/components/graphical_display_menu/__init__.py +++ b/esphome/components/graphical_display_menu/__init__.py @@ -34,6 +34,7 @@ GraphicalDisplayMenuOnRedrawTrigger = graphical_display_menu_ns.class_( ) CODEOWNERS = ["@MrMDavidson"] +DOMAIN = "graphical_display_menu" AUTO_LOAD = ["display_menu_base"] diff --git a/esphome/components/graphical_display_menu/graphical_display_menu.cpp b/esphome/components/graphical_display_menu/graphical_display_menu.cpp index d261c48855..8761e5fb4c 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) { diff --git a/esphome/components/gree/__init__.py b/esphome/components/gree/__init__.py index 2dd9ac0f1c..67489b3876 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 e59f186521..f47be4aea0 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 2f649733af..0d8f769580 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 e69de29bb2..31a956ac6f 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 0de0b02182..b01dd119e6 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 2d44d88323..0874613c5c 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 2c68eef623..80de3451d7 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 e69de29bb2..078e836783 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 08c3966ed9..5c079b42d9 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 c58ce8a01e..24d19ee887 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 9115130f4a..e518368071 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 1f7ecd1d5e..2b45ba5efe 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 7b356f3946..2a3fc631d6 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 8ced267947..07dd325a2c 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 e69de29bb2..605d315185 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 5c4d498912..ba76053686 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 87f9331d55..65a3ed2616 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 0ce4142fd4..5e672c3e65 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 fdb3b779e2..01e1cbaeed 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 e69de29bb2..f6fa38817c 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 d43dfbb89a..6abca8c2dd 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 7eae863ff5..bbf8313de0 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 fc2a738e8d..311f82b850 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 c8e472d7aa..837c355f46 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 e1028456b0..bbcfae894a 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 6a19f977a6..4fbfbc84d7 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 e69de29bb2..7c1f276f3d 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 6692345e62..be0de1bae6 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 badf9dbb0c..839dc630b2 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 946cf4898f..825d7d3da9 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 341ea61048..e1ab22cc41 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 bf3a4bc79f..8210733b4c 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 d8165281bf..2fbfb0191c 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 53d4c7f016..d542848657 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 c58ce8a01e..070ba595a3 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 008505e2bb..7c67c5ffd3 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 e69de29bb2..f0ef79b895 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 502f83cd5d..eb23eb8ed5 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 diff --git a/esphome/components/hitachi_ac344/__init__.py b/esphome/components/hitachi_ac344/__init__.py index e69de29bb2..15dac43260 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 ea211a7a59..c712500a53 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 10f2c27fe8..8372b84c7e 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 cdf8dda22c..d8f7f66179 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 b8040e5579..72d3cc2ada 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 a924259802..e4845daa57 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 e69de29bb2..3da6262ce7 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 9ef81f075d..a14a9aaf48 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 4908e10037..fc9c674659 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 55e6664a8b..cbb6b6c628 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 e69de29bb2..eb45989270 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 02c6e75146..bfed99b21b 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 e37eb9b116..72f9aa60fe 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 158d740dc5..4f42c968a3 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 e69de29bb2..5326507e44 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 c6b7da6610..c747e7968e 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 958b495c2e..addd1b299b 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 edce6ce4c2..db32eb2952 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 66a141758e..ab2afc74d9 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 22f0c12c8c..bbf1380d17 100644 --- a/esphome/components/hoermann_hcp/hoermann_hcp.cpp +++ b/esphome/components/hoermann_hcp/hoermann_hcp.cpp @@ -9,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 diff --git a/esphome/components/hoermann_hcp/light/hoermann_hcp_light.cpp b/esphome/components/hoermann_hcp/light/hoermann_hcp_light.cpp index f7e40b8921..6195c5d63d 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(); 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 765a80ae6f..cc8d15d723 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 1b66842f1e..3851bc3c0b 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 6ea17b6831..310ade27a8 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 735fb9f5da..d14158399c 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 index 660c59164e..f486a84126 100644 --- a/esphome/components/homeassistant/button/__init__.py +++ b/esphome/components/homeassistant/button/__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, @@ -29,6 +30,8 @@ CONFIG_SCHEMA = cv.All( 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") diff --git a/esphome/components/homeassistant/button/homeassistant_button.cpp b/esphome/components/homeassistant/button/homeassistant_button.cpp index d8d2faa37a..6cf1c87eaa 100644 --- a/esphome/components/homeassistant/button/homeassistant_button.cpp +++ b/esphome/components/homeassistant/button/homeassistant_button.cpp @@ -9,7 +9,7 @@ namespace esphome::homeassistant { -static const char *const TAG = "homeassistant.button"; +ESPHOME_LOG_TAG(TAG, "homeassistant.button"); void HomeassistantButton::dump_config() { LOG_BUTTON("", "Homeassistant Button", this); @@ -24,6 +24,13 @@ void HomeassistantButton::press_action() { 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"); diff --git a/esphome/components/homeassistant/number/__init__.py b/esphome/components/homeassistant/number/__init__.py index 9658d3dc84..6288db6880 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 553c2466f8..df92aad38e 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 index 8be0a4545c..e8ad0fc23b 100644 --- a/esphome/components/homeassistant/select/__init__.py +++ b/esphome/components/homeassistant/select/__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, @@ -40,6 +41,8 @@ CONFIG_SCHEMA = cv.All( 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") diff --git a/esphome/components/homeassistant/select/homeassistant_select.cpp b/esphome/components/homeassistant/select/homeassistant_select.cpp index 82571c768d..189a6b1f37 100644 --- a/esphome/components/homeassistant/select/homeassistant_select.cpp +++ b/esphome/components/homeassistant/select/homeassistant_select.cpp @@ -10,7 +10,7 @@ namespace esphome::homeassistant { -static const char *const TAG = "homeassistant.select"; +ESPHOME_LOG_TAG(TAG, "homeassistant.select"); namespace { @@ -230,6 +230,13 @@ void HomeassistantSelect::control(size_t index) { 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"); diff --git a/esphome/components/homeassistant/sensor/__init__.py b/esphome/components/homeassistant/sensor/__init__.py index abee957fda..1417c57bb5 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 b79a56953a..8256dad484 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 55854cd659..495779b6ed 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 8a4ea19f2e..841f16df20 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 index 6ca920bac7..67377e02d0 100644 --- a/esphome/components/homeassistant/text/__init__.py +++ b/esphome/components/homeassistant/text/__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, @@ -29,6 +30,8 @@ CONFIG_SCHEMA = cv.All( 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") diff --git a/esphome/components/homeassistant/text/homeassistant_text.cpp b/esphome/components/homeassistant/text/homeassistant_text.cpp index a8fabd5e14..7155f6ee18 100644 --- a/esphome/components/homeassistant/text/homeassistant_text.cpp +++ b/esphome/components/homeassistant/text/homeassistant_text.cpp @@ -10,7 +10,7 @@ namespace esphome::homeassistant { -static const char *const TAG = "homeassistant.text"; +ESPHOME_LOG_TAG(TAG, "homeassistant.text"); void HomeassistantText::state_changed_(StringRef state) { if (state == this->state) { @@ -79,6 +79,13 @@ void HomeassistantText::control(const std::string &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 + 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"); diff --git a/esphome/components/homeassistant/text_sensor/__init__.py b/esphome/components/homeassistant/text_sensor/__init__.py index 265250c695..f57b398111 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 40d7455ece..853b0d9b38 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 c08bd73d62..a668d66800 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 8d13fcb152..5808cb6fc8 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 7f075847fd..352d3f7f01 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 03440d675d..e6e1fd8915 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 dd86b95c78..27f3635a73 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 29a910eca9..1205355bc4 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 bfab045838..2ca3fb821c 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 16e4c81d34..d0ec6f2535 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/gpio.cpp b/esphome/components/host/gpio.cpp index f99b82bcc2..c1fd56e945 100644 --- a/esphome/components/host/gpio.cpp +++ b/esphome/components/host/gpio.cpp @@ -6,7 +6,7 @@ namespace esphome { namespace host { -static const char *const TAG = "host"; +ESPHOME_LOG_TAG(TAG, "host"); struct ISRPinArg { uint8_t pin; diff --git a/esphome/components/host/hal.h b/esphome/components/host/hal.h index 12abf6684d..c767632772 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 7274d9de57..9e203efcb9 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 497b9d11e5..9a8208e96e 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 e69de29bb2..2210989d9f 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 b323dd0436..1d58a4317a 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 e69de29bb2..18bffe2b22 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 67bfb1917e..e955531b57 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 de35d52a40..9235971402 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 37e0de5d6a..e689c82d2f 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 0d968222e9..b7565e676d 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; diff --git a/esphome/components/http_request/http_request_host.cpp b/esphome/components/http_request/http_request_host.cpp index a788970202..c32881dc1f 100644 --- a/esphome/components/http_request/http_request_host.cpp +++ b/esphome/components/http_request/http_request_host.cpp @@ -16,7 +16,7 @@ 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, diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index 4e5a2c42b5..1413b843b7 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() { diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index 7e7594c3c3..a26c366fa6 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 6a74c00e8e..bedb0e98e7 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 e69de29bb2..10e6e9d66b 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 8a3b2cb5d5..468a7f8e6a 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 039693cb30..bc17a0d886 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 6821b8b69e..86c9876b2b 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 cd5441f749..3ee6a86415 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 d36928a83a..674e23cb34 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 e69de29bb2..f9402be78e 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 fe2fb4c350..48afc0192d 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 b488bfc1b4..313ce9158c 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 05557111fc..36a7b1b351 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 2e88d4f366..70be33a00e 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 7b2c960a5f..a8374c4115 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 b053125446..453c2513fa 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 76b3ec3b4d..8fc0a50327 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 39a6aec774..95aee2bddb 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 7ca9537e2d..b13b525abb 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 303944636b..672bc9cc5d 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 ffdd2ba8bb..4145cd5859 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 f890fefdb9..a118cb3f17 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 2f68a9e96a..379a25574e 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 c5e82beb46..6e4e35590c 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 c577ed092e..79367d67e0 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`` diff --git a/esphome/components/i2s_audio/speaker/i2s_audio_spdif.cpp b/esphome/components/i2s_audio/speaker/i2s_audio_spdif.cpp index ec4e459be7..5054478ceb 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 4b74b2e29d..a57ef7f55c 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. 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 b4b6173458..fbc1edbde9 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 30146e0a70..c98cbcb20a 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 e69de29bb2..216921250f 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 7397d0975c..48935c2468 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 07f3b4e31c..7736bfd393 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 62a4586ebc..42585269f8 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 e69de29bb2..95ff0c3056 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 84888bbabc..2803b43915 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 9b57b6561f..1acc5e6ca0 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 6745f8064b..e25980545c 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 72ac586628..06e73803f1 100644 --- a/esphome/components/improv_ble/__init__.py +++ b/esphome/components/improv_ble/__init__.py @@ -31,6 +31,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. diff --git a/esphome/components/improv_ble/improv_ble_component.cpp b/esphome/components/improv_ble/improv_ble_component.cpp index 0a20beb33c..2029058899 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 0231791e9b..3e1aef21bb 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 3827fb6ed4..1045467866 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 e69de29bb2..5e9d2cef17 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 833d1989c8..a675099b4d 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 ca1f2caa31..9998623c82 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 c22237d144..97b3caf949 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 e69de29bb2..4728ef80f8 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 05039f0e33..82a4e4fba1 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 b6cf0e38f3..7fdfd90bb6 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 fec5cd2f13..58e0598b06 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 e69de29bb2..d6c784bad7 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 4fc3b00a21..61c7744cca 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 e69de29bb2..ae72c2d195 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 43eb676236..b9b7a6c774 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 e69de29bb2..f8f491ddec 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 b88480fc00..f3647c4d1c 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 d04c82ea96..2d3d478e58 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 83039a5a9b..9b766a8996 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 afbde57be2..d81e3e2e38 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 e69de29bb2..e0fbed17ca 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 d360142bcb..ef6600b4eb 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 1c6013793a..36740332a5 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 2e837fb614..b1e8535d20 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 e69de29bb2..2b198f666b 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 71a87b6ae5..4afc7ce9e1 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 bc26fb0f19..c7b6173e39 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 9433ade13f..aa1671a015 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 91f47d831f..00f3b97ec0 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 89a7d34333..51c55d4adb 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 2c6fda2af4..4da062ac05 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 c4ab33b0a5..b0ccf99145 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 50c597f6f1..5c3e81d10f 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 11c3e15b0d..adbe4349c6 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 0000000000..7a4cde3677 --- /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 0000000000..91db4252c0 --- /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 0000000000..c6dc877696 --- /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 bc4079ede7..45bce4ac6f 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 288bd91673..548ba2b132 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 ceb4c9a67c..0eae11d1a5 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 1aa4394fe8..a45b0ac472 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 28b8fd5953..1519fe9908 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 7fc4ae2cd0..94bdf451d3 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 a6511b63c4..2cfcf111ea 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 ef6335f316..672487d32a 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 c67771a0b6..0c7dd27738 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 618c56faa1..cac06a29d2 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 3ecfce8b69..28ba34d6ca 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 e69de29bb2..35782ff625 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 e69de29bb2..2446761ce9 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 24e5d25921..9713c72b61 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 dedd960c36..8dafb5da07 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 42f02d39d4..bc201a1514 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 f397382ff2..3008545f40 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 e69de29bb2..3316358096 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 d6934a97ac..8bd8b47e21 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 e69de29bb2..34a9637a09 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 cb04afe437..647227524e 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 3be68d174f..3076e38527 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 36e5bce8e3..a97f5217a8 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 08ec395720..58efbd4884 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 0890da85c1..101e858bef 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 e69de29bb2..9df8039c3b 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 213fc8637e..92ae37eab8 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 88b8ac21d4..483a36491d 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 f731817bdb..7ced6e67ce 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 e69de29bb2..cfeba4c863 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 d5fc683598..fc7f5ff2f5 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 d3179d9b30..e2728aa73c 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 914de8e145..22bd20d1e5 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 5b523d0944..a8636f3df6 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 82db319861..fd63f78c3d 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 7041b7539f..bd43d06cc2 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 e236a7d5f3..f350fb7050 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 5a5aabeba0..87a54d278a 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 6297cba2e7..87375ccbd9 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 1e748e59b8..85e3411f49 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 e342ead414..dec7e42de4 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 998eed2188..491cd85734 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 82939e18d3..6d9bb24cbc 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 02b5826f98..d8690a6898 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 f7b016c9d9..a8360b1ccb 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 4c37f4fcd1..a54e83f287 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 4b41d63a88..b43493c26e 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 e714d353d1..6f2056330b 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 516af84856..97ff42626c 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 af1e501a6a..c7529f9314 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 a664890a86..a33e706eb6 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 aa34ad9d39..93a2ea341f 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 bea3804312..c52f770d6e 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 236b049f53..de0ff57a64 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 5653f8b944..d2285187f6 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 71a87b6ae5..cc2c4d56aa 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 62833a7649..18ce19a183 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 5d630fec6c..61eafe5f86 100644 --- a/esphome/components/libretiny/__init__.py +++ b/esphome/components/libretiny/__init__.py @@ -57,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. @@ -491,6 +492,9 @@ 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") diff --git a/esphome/components/libretiny/atexit_stubs.cpp b/esphome/components/libretiny/atexit_stubs.cpp new file mode 100644 index 0000000000..1d237e7f05 --- /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/gpio_arduino.cpp b/esphome/components/libretiny/gpio_arduino.cpp index ca88835f69..ff333aacef 100644 --- a/esphome/components/libretiny/gpio_arduino.cpp +++ b/esphome/components/libretiny/gpio_arduino.cpp @@ -5,7 +5,7 @@ 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 = diff --git a/esphome/components/libretiny/hal.h b/esphome/components/libretiny/hal.h index 48b94a5214..838536b866 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/lt_component.cpp b/esphome/components/libretiny/lt_component.cpp index d7d93249c5..9cc7b5ff68 100644 --- a/esphome/components/libretiny/lt_component.cpp +++ b/esphome/components/libretiny/lt_component.cpp @@ -9,7 +9,7 @@ namespace esphome::libretiny { -static const char *const TAG = "libretiny"; +ESPHOME_LOG_TAG(TAG, "libretiny"); void LTComponent::dump_config() { ESP_LOGCONFIG(TAG, diff --git a/esphome/components/libretiny/preferences.cpp b/esphome/components/libretiny/preferences.cpp index d0bd3bf26b..a868e5042e 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 4db724f8ad..1825606769 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 eea593a39d..ee6fd5bb0e 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/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index d2f5913f4b..a6440700fa 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 ddac2f9341..35aa035b26 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 7e54fccebc..5ad9215eed 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. diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 1b9a7f94c1..9fe483a291 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -13,7 +13,7 @@ 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 diff --git a/esphome/components/lightwaverf/__init__.py b/esphome/components/lightwaverf/__init__.py index a1138c7de8..1a4234ceaf 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 b8b6a9697e..58c4ab0784 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 5499d096a9..cb3605c63e 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 87319235e9..d76f4dfb39 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 e69de29bb2..228b24febe 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 2e8b9d3cfe..56e69faa62 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 aadc1f3b2f..7e382eda85 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 0b15bf434c..8f569086de 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 69a01ae1ac..0d2b3a5180 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 e1083e5fbe..34981560d2 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 b4e179c4a9..d23702d799 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 aa5bec98d5..cf723df9b9 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 66eb692bd5..aca3a3b024 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 a966ba67ad..67137214c1 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, @@ -365,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 14121d3dd9..019d13950a 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 757ba6771e..24a6586ae2 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 6bdd5910f6..b82aaa7c21 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) { diff --git a/esphome/components/logger/logger_rp2.cpp b/esphome/components/logger/logger_rp2.cpp index b949d7ee16..518a1d7b1d 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 396728ceaf..95cc5d4a14 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 00f67422f3..4bd979398c 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/lps22/__init__.py b/esphome/components/lps22/__init__.py index e69de29bb2..b9936d7ab1 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 020add695c..57f801c332 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 b1044276a1..311058249e 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 efdd241578..b02a610c0a 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 e69de29bb2..3bf5ae2563 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 dd78b20f2c..d9699afe5a 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 dd06cfffea..450e38be0f 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 afdc271167..91fca7fa9b 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 dd06cfffea..3b6fa0d268 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 0d43aac20e..2d3b3755ad 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 e6527bbc9b..30480eacc7 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 961fa4079d..d8a96de639 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/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 466d400023..e35cc0c48b 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 0d8d99ab21..b23e77df22 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 cf766b96c8..924c17fb54 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 0bb5715439..6b645da3aa 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 ba32288128..fedc79383d 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 6404bcf64c..96b9782e81 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 e132c54daa..65b5816e46 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 f466fba77e..06f9b90af9 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 f9b744daf0..8e9b21fe43 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"] diff --git a/esphome/components/matrix_keypad/matrix_keypad.cpp b/esphome/components/matrix_keypad/matrix_keypad.cpp index 97641ac2d3..e0f19ca165 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_) { diff --git a/esphome/components/max17043/__init__.py b/esphome/components/max17043/__init__.py index 1db25ccdd6..ba609881f0 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 8776bb5558..a15658512b 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 e69de29bb2..b8a76cd500 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 ac6b3cecb4..4bef41761c 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 e69de29bb2..3fde339873 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 b5bad8ef74..bdb47216da 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 e69de29bb2..42068dc04f 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 e5a6fca8fb..94899b05ab 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 e69de29bb2..a7d755f6a6 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 731f584056..b616b9032e 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 e69de29bb2..d00df9770d 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 8734405508..2fc8dbb73d 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 557f497e1a..77e82fdec7 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 ccb14496aa..7060a8c76b 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 c53a429d20..e3c9c1e827 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 e69de29bb2..2c1a783395 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 bec62ea005..d755dd1a22 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 e69de29bb2..33d7ad3f80 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 26c65aa5d3..178a6fa23a 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 9b0ea14b57..a3fc6ca774 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 68ef7c3135..420eb7da7c 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 3d1480a6f7..9f1e9c9f96 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 6be5f4c951..3d2eb618d6 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 5f4b7276d8..7dddf4b042 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 126ece3e7b..13d930fd47 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 474d75f6ff..6733f19456 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 173d117457..9cad032447 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 ffc51b8146..95a396865b 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 e7f582f787..341951f119 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 d693a64ce8..9745b74bc0 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 652931e9a9..534327f401 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 a3c12165f0..d283d57bd0 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 98fd2f6249..0cda5dac4a 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 1b93d16ff3..6160987cef 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 870c6c165f..7ae17b1c13 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 755d86e4ea..f7449b0ab9 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 b8032fcec8..8ad2f7521e 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 1c45b0f4af..7414677175 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 e69de29bb2..5b0e72b758 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 f8c5e9f068..8a1973d022 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 6d1bd5a970..f29050f9fe 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 e65e249f52..2ee6ce5b9b 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 2647ed199e..3a119f7d2a 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 5757bdbfa9..7829a07d80 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 ef8b568453..dddc043f0e 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 153c37d160..188d7a1dfc 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 677bb78c35..8e5a7f134e 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 1b794ba966..d38883c342 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 60cece67d7..d5465098f2 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 cc53f9de7f..b6e8c24702 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 d38eed4d09..6c1ab99e29 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 e69de29bb2..d63ffaf7c3 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 8e94623de3..faa377b8de 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 f48bdde681..7156b4aa7f 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 1b15ca0510..7259a0a89e 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 e69de29bb2..274ca6fa01 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 e2b0fa5575..8bbde59006 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 e69de29bb2..3d85a4d937 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 0e9b472be2..70e7cb176d 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 e69de29bb2..7a167ebf06 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 10e26ed709..47df37f456 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 6a928d1682..0928766429 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 0fb24fdf1d..e49a3897fe 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 4f236153c2..4397c101fc 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 48df61326e..1df5b9217e 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 d99e7877bf..d012773d8a 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 6c3eef912e..423832dd2f 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 c9dab7e4d2..348ea01428 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 e69de29bb2..1b75339b45 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 707d952f83..5be6da88ef 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 cf239be696..9ad19f66d5 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; diff --git a/esphome/components/micro_wake_word/model_data.cpp b/esphome/components/micro_wake_word/model_data.cpp index c235e290ec..ace0d9770a 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 72984f04fb..7cbd11711a 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 13c0e1f117..159dee2d20 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 c2e3ef8640..86b340d5e4 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 02a6de1c84..6ce2d51d7f 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 8d528145cd..f53c80fbb8 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 d01cc57254..ba74e68eff 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 50a0d34b3a..28f755389f 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 c604f64d77..6fa14cdd8b 100644 --- a/esphome/components/microphone/__init__.py +++ b/esphome/components/microphone/__init__.py @@ -21,6 +21,7 @@ from esphome.types import ConfigType AUTO_LOAD = ["audio"] CODEOWNERS = ["@jesserockz", "@kahrendt"] +DOMAIN = "microphone" IS_PLATFORM_COMPONENT = True diff --git a/esphome/components/mics_4514/__init__.py b/esphome/components/mics_4514/__init__.py index e69de29bb2..9b985504b0 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 14a73bc15f..957cdf90ef 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 e69de29bb2..d9bb8492b3 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 e69de29bb2..dbff1a1186 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 e69de29bb2..01f3c67f7c 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 de25c4652c..55f84e058f 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 526afa914b..b2351e23a4 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" diff --git a/esphome/components/mipi_dsi/__init__.py b/esphome/components/mipi_dsi/__init__.py index 84dd66e2f4..ae17e00bdc 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/models/m5stack.py b/esphome/components/mipi_dsi/models/m5stack.py index 5b07229ec7..4e847ef6fd 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/mitsubishi/__init__.py b/esphome/components/mitsubishi/__init__.py index e69de29bb2..05ae0f0543 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 9cb355fe9c..dabd62b8f1 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 3d30d1a25f..c6c7b57c5f 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 53b8c21de6..70c5d479a3 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 e2a6ee05af..e3222ecf53 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 e69de29bb2..5ea96da4d2 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 41b7123269..dbe0799163 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 { diff --git a/esphome/components/mk2pvrouter/__init__.py b/esphome/components/mk2pvrouter/__init__.py index d00b4ce8d0..7df1f7104f 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 9201596af2..61ecb23743 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 f8aa1003bc..eecaa1a1d4 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 5ea07307e4..92e2ea715c 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 4aea2ef7fc..66e6ba0a3b 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 fc92f02120..800d120558 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 2288e8ff84..e47b7d8c30 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 e69de29bb2..e1db3524a4 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 508b3743d1..a67d770cc3 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 cc88f26231..b7008ff157 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 15e715e675..b7d16b30ed 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 c8db8c4300..5e5287db5f 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 a99df36a70..f7840aafbe 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 cad2781cc1..4e4d7fe160 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"] diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 5f237be8dd..80065546cf 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 d80e6c86ad..19edc98938 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 66ddcd7722..5294f20f2d 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 b0c927cf84..679b8dc714 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 b8d06d3d5a..6c90bfa464 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 223aa12bec..f33fc36b0a 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 ad29015d32..9fb40f9005 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 a2f15d54f6..793aa8e9ea 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 2035f2220a..98037c5237 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 855a7b28c3..fe2b276231 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 31b3fb3e55..74efd41e0f 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 0de40a4cc1..bff23e364e 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 65bf4ef2f4..763bbf872d 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 e69de29bb2..4f572e8557 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 ec7deb3067..968447dadf 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 0bef1eb6d4..24e1667a4b 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 c57f60f521..86bf451227 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 fe3178d3aa..ba93c70d15 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 88e344464f..be8f3d329b 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 d70306c97d..f63f2bc190 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/binary_sensor/motion_binary_sensor.cpp b/esphome/components/motion/binary_sensor/motion_binary_sensor.cpp index f663b41adb..1de535a0ff 100644 --- a/esphome/components/motion/binary_sensor/motion_binary_sensor.cpp +++ b/esphome/components/motion/binary_sensor/motion_binary_sensor.cpp @@ -6,7 +6,7 @@ namespace esphome::motion { -static const char *const TAG = "motion.binary_sensor"; +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 diff --git a/esphome/components/motion/event/motion_event.cpp b/esphome/components/motion/event/motion_event.cpp index 5568c953fd..9ad2668fd4 100644 --- a/esphome/components/motion/event/motion_event.cpp +++ b/esphome/components/motion/event/motion_event.cpp @@ -5,7 +5,7 @@ namespace esphome::motion { -static const char *const TAG = "motion.event"; +ESPHOME_LOG_TAG(TAG, "motion.event"); MotionEvent::MotionEvent(MotionComponent *parent) : parent_(parent) {} diff --git a/esphome/components/motion/motion_component.cpp b/esphome/components/motion/motion_component.cpp index 226dc94e63..3171bef4ec 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/mpl3115a2/__init__.py b/esphome/components/mpl3115a2/__init__.py index e69de29bb2..08956d32f6 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 238e37aff0..db89271625 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 da56b4ff4b..1764ff9c05 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 6b183233d1..26cdcae613 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 e69de29bb2..ee02560478 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 8784e8caf8..cf393fd4d1 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 933f71ccd7..af9cdd2f27 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 b8cbd4635a..514ff0dd0e 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 9418614346..b21dc2e2f0 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 64521f5cf3..42171fcfb1 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 f059360e23..225f839633 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 791e550496..3b49ca33cd 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 ebb29db44f..7a75cca6e8 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 7e0ae7d06e..906913723c 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; diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 2ecab47904..b3d8ab80b1 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. diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 0e6a374f9b..83efc3da9c 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 59a5d02d97..adb5a95828 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 b4ae624404..141b5f12b3 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 ddb4b2d69d..a7da9a1b22 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 c422bb3058..b5f1cf8fb8 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 1492abd011..8275580938 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 93ff6971b3..4266722ca8 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 3a8658a55f..daf2a511dc 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; @@ -121,7 +121,7 @@ void MQTTFanComponent::setup() { }); } - 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 a8b52a3839..e1affae8a2 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 369c1c6668..4f81045fdc 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; @@ -29,7 +29,7 @@ void MQTTLockComponent::setup() { } }); 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 b0bac8b3d7..3b2bebf291 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 25fd813496..42c0ae002e 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 a601a9bcc8..bdaaa47e8f 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 70cd03a4eb..17ba86a918 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_; @@ -34,7 +34,7 @@ void MQTTSwitchComponent::setup() { } }); 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 200e420c9c..dd43a8e2cb 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 3acd71b50d..d105aaac99 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 be391ce88c..889ee2bf34 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 c01fb9e52e..eac45d30b7 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; @@ -24,7 +24,7 @@ void MQTTUpdateComponent::setup() { } }); - 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 b155a4c897..b41a482de9 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 b17f321372..ee58f82fb6 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 afb725feb6..a79216e04d 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 470e08d59a..55992f42f9 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 e69de29bb2..925c57d57b 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 9b7dcbe653..8f66d704fe 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 e1cd49ec7b..9fc73f80a9 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 b9cbdc749b..2e20c4e0ea 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 0beece6710..75260a1b0a 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 ecde0cb117..78f2b75c9c 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 e43454d6ed..6fcb09a347 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 0072f7196e..679600eebc 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 e69de29bb2..d86eed44b8 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 2092452087..ad1a569e0f 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 e69de29bb2..0294a2a735 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 decee27bcf..1c08202d41 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 7e05983733..fad7a385fd 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 efb6c88d28..c1ab86427e 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 08e7c58ef1..75e05e2018 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 73a56e0a42..3a70be21ca 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 625c915e73..b9194b3f31 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 dd3a97065c..cac1d60594 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 a49e7f18d6..f106cdf18f 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 944fa1db47..c755a0e9c5 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 c4dc74b5d3..cabd0761c2 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 ca657522f9..bb5206e8d8 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 0018cff005..f363666dbf 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 45e4691423..b2ae436684 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 6afc2eca09..a837edead5 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 995255d1e4..3595f37016 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 e597b9192d..552dfe99d3 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 8b9e7023ed..0e8e7ff6ef 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 8ad687daf8..f1e81dcd96 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 e2c6d21a34..5d7ebbba48 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 a9fc1c2b8c..03703bb421 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 82f4591bdf..412cc385c6 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 c43210517d..0d2d2a2076 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 ceceff9df9..a7c86b064b 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 ac8d2641fe..a9fb3935f1 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 d9880fe6f9..1769c11018 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 118bfb234f..5e70300f21 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 bd1afa198d..e0b7ca7c37 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 e69de29bb2..190848e54d 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 207acc9d99..2f81bfe3ba 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 86d485777c..7cd0c6cdc1 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, @@ -86,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__) @@ -125,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) @@ -380,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] diff --git a/esphome/components/nrf52/dfu.cpp b/esphome/components/nrf52/dfu.cpp index 3d9219e96f..a1d574a034 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/ntc/__init__.py b/esphome/components/ntc/__init__.py index e69de29bb2..6b126fe8d0 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 49a42e858e..d2b095a3d7 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 8edbdecee5..5822c95c7e 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 a3d49a6ff6..1bca604659 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 ca5aab6469..1d5533c423 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 e300ca72de..a928eaf3fa 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 1e4239ceca..e9559e9177 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 227202622a..e90c349f4f 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 8d35aaa34e..69368a990a 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 aeab821f6a..e950472072 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 4dbbe11792..e3abb95bb7 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 b62e4f47d4..0a4880cf73 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 2ec6365aa1..c4ea9e5533 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 a21849d618..4497d5cad1 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 85632d0bf8..439ec03245 100644 --- a/esphome/components/opentherm/__init__.py +++ b/esphome/components/opentherm/__init__.py @@ -11,6 +11,7 @@ from esphome.core import CORE from . import const, generate, schema, validate CODEOWNERS = ["@olegtarasov"] +DOMAIN = "opentherm" MULTI_CONF = True CONF_IN_PIN = "in_pin" diff --git a/esphome/components/opentherm/hub.cpp b/esphome/components/opentherm/hub.cpp index f8b515fa05..c253d18671 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 d78c6eb38a..f2d8d76f6f 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 3343871cd0..b8b639167d 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 9b87cd8d12..adbb1c40c1 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 2fc99e793e..6bf06f825e 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 ac14efc8e6..25c7a78beb 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 7d98522cd2..4986850cd4 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 526fc961f2..f3860b2cc8 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 0f474fc040..c252034c33 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 e69de29bb2..245d84a104 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 9beb52d64b..d17dc9a9b6 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 fc61ad81b2..44eb546850 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 e69de29bb2..efd544b1e2 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 ce5fbdfd82..0101368ceb 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 6031a68489..f7e03a1699 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 231c4d2dd2..02a5c5750f 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 48725b1265..74f44c0b3b 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 2a6a9e08b1..5be55a5200 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 f33f37bbeb..2316aad5cf 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 89e3f99e1e..456fbb5863 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 5a83d92689..a59d010e4c 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 d2b1196de6..f9ce1b49e1 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 2192a79441..59ffa9edb0 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 5833743586..17ffc39d7f 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 e2b029f368..8057d0f9f1 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 ee9d918542..2f90f602f0 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 a21cbc0f6b..a7bbbab5e6 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 703af8281d..9498d4b4da 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 998e1be5fc..f2dc43654a 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 e69de29bb2..7d911eb405 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 2755f82294..6f2645b365 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 47b9118d9a..69d8935977 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 4c19feab6e..f805a8da54 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 5272df2b55..cba5656e88 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 c3ea6a3c0c..f367d14067 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 817cfd1c30..16103aee44 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 533b3391b1..fe417ed65d 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 e69de29bb2..ff11812a30 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 c80283ffc9..953c4ac19b 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 e69de29bb2..577cea5e1e 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 6ec61770f2..8ce76e9781 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 e69de29bb2..624ec1be8c 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 93c0f2bdf2..90ea4b3fd1 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 9f8c7fabd2..6c9ed3cd98 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 2e054b0683..727e9a2026 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 81e00ca74b..f79d1eadfc 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 4f6417f6c0..0ca851357f 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 1aef130457..104b2cb535 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 11a5dd62bb..cd901cb448 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 e8e9530dba..9bc10872d2 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 71a87b6ae5..b7b1c5c7af 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 a7ae631956..8b9ce7bbee 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 1f0d45b084..d33a7f7544 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 afc77efde3..1fbb42a0b1 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 b404409145..54337b0b13 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 1af753fce3..21e1a4ffcb 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 bd5733fe74..ade93b8735 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 1eedfed0fd..266df24630 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 b1de57df8f..a2c5172bd1 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 aa035be347..7e9b0bd89b 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 e69de29bb2..e79bb3f2a4 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 d4c6824713..ebd508b535 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 3716dd7b5e..bda634dc03 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 54a98bf3ad..784a555e02 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 e69de29bb2..e66395e3de 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 f20b93fff8..6885795e8a 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 e69de29bb2..1c60a105dd 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 f8d890ac9e..14b1f60df5 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 b04c30a005..64229140cb 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 94c0c30766..7493f5d97f 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 c9118834c5..b8efeb213c 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 89392105a2..8fd92c9031 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 9f937b3e28..878e07825d 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 90c8523722..afb60bd843 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 16b877b1f1..bd2a9373d7 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 7f00e18d4c..1687edd511 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 734f794180..0e24425f76 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 73d65e4330..e86153be86 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 14299ee310..4fbe07e2e3 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 25bb7a0c24..16bbac38e4 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 d15c0b431e..c0dfb4c2e4 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 58d9daa311..a87bd7d32e 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 4cde36e9e7..f1bd6122ff 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 185ff0ce09..7460f7cb6f 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 d711c3e48f..f9add96bdf 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 d559c6c251..c9da1f4ad2 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 7705dfa6f6..32c1008f72 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 85a6c1fb7d..2914b11e48 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 8472d4192c..3b40eb1141 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 6a24acd843..469700e01e 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 c1c927634d..e1e805d108 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 53874e18de..68b86b6257 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 b68118b352..4d90642b5f 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 f094f6e2e9..8fa1b92c25 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 eaf77af32e..dc6b368ea8 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 abd050ec3b..142cfb32dc 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 3624fb2442..8bf0163fa7 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 02c089bfed..0089bdcbb7 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 ab680c9695..55b6873d05 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 e69de29bb2..9efceae6de 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 13bf3baf83..b104016fb8 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 e69de29bb2..7c4fef139c 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 d6959d1a96..c2d4c648ce 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 e69de29bb2..97cf2e1c39 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 5209ed5352..53896cbacd 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 e69de29bb2..56767de804 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 64b8974901..995c4aff50 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 9141d12b16..0d7f440f15 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 242a613a6c..f9eef4c809 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 932b71ba55..0898b92486 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 e2def28be5..dc236f47a5 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 a7c9db1599..1beb04064e 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 e69de29bb2..f8d5b7a0c1 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 a28b448340..7809e803c9 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 e69de29bb2..45ecb06188 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 f9f97606a8..41524eebd2 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 e69de29bb2..600881c324 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 c0d163c1de..e291844d78 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 e69de29bb2..81e2bf067d 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 eb9a355806..76b9c2b2e0 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 e69de29bb2..fb646a60c3 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 ba6a71f97d..dcfa46abc5 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 67838dbc3c..6c871dfc4e 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 2fd457d290..4f35d5bf36 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 09bcf51589..86498d192a 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 547991f75e..ea3bf19eb4 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 8b773dcdf5..222041837b 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 edb78b98e1..78d20d9bfe 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 00773b516a..0000000000 --- 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 aebbe2fcc8..abf64f675a 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 e69de29bb2..2232f1e670 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 eb338db772..fffc3514d0 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 f303a14297..cd680450d5 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 61e7feb9af..231d6c3f89 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 8782c255f0..d084a6c016 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 0ca9f64326..e2af976399 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 9ff279cab9..bef5975785 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 0740f6967b..a2076ef945 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 de5bd3d8d5..84b89f377b 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 e9e8dd7b73..69cc713813 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 7c1b6ae314..6549ba9846 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 c3fd368bbd..3fa9ef8a7b 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 dbc86ff8e7..7fc0416030 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 f2820bbaee..6f517458bb 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 b63ad1cfdc..2d9bbf5b52 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 4fff41e4f6..96bf4ffd74 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 18328def9f..7a2af571c7 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 d3803bd3f7..86eb2cd738 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 00c91cbc47..2bdbde9174 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 5d615f0edf..a880169fb2 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, @@ -2060,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 @@ -2116,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)) @@ -2215,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 @@ -2358,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 2000148ca8..638f33687a 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 69f91ba90e..eb05f7d21e 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 98a5501155..57706a7e4f 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 9291501ea3..e79e05d839 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 35261dc00a..2537ba5560 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 11d6d975d1..468b11f695 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 eafa98ebcc..ac83c37227 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 53e1d59f13..e2e54afadb 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 9a6420afd5..6877d661bd 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 6dc7926b4a..5291d69345 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 2261bd04e9..598f1a476e 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 942b40d26f..f6a7943ebd 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 1d67be86b8..affafa694c 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 8801f1049a..3c9ba7b61f 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 e1fd60411f..90af88d2c7 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 9e706b9fe6..95683ba8cc 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 86a47e757d..6fbbf6ee47 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 8cc9de94b8..caa950218e 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 e450659b42..07f4b5a1dc 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 f417827b38..5b47f7e438 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 6889c5d9b4..57aeda2e90 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 85bbef1cb1..19521e8f6d 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 380cfaecb2..c2a487080b 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 a37fb93f4f..958127ba66 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 e639248b4e..3edb50141c 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 e691a69598..2d0d2cf757 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 0000000000..6290e5bc50 --- /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 0000000000..c6f0f1dd97 --- /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 b026213080..c2fc35d3ec 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 f4d6aa4026..20603848f1 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 dc128d4622..e47ba89291 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 02c2916849..134204aa2a 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 fd136a4e6d..cec747eb48 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 ddd9e53258..067b8b812e 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 bdd6615d43..a469cf2d45 100644 --- a/esphome/components/remote_base/rc_switch_protocol.cpp +++ b/esphome/components/remote_base/rc_switch_protocol.cpp @@ -6,7 +6,7 @@ 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) { // entry 0 is the all-zero protocol, so an out of range index from a lambda transmits nothing diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index 5d1bba16b6..7205fb6221 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 67e5799bca..36d2a44c50 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 8053792a60..bd40a7a544 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 ded8c71aa3..c59ccf0106 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 7190e97403..7fce46313b 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 0abb7fc0e0..00d5aee9c8 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 8b39abf9ad..962cf12ff6 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 077b4340fa..addcd6f9cb 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 042efcbc36..c94fce87be 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 866e108131..481acaa06c 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 b3e4649096..58c72a8bb8 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 64392aa7ee..2b76ab8989 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 68eac315d5..7553ed767b 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 5e82213a48..34eb67b3c5 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 99e1ce9504..fad273a118 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 822389ccf9..dced3720b4 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 fad91f593f..55f18277ce 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 6d27be8d47..22a63ca4fd 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 6db9faac36..62be203ee3 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 b9b070e015..e41b004b86 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 0dcd523380..a50f6adea8 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 16d2d5dc9e..99b00ec094 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 e69de29bb2..5bba63cc1b 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 7522d026a4..d6bc324fa4 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 f70ffa9520..6337520ebd 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 d6404315ea..cffa47896c 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 96a4fc40f5..50102366fc 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 fed8365e9e..e967335c18 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 a4a4da5d8c..86daaf4b08 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 e69de29bb2..11e05410be 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 e69de29bb2..b8a02914c5 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 e69de29bb2..0635bc36c3 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 e69de29bb2..ce95d11431 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 b53476dcac..d053a7e409 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 dfcc53ebb2..7197a07348 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 e69de29bb2..5f448e531c 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 0734ca87d3..4c43830888 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 e69de29bb2..49a5eec426 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 dd2428e4df..5fdc1ac716 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 23346ca6f4..76dd240f7a 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 9bcdc8bee4..0741cc24e3 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 b95da9bcdb..93bdba1fca 100644 --- a/esphome/components/rp2/gpio.cpp +++ b/esphome/components/rp2/gpio.cpp @@ -6,7 +6,7 @@ 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 = diff --git a/esphome/components/rp2/hal.h b/esphome/components/rp2/hal.h index ec46937bab..2a538f1571 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 d1e0bc555f..dfe19f4bf4 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 d2a08e9fc0..ad79551792 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 80e8bf9415..fca0c45d6f 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 4c9aa2d155..10b24efa09 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 1f4bea9ecd..8ea9880c7c 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 e69de29bb2..3abe0b8c83 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 270cc33551..8324da3dcf 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 12857a556f..4a3d3f788b 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 a0a04b981d..192e19455e 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 915ab73b87..1b2236ae8e 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 8628f0e063..20629fd730 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 88928b2bce..d3d5150819 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 4f4d17b441..47b8f26e3f 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 a5f8567c9d..fb2bf63a64 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 e27fcadbee..fcf5cb770c 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 204d6cc14b..9260860f40 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 f2c4f5c8cd..7056ce35aa 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 85ec945259..74ced63a4e 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 106f25bbe1..ae76347118 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 b0fabb5233..dacaace3fd 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 5c14d39807..6c10194574 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 62f5f46cb4..2d1a6ac910 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 ac4bafbb1a..ccfa8ecf10 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 19753b3c9e..c781e282fe 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 e69de29bb2..2a0e78efc8 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 1536befb1b..3dc4a3a223 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 e69de29bb2..e0b7464656 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 fee2c83a5f..502f6f265d 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 e69de29bb2..3b5225c024 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 1b5b71f0e0..63153c5db3 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 5d03ed70a8..8d77ff363f 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 982ecf8402..e9779953f0 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 8fd5911ab5..3099df5860 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 b4b9735757..c60c59dbfc 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 e69de29bb2..71e20c7e13 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 a780297756..5802503627 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 e69de29bb2..338bfdf59b 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 d9a2439bb9..8249a6feb1 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 2c79e9ed0a..48a208b7f7 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 bfe96e1b23..7174a9a0b4 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 872d831850..a8a2391a3c 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 e69de29bb2..b13d6e18b6 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 c1b359cc97..5389325cc2 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 e69de29bb2..50ffb263bf 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 7fcd47a265..98b5d9940a 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 e69de29bb2..386bc61be5 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 dfc7857266..3d46f0613e 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 56630f18f4..4ec01e46c6 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 a0dc7ae4cb..31c8e89af3 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 6bf8657af9..7a85567510 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 cbf0415f6c..c5d1292458 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 159a1ece9c..cc22741583 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 2d1cd0fbb4..332c59061b 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 e69de29bb2..469119b8ac 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 3ad1f8b87c..a11cc6a008 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 fa266fc81f..07f86c1330 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") diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index 05a0ee1ed9..4d77c6d6f6 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 9d2fb725f3..167e6769a4 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/sen0321/__init__.py b/esphome/components/sen0321/__init__.py index 458ffa67f9..905618bfba 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 e074934d0f..564e05e039 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 e69de29bb2..c2802220f6 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 3f6212e7f2..29175d845c 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 e69de29bb2..e58c474fae 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 f8df89ee33..f192a40288 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 e69de29bb2..abd5e7e50c 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 ed6cb24c52..91762e0f07 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 42dae464ed..c557a7960d 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 e26e8204aa..a5336d5e05 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 558a292d5b..a93c2eb443 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 2f6f4e8a4d..43dd4ed66b 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 59ead1bb53..8c82022f1e 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 ff76473189..bc07254efe 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 743d0bb62c..31c454fb71 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 c3fb1fe1cb..5e5cf18799 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 1c5cb625bf..f0800dbcb1 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 15d358d50f..8b5eaeeed2 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 ea09dafe68..cd8d0d0afa 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 5b29fff55f..77bb835598 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 1f896640bf..63d30d0c03 100644 --- a/esphome/components/sendspin/switch/__init__.py +++ b/esphome/components/sendspin/switch/__init__.py @@ -1,35 +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 CONF_TYPE, 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( cv.typed_schema( { - CONF_ENABLED: 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), + 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 0bf029d4c7..51c151980a 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 253d952b22..4b04c95857 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 87f6c9b936..24aa2d5734 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 554e01cf88..629396a681 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 d38f360d94..cc25ab93c7 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 e69de29bb2..5973c85b6a 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 f5017cff75..858510f427 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 5d9424d1e7..e1fc793d6d 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 3325c27772..19172e68d2 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 f3293d4ed5..2b404442a4 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 9b9d7c033a..732db5b0b4 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 bc086e3805..510ee94c6a 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 bee5d7c6d3..df73e2c481 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 b6e780fabd..a876728050 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 729ec40256..d512362fa0 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 df775d4f63..1a406bc089 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 74acaa3496..44ec294765 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 8d5344cf44..a415fdb8f7 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 28b665906f..d608a07f79 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 960806e98b..efa3d8dc61 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 79568d458a..077f0d2e9f 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 0d6bee3f9a..31a25a4f38 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 e69de29bb2..4754f35d59 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 fd007c92cc..72827ad51b 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 e69de29bb2..f18cc0d930 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 e69de29bb2..f8030fdae0 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 0cf5c31483..854dddba05 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 5db0e77b76..5856d230a2 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 accefbbc34..72586d1637 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 b69e417591..eedf7ff7ce 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 beb015851e..c33876e494 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 e69de29bb2..f58b3fb150 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 eba6be65d5..672d119aed 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 e69de29bb2..2e9124bdab 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 4a3df5a91f..45de59fe0d 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 e69de29bb2..30c06bdaec 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 9ec0a2cdb7..68f2c4ea21 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 480a6f3e31..949ea69bb9 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 6394c6c14e..7a1380469b 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 a44a572aa1..6e0a70ff82 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 3356e61bb2..2af211995b 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 6cc131c68f..37412c5d9b 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 c472e01f83..e20c091b75 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 13b9888e05..e75e5e95dd 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 e69de29bb2..c46da34e38 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 e695ab9540..f41455bc80 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 2b555a59df..05ba98562d 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 45de3c457d..d01ec5dbdb 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 3fb9ac3f06..ae3c54ab7c 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 59e9f1b712..647a40dc47 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 537db5bbe4..c42ce4c8b3 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 0086a63878..c8a4287b65 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 95037fd275..803cd54be5 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 e981bd0f71..3bd302047b 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 145c5a0117..a9fdfcef39 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 93f1096800..6e0d7b3588 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 e69de29bb2..da0a0637ed 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 882959a454..0660e0f20e 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 07ca5bf444..35ca231f02 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 047a13e88d..9861edcf8f 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 bacfbcccef..a4bc4373d7 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 d0965e3dca..9bd31923ec 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 44ed591400..572df68d99 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 2889a9fb4d..7785bbca2d 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 4f21312fec..af566cebda 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 fde789e90c..318390e8e2 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 367b65176b..78ed703ed9 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 710e51ad12..4691ffcf6d 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 6297e3e18f..6618d9ae74 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 096ec48eb3..58406f17ff 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 e69de29bb2..481183e020 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 f8d48b4098..de951483bb 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 9d849f9fe9..9365339473 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 3d2cd8ef50..cc5f672ad5 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") diff --git a/esphome/components/socket/ipv4_resolve.cpp b/esphome/components/socket/ipv4_resolve.cpp index d2316cd064..d5da8745b9 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 b411445b98..2b21291c30 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/sonoff_d1/__init__.py b/esphome/components/sonoff_d1/__init__.py index 18b4d30d18..40a6c103db 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 3e5af2b51f..02282fef9c 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 e69de29bb2..93f0f4b2e3 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 99ab7932d6..c5eee3665c 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; diff --git a/esphome/components/spa06_base/__init__.py b/esphome/components/spa06_base/__init__.py index e084020a14..c08d92f9d8 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 d3de5168e4..02defd0773 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 e69de29bb2..928c6c157b 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 4970b0822d..cdb0818d39 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 e69de29bb2..b473921999 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 9f3683d219..08d725d4f8 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 0000000000..67a78e5f28 --- /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 0000000000..94fad87d7d --- /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 0000000000..8817396872 --- /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 0000000000..7ead6a3418 --- /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 d0a743543e..d34cfc32c0 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/audio_pipeline.cpp b/esphome/components/speaker/media_player/audio_pipeline.cpp index f150a48573..a39a1ecbbe 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 diff --git a/esphome/components/speaker/media_player/speaker_media_player.cpp b/esphome/components/speaker/media_player/speaker_media_player.cpp index ab98935bda..8ab4128e92 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_); }); } } diff --git a/esphome/components/speaker_source/__init__.py b/esphome/components/speaker_source/__init__.py index e69de29bb2..ebe19a4ead 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 6a000b8f21..657c26af80 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 b75374863b..9f048ef37d 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 af46ef52e1..322c638d4b 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 dd2dce01c5..d6949cc590 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 dfdc9fa624..343fcd5488 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 ae2d2906ed..8ad868ad2e 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 95b5e4f14b..7b51bfaa69 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 bd2bfe6452..3f64dfc3d1 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 bdf5978bc7..f89b9f624f 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 850a1f6e02..2d856e1b10 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 be7472936f..e66c0c6efa 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 63a0f0d919..56b1d326a3 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 00144adacd..fe096514d0 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 cdec158126..eb9e61958f 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 2499a0a591..c51824d883 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 e69de29bb2..4b06a323ab 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 73fa4ef463..3a2ebb14c3 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 26eda3b4ea..9577f9d4f7 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 5f1c6fca8f..ef7fa46e73 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 e69de29bb2..9389c74a44 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 00c864a217..1cdfed9f50 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 e69de29bb2..fc62e74a43 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 0534deeb03..8e2dcebc0b 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 daa0345857..d66ae8d5c9 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 a8a39b9ab0..30ca4109ef 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 e69de29bb2..52b9de59b2 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 79f15508c3..38426b9eaf 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 e48a821825..fbbf329d53 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 b8d8f99999..17175447d9 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 e69de29bb2..218baca080 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 af6a01c7f3..cb21eb09fe 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 462f197696..2c9c0faf3b 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 ae02b836bf..7c8b490f61 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 e69de29bb2..b093087fa1 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 90fbcbd05c..98328c800c 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 e69de29bb2..f95fe10579 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 c6a64227fc..8816aadf6d 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 d1c0b585f8..2345acdf65 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 7755e5a2a0..8d5ebb4dbb 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 e69de29bb2..4e37ffcaba 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 a5118cdfbe..d36f9b75af 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 a516b4d951..f693004d63 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 3df575499d..7ab3a64ea8 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 e69de29bb2..0cb07c8b5e 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 f6d2323e9c..06d95e74a4 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 335bc238be..6e3a763eb4 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 117f975264..5cd986f697 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 51225c5023..8fdc0a7f7a 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 fff3aa7e03..aa83de6a8c 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 dd06cfffea..3c54c70741 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 c99815ecd6..657a60f954 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 dd06cfffea..ed60104857 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 fae8c31381..fa9fb792dd 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 c58ce8a01e..812c6caa8f 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 62dfa6f413..176eb233e1 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/st7789v/__init__.py b/esphome/components/st7789v/__init__.py index 7915cf119c..e67f480912 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/st7920/__init__.py b/esphome/components/st7920/__init__.py index e69de29bb2..a1512e4e57 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 429ed9e57b..19a96aad60 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 58d4be97fa..16df864082 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 2a56551255..12af8345f1 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 e69de29bb2..dee924f6cf 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 2c95be8569..192c03219f 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 700296c0df..8bd98c0ef8 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 341d3bfce5..6eeb9732a4 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 ae37b3fae7..81b3b9e3af 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 e69de29bb2..d0f16805b8 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 efc7a2b8ee..ac7a998e5d 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 017fd95cd8..f5c1a30433 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 54df83782e..dd3040d898 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 e69de29bb2..d7d6491925 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 ff2a7748bf..8b97b2f499 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 a33c0b554b..15a864ef0c 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 2b2559c843..7d66dd0a18 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 d59f39b9c2..93cf588e02 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 73ddefe972..71441e9782 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 d788e19ea4..b3cdacddae 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 d03ff07981..f4c9d830b4 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 21aa4b86e0..15a3fc21be 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 0f5ae27753..61643cf488 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 78e398d086..f89797cc10 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 c34a73a36d..e965b552f4 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 19995fb1ae..c5b93e1dbb 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 2cc8ad782d..179ef2ce35 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 ce47a57050..c8b920d6cb 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 411de12341..0dbf3cb42c 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 5e992cc731..70dc818495 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 376676ce85..6c1281c89b 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 b3dfe6590a..536bb93c48 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 9f95c61844..ea7fe38ba3 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 2ed1614681..8d409f0b01 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 52d5631791..77d1ea2880 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 cd81f08914..16c3d1dd21 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 070a6eeb96..27031d98b2 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 e4df7b443c..f96479498d 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") @@ -133,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 528de1fde5..5bf47e97bd 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 ee8606db96..169d35331f 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"); diff --git a/esphome/components/sx1509/sx1509_gpio_pin.cpp b/esphome/components/sx1509/sx1509_gpio_pin.cpp index 28ef1c5830..41f967b667 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 cb9d64aee7..90bd9f4691 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 1f1648cfa7..3f381002cb 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 e25567c783..b48192ddd2 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 0695b43d41..49ac214d40 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 08a4677180..5d9890eca5 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 3a2f6717e9..4c537b75cb 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 e69de29bb2..bbbca72b13 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 982cc181b7..d399752889 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 81e00ca74b..f1fed4b698 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 191dbf823c..d69bd4dca1 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 0931d7ba1f..56700aeafd 100644 --- a/esphome/components/tas58xx/__init__.py +++ b/esphome/components/tas58xx/__init__.py @@ -1,6 +1,7 @@ from esphome.config_helpers import filter_source_files_from_defines CODEOWNERS = ["@mrtoy-me", "@remcom"] +DOMAIN = "tas58xx" FILTER_SOURCE_FILES = filter_source_files_from_defines( { diff --git a/esphome/components/tas58xx/model_tas5805m.cpp b/esphome/components/tas58xx/model_tas5805m.cpp index de0a0c5d75..09b369435e 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"); } diff --git a/esphome/components/tas58xx/model_tas5825m.cpp b/esphome/components/tas58xx/model_tas5825m.cpp index 3aa74c675e..cf5861cb4a 100644 --- a/esphome/components/tas58xx/model_tas5825m.cpp +++ b/esphome/components/tas58xx/model_tas5825m.cpp @@ -4,13 +4,11 @@ namespace esphome::tas58xx { -// Remainder of the startup sequence, run after the reset. Register 0x00 selects the page. -// All writes except starting and last three register writes are exactly the startup sequence from TI PurePath Console. -// Registers 0x46, 0x7D, 0x7E, page 1 register 0x51 and Page 2 registers 0x1D, 0x19 are not documented in the datasheet. - -// clang-format off +// 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}, // Deep Sleep + {0x03, 0x00}, // DEVICE_CTRL_2: deep sleep, the state the PPC sequence starts from {0x7D, 0x11}, {0x7E, 0xFF}, {0x00, 0x01}, // Page 1 @@ -20,15 +18,16 @@ static const uint8_t STARTUP_SEQUENCE[][2] PROGMEM = { {0x19, 0x80}, {0x00, 0x00}, // Page 0 {0x46, 0x11}, - {0x02, 0x00}, // DEVICE_CTRL_1 - BD MODE, Damp to PBL MODE - {0x53, 0x01}, // ANA_CTRL PWM Phase Control - in phase - {0x54, 0x00}, // AGAIN 0dB - {0x03, 0x02}, // Hi-Z - {0x61, 0x0B}, // GPIO0_SEL - GPIO0 as FAULTZ output - {0x60, 0x01}, // GPIO0_OE - Enable GPIO0 as output - {0x77, 0x07}, // CBC_CONTROL enabling CBC function for warnings and faults + // 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 }; -// clang-format on static const LogString *model_name() { return LOG_STR("TAS5825M"); } diff --git a/esphome/components/tas58xx/tas58xx.cpp b/esphome/components/tas58xx/tas58xx.cpp index d042d42e24..3addbfda00 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 diff --git a/esphome/components/tc74/__init__.py b/esphome/components/tc74/__init__.py index c1407c7cad..1021a425b8 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 bc522d1b74..98428cd122 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 381b84cad6..a1945e97c5 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 3fc91a3bbb..f10578c97c 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 3033cc65e0..a039872e16 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 b210c082dd..7a59c403f3 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 e69de29bb2..03f3908817 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 cd819e5b16..5e7f0ef29c 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 796a9fc48d..35326d77d5 100644 --- a/esphome/components/tcp_uart/__init__.py +++ b/esphome/components/tcp_uart/__init__.py @@ -24,6 +24,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@Bascht74"] DEPENDENCIES = ["network"] AUTO_LOAD = ["uart", "binary_sensor", "sensor", "socket"] +DOMAIN = "tcp_uart" MULTI_CONF = True CONF_DISCONNECTS = "disconnects" diff --git a/esphome/components/tcp_uart/tcp_uart.cpp b/esphome/components/tcp_uart/tcp_uart.cpp index efc85804e9..3dead1fb65 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; diff --git a/esphome/components/tcs34725/__init__.py b/esphome/components/tcs34725/__init__.py index e69de29bb2..cf99348a86 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 b585392790..8e79ac1c16 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 e69de29bb2..d3790674e2 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 c198ff1081..74906ba5c4 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 67aad11d0f..8caf020212 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 3878a3967b..443d4be8a1 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 17d3d6c099..6b68dacd31 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 7c638d8545..a2646e5bb7 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 e69de29bb2..728db67d54 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 72cf31e0a6..e964814bbb 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 6253af9090..542e78809c 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 59624a5f53..a708c83d09 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 a224ab8459..12ff0b5a4b 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 5888ce5e29..c977da10a9 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 b63121d2db..8ce51aa4ca 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 a7a4d2ccab..a731f037e3 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 1bf057da5b..88a0af10b5 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 c0f5d96c3d..4ae7429e96 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 5b8b308c00..0776a6968a 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 b5efa62ae7..fd676e66a6 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 431be84654..93b02f1245 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 4a293aab85..54e4e49ce9 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 64c2deb281..93eb42708e 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 e68729c2d4..8c46b3a58c 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 313a163e38..cc12d7bbec 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 729db37053..456e93d13d 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 af134e6ed4..d892afade4 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 89a15b6081..38de4ea8de 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 4b523def25..4e8a272285 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 5090687639..77e65f0457 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 7718688d68..0a71225de3 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 2f82f19ff5..fbbc09bd2d 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 a1df6286c7..58ab300b90 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 b7692658af..93f0610d7d 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 50504c605e..6574141e9f 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 08870db64a..e9c70f7a2e 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 d4e6b5b9bb..9d18fb9b1a 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 17c606d253..af4c5fd081 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 6ba821b1ab..6914894ba7 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 1466ba8947..e3d7045d8e 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 e69de29bb2..f2f59ccbc2 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 d10a6c33cd..38a58137ae 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 e69de29bb2..7bed41dc56 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 f64673e13f..abfe1b027a 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/automation.cpp b/esphome/components/time/automation.cpp index b91f8b0360..26956703e7 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 diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 4f32560525..5f6b7d150f 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 ce2f453bda..39ec2d9a8d 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 613b190cf3..4be933dd46 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 1b819088ca..586b69d603 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 3fab9de008..14291fb328 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 1af9f66b95..5d719e40ba 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 def337befc..d76845b4f0 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 05e2ef037d..71247dd13e 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 f886118a08..5502679c9d 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 5b9027e61a..26beec280e 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 5818eace67..2c4ec56732 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 2e88d4f366..1b78f353ca 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 68d16e3811..5ca1c83c48 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 e69de29bb2..c8b08abf03 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 a1604fa60e..03eb427e76 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 e69de29bb2..4500a10ff4 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 e32826fa93..8e2020459c 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 743d0af507..f5573a3592 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 1f0692479c..ffb0ead0cd 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 dfe475f7f2..14aa1c8842 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 282b0dcf76..467695b4b4 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 3e32a230f2..8510f18769 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 cb2462858b..d851bd925f 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 ddd04ad11a..81c0e50033 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 681603d113..9b152c1ad3 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 e69de29bb2..e7429bd52e 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 b3e900f5b6..5925b297c2 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 e69de29bb2..89b7cffed4 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 290bc76a52..3943b37150 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 7f3f05a3cd..e26792c1d3 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 5c8d6623b6..fe7b4cf112 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 e69de29bb2..1e5ec1fc27 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 19950bbd15..f9f09a98bc 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 e69de29bb2..d3880a5084 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 7c9dbb604f..c8e6bdd04a 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 c8b918007b..7a23d14069 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 f4ef66ef3e..7072b88d55 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 e69de29bb2..9986b7fd3e 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 4e4d403488..d1d1d66a26 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 63331641c5..c611ad0ebd 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 d147aae88a..f1f9ae9cfd 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 a309d34beb..a8f0ec0dd3 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 ccf6e53d66..ea388703e0 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 018094df73..b850dbc696 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 9e2a4ad114..1b10940570 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 1c7fa3531f..807d736557 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 d32f72fcce..e89b177a3e 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 eaef33d793..7ed06c6d6f 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 2cc60e225c..6957b57e0e 100644 --- a/esphome/components/tuya/__init__.py +++ b/esphome/components/tuya/__init__.py @@ -5,6 +5,7 @@ import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT, CONF_TIME_ID, CONF_TRIGGER_ID DEPENDENCIES = ["uart"] +DOMAIN = "tuya" CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS = "ignore_mcu_update_on_datapoints" diff --git a/esphome/components/tuya/automation.cpp b/esphome/components/tuya/automation.cpp index 5c84f36b83..a7eacf8e9a 100644 --- a/esphome/components/tuya/automation.cpp +++ b/esphome/components/tuya/automation.cpp @@ -2,7 +2,7 @@ #include "automation.h" -static const char *const TAG = "tuya.automation"; +ESPHOME_LOG_TAG(TAG, "tuya.automation"); 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 f93bd31b9d..9d0589305e 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 111d090c3e..e07806326c 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 dd268388d0..b23dde75db 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 0b5fc19038..8e4694baed 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 32cbca9a39..4852e5ff44 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 b0bbfce649..538021bca2 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 057f7f8ea6..7c00c7c0f9 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 c948984786..6b16d93080 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 8d4d183d5b..204ee84170 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 ebb35cead7..2e64fb1dea 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 f9b4fe2453..784aea5c50 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); diff --git a/esphome/components/tuya/water_heater/tuya_water_heater.cpp b/esphome/components/tuya/water_heater/tuya_water_heater.cpp index e1c78530e3..4ece2b0a03 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 e69de29bb2..83dbfc84f6 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 3574bd7c2d..ce4c20ede4 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/button/uart_button.cpp b/esphome/components/uart/button/uart_button.cpp index 809ceaabb0..b55207cb46 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 02c5f2e631..22bcee9c83 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 6b8eae611c..215cdded99 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 642bd19772..6b6aa273f7 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 337fa352c1..20f6385b6a 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 762e56c399..6516174635 100644 --- a/esphome/components/uart/uart_component.cpp +++ b/esphome/components/uart/uart_component.cpp @@ -2,7 +2,7 @@ namespace esphome::uart { -static const char *const TAG = "uart"; +ESPHOME_LOG_TAG(TAG, "uart"); bool UARTComponent::check_read_timeout_(size_t len) { if (this->available() >= len) diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 0f6d7f6577..a32677b3da 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 5d90fc1c8a..41fae283b4 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 diff --git a/esphome/components/uart/uart_component_host.cpp b/esphome/components/uart/uart_component_host.cpp index 63b5631564..9e2f273f47 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 293cb9e786..5a77bf26d9 100644 --- a/esphome/components/uart/uart_component_libretiny.cpp +++ b/esphome/components/uart/uart_component_libretiny.cpp @@ -18,7 +18,7 @@ namespace esphome::uart { -static const char *const TAG = "uart"; +ESPHOME_LOG_TAG(TAG, "uart"); static const char *const UART_TYPE[] = { "hardware", diff --git a/esphome/components/uart/uart_component_rp2.cpp b/esphome/components/uart/uart_component_rp2.cpp index ffb9bc0f2d..07245bc230 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 e0be599d8d..53c6c0406a 100644 --- a/esphome/components/uart/uart_debugger.cpp +++ b/esphome/components/uart/uart_debugger.cpp @@ -8,7 +8,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 953e81d533..11057ac078 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 6e7a6fef32..601fdeb327 100644 --- a/esphome/components/uart_tcp/__init__.py +++ b/esphome/components/uart_tcp/__init__.py @@ -7,6 +7,7 @@ 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, @@ -125,7 +126,7 @@ def _final_validate(config: ConfigType) -> ConfigType: 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", []) + for conf in full_config.get(TCP_UART_DOMAIN, []) ): raise cv.Invalid( f"Port {port} is already the listen port of another uart_tcp " diff --git a/esphome/components/uart_tcp/uart_tcp.cpp b/esphome/components/uart_tcp/uart_tcp.cpp index 4f241f8567..bc75eb04c7 100644 --- a/esphome/components/uart_tcp/uart_tcp.cpp +++ b/esphome/components/uart_tcp/uart_tcp.cpp @@ -9,7 +9,7 @@ namespace esphome::uart_tcp { -static const char *const TAG = "uart_tcp"; +ESPHOME_LOG_TAG(TAG, "uart_tcp"); // Keeps the pacing product in 32 bits up to about 10 Mbaud. static constexpr uint32_t MAX_PACE_SPAN_MS = 4000; diff --git a/esphome/components/udp/__init__.py b/esphome/components/udp/__init__.py index 316d4fc311..81149f056b 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") @@ -184,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 9b3ba7e0dd..a6a40829b1 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 40ce46d74e..2e49b676ce 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 904ed97dc0..3918ddc1fa 100644 --- a/esphome/components/udp/udp_component.cpp +++ b/esphome/components/udp/udp_component.cpp @@ -7,7 +7,7 @@ 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) diff --git a/esphome/components/ufire_ec/__init__.py b/esphome/components/ufire_ec/__init__.py index 08f36c7934..e5461b4ff7 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 72bf837409..1bb1222941 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 08f36c7934..9fb4cb9c65 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 d595b37a83..aa899968e4 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 85ca0eecae..e1fcef107d 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 fbacd84db0..cd4388372c 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 e69de29bb2..6f2260af57 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 a2244eadaa..64db28a530 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 3bca12bffc..cdc67e7813 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 a354b198b4..039d08eaae 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 index f70ffa9520..c5464cbe6c 100644 --- a/esphome/components/unicode/__init__.py +++ b/esphome/components/unicode/__init__.py @@ -1 +1,2 @@ CODEOWNERS = ["@esphome/core"] +DOMAIN = "unicode" diff --git a/esphome/components/update/__init__.py b/esphome/components/update/__init__.py index 1533a893ba..e455a4f768 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 b0d4c01cc9..33c33ccb62 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 ba686dc22a..5dc32259ea 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 0400888511..7b6717c682 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 97e9c27570..9b5f7df87a 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 74974548af..fd6eda73a0 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 e69de29bb2..74eb20c656 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 a13d6cd7fd..04140ba431 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 552e4e5da4..c5dbf742a1 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 e8b8a21562..43c8d1cf75 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 7a56654804..33cde23b94 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 8cd078ab49..e49531c80b 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 454c049da3..f24339b4bf 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 7aa7b46b7b..9ac94802e1 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 4abcc3a449..94532958ed 100644 --- a/esphome/components/usb_host/__init__.py +++ b/esphome/components/usb_host/__init__.py @@ -1,4 +1,7 @@ +from itertools import combinations + import esphome.codegen as cg +from esphome.components.const import CONF_MANUFACTURER from esphome.components.esp32 import ( VARIANT_ESP32H4, VARIANT_ESP32P4, @@ -15,6 +18,8 @@ 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 AUTO_LOAD = ["bytebuffer"] @@ -26,11 +31,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 +62,40 @@ 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 validate_usb_clients(configs: list[ConfigType]) -> list[ConfigType]: + # Two entries overlap when no field they both constrain tells them apart + for first, second in combinations(configs, 2): + for key, wildcard in _FILTER_WILDCARDS.items(): + a = first.get(key) + b = second.get(key) + if wildcard not in (a, b) and a != b: + break + else: + raise cv.Invalid( + f"USB configs overlap: {first[CONF_ID]}, {second[CONF_ID]}" + ) + return configs + + +def _final_validate(config: ConfigType) -> ConfigType: + # Every USB client on the bus, whichever component configured it: any two could + # otherwise open the same device + clients = list(config.get(CONF_DEVICES) or ()) + clients.extend(fv.full_config.get().get("usb_uart") or ()) + validate_usb_clients(clients) + return config + + +FINAL_VALIDATE_SCHEMA = _final_validate def _set_max_packet_size(config: dict) -> dict: @@ -91,6 +139,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 42869fb2a6..aaab8e22a1 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 7bc2b0a16b..1b50aa75ba 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 af58c24d43..2898576b32 100644 --- a/esphome/components/usb_uart/__init__.py +++ b/esphome/components/usb_uart/__init__.py @@ -18,17 +18,27 @@ from esphome.const import ( 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, diff --git a/esphome/components/usb_uart/usb_uart.cpp b/esphome/components/usb_uart/usb_uart.cpp index 3113f695f6..86dfa6d729 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 c99992d878..c6ae0b4eef 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}; }; @@ -182,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) {} @@ -267,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 7df58c7a2a..b000f6dd3f 100644 --- a/esphome/components/valve/__init__.py +++ b/esphome/components/valve/__init__.py @@ -33,6 +33,7 @@ from esphome.cpp_generator import MockObjClass IS_PLATFORM_COMPONENT = True CODEOWNERS = ["@esphome/core"] +DOMAIN = "valve" DEVICE_CLASSES = [ DEVICE_CLASS_EMPTY, diff --git a/esphome/components/valve/valve.cpp b/esphome/components/valve/valve.cpp index d8fb18b1b7..a5ad9817df 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 fd54658912..533acfbbb9 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 ddb6b53068..60822c5283 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 773e1435e2..5c111c2294 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 080567e7f9..6b6a3393ee 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 e69de29bb2..57c95f46ef 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 b3170469b9..3a85669678 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 dd06cfffea..9c8f1442d7 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 6c609f4fe7..236b4bb2a9 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 f70ffa9520..f9c04b2596 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/version_text_sensor.cpp b/esphome/components/version/version_text_sensor.cpp index 15e6b0d088..1be7920a4c 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/vl53l0x/__init__.py b/esphome/components/vl53l0x/__init__.py index e69de29bb2..3363ba9961 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 49eb3d00a1..68cb98b053 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) diff --git a/esphome/components/voice_assistant/__init__.py b/esphome/components/voice_assistant/__init__.py index 14c036de8e..d306947ec1 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 dba9b925d0..039410abdb 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 e60918096e..1dad646322 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 90539e5d3c..ad052dd85e 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 fd94f56b75..52dfe98aad 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 6fb87e45a4..2608ff7c57 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 2063faeb91..61c0baf686 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 6bb2b2f8fe..b5821e55af 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 4b65e907de..e5236a0491 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 b410406a58..4bcfcdfda5 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_io_ch32v003/__init__.py b/esphome/components/waveshare_io_ch32v003/__init__.py index 29a939c523..5e53d30149 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 30458310ce..cef569b905 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 82da7451f9..71cb9ba79f 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 8a58c7e7bb..159390086f 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 74feec7915..c3218e5b13 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -67,6 +67,8 @@ def AUTO_LOAD() -> list[str]: return auto_load +DOMAIN = "web_server" + AUTH_TYPE_BASIC = "basic" AUTH_TYPE_DIGEST = "digest" @@ -254,11 +256,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_), ), } @@ -474,12 +476,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) @@ -585,7 +589,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 03a5c2ca9b..937d99ede8 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 95763e2daf..3cf1d9802e 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 ee601519f0..c09f333b28 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -59,7 +59,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); } @@ -2135,10 +2135,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_base/__init__.py b/esphome/components/web_server_base/__init__.py index 148c00d27e..1625153749 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -11,6 +11,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_base/dns_server_esp32_idf.cpp b/esphome/components/web_server_base/dns_server_esp32_idf.cpp index 7948539cdf..c87433fa56 100644 --- a/esphome/components/web_server_base/dns_server_esp32_idf.cpp +++ b/esphome/components/web_server_base/dns_server_esp32_idf.cpp @@ -9,7 +9,7 @@ namespace esphome::web_server_base { -static const char *const TAG = "web_server_base.dns"; +ESPHOME_LOG_TAG(TAG, "web_server_base.dns"); // DNS constants static constexpr uint16_t DNS_PORT = 53; diff --git a/esphome/components/web_server_idf/__init__.py b/esphome/components/web_server_idf/__init__.py index 5eac63212f..98e067665a 100644 --- a/esphome/components/web_server_idf/__init__.py +++ b/esphome/components/web_server_idf/__init__.py @@ -7,6 +7,7 @@ import esphome.config_validation as cv from esphome.types import ConfigType CODEOWNERS = ["@dentra"] +DOMAIN = "web_server_idf" CONFIG_SCHEMA = cv.All( cv.Schema({}), @@ -17,9 +18,8 @@ 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 set_idf_sdkconfig_default("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") + # 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 7744272a5c..3b0da4fb56 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 dc9e3952df..64fc4b3914 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; diff --git a/esphome/components/weikai/__init__.py b/esphome/components/weikai/__init__.py index 8f0cf4ba33..58bfc6f815 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 b95d474fd3..2576856e2f 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 2c6a421a0a..9bbe558dcf 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 af5bd7f313..bafa6c4e03 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 2c6a421a0a..2a13bda034 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 03bf66e07b..6a44fad6f3 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 e69de29bb2..4bc63aeba8 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 f560917f41..73fab843fc 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 7316776d78..f69112ca3d 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 f5f304aa17..5aee703feb 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 dd6e84ac25..93d5e4f0f0 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 df64cd48aa..4f576e4c9d 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 27ebdbc976..aa6d113e16 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, @@ -683,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 @@ -1087,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 c14341330f..eb3d7471dc 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 023b4ad61b..490e6085ab 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -54,7 +54,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) { @@ -401,6 +401,7 @@ static constexpr uint32_t WIFI_AP_EXCLUSIVE_DWELL_MS = 300000; /// 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 @@ -1218,7 +1219,7 @@ void WiFiComponent::connect_soon_() { // 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("wifi-ap-handover", WIFI_AP_EXCLUSIVE_HANDOVER_MS, [this]() { + this->set_timeout(AP_HANDOVER_TIMEOUT_ID, WIFI_AP_EXCLUSIVE_HANDOVER_MS, [this]() { if (this->ap_setup_) this->pause_exclusive_ap_(); }); diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index aa1fa67b7b..41b5146000 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 diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index cbd666b4ce..58c6ecee14 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 28b30628fd..2a9e682440 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -52,7 +52,7 @@ extern "C" int __wrap_wifi_sta_connect(wifi_sta_connect_t *connect, wifi_scan_cf namespace esphome::wifi { -static const char *const TAG = "wifi_lt"; +ESPHOME_LOG_TAG(TAG, "wifi_lt"); // Thread-safe event handling for LibreTiny WiFi // diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 325bcf2652..5b05132f4f 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 e69de29bb2..77d5dd714a 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 5d4e77eaad..98b0cc4814 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 e69de29bb2..febfbf7387 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 11d816a909..57cbf9ae9b 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 3b67aa46d0..e9641a5957 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]) ) ) diff --git a/esphome/components/wireguard/wireguard.cpp b/esphome/components/wireguard/wireguard.cpp index 1fd610f4e8..01921f17e8 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 @@ -154,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)"))); @@ -249,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 faf63cd444..6125d2f52c 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_; diff --git a/esphome/components/wk2132_i2c/__init__.py b/esphome/components/wk2132_i2c/__init__.py index ecb7a5da09..7e0999b08f 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 94b692f170..de482839e1 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 d4aacaf97d..c450717ea2 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 b4efaf65ac..ef12117a2c 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 1502cc6cdb..b8882d41c1 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 930f534bb0..2de911aebd 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 00018dd735..1f51fea314 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 c1a050e8ca..a0cce3331a 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 e69de29bb2..03fae78280 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 858f974f2b..745f9ea3e6 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 a7135975f0..6b3263e6a3 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 5150cda2a5..da0accc9a9 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 e69de29bb2..5d5a06ea05 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 cc7ee98079..2888fc0abb 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 e69de29bb2..46a7316bd1 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 b0ad79e51c..a9e9e9cdae 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 943139e19a..181b38caca 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 6deab23828..5539a1318f 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 122ffaf6ed..6cebeffba1 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 0eefd594d0..795db448f0 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 16da83934e..de7998b290 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 06c3a7ab7a..57f26b6acc 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 3d75ef24ec..aeb29d234f 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 7f9cd17d1f..b59c662e80 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 e69de29bb2..f76e529bb2 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 0159314f4d..d64e66e660 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 e69de29bb2..5a9b433236 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 01912c3778..7ac6d50e16 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 e69de29bb2..ea01df8c57 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 679cb76198..64207868b7 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 e69de29bb2..b17553724d 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 019de54ad8..1515e43dea 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 e69de29bb2..643a26ffe2 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 27effd64bb..11cb3a98d3 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 e69de29bb2..e8e38dc51f 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 5e2369a6d9..80de9a6cbc 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 d47cef13c6..eeb0e7d7ef 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 680eb04e77..ac1541c386 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 e69de29bb2..4d74c2f432 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 fc8d15228d..f502cb1f66 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 e69de29bb2..e381fe6172 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 f7a1318d7c..ad2383f107 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 e69de29bb2..e8eb493338 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 d465f2fec0..0762d97eb0 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 e69de29bb2..2398660e95 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 dca5f73909..15d9f32bd5 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 e69de29bb2..3d1638fc11 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 356a4ffd4e..3bf8b7328b 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 e69de29bb2..bbf235aa96 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 1ddf7ec235..1c56ed34f6 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 0000000000..a65bad1cb1 --- /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 0000000000..f410ad7f73 --- /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 0000000000..519358270d --- /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 0000000000..8897752b44 --- /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 e7005b3957..5ffa02332f 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 9706e50861..23f20f380a 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 e69de29bb2..14fe18af57 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 d725978418..97266c86e7 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 e69de29bb2..ddda4a0a5b 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 482c0ed395..438b9b5529 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 e69de29bb2..346db02445 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 e69de29bb2..eb881ca39a 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 233f5f5783..767bcd8da0 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 e69de29bb2..3d539b267d 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 eca83c0912..683e8a2a92 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 7b289a8ee3..71d7e0a265 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 f349dfa797..f7ecf15c3b 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 e69de29bb2..d795931df9 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 ae37d63096..420b67e788 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 e69de29bb2..98af32069c 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 aba954fd91..65ab9e342d 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 5686b74173..1c6548bb5f 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 d189d9b5c7..228274b321 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 3b8a925bb2..a2b1372b62 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 83a7332005..afb8f6cd5c 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 7a7bce1781..79d31dc44a 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 e69de29bb2..587acabe78 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 ba0a3a6404..986237b2d6 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 49654736cc..4f69f00339 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 23da2cafac..4400f0a0c5 100644 --- a/esphome/components/zephyr/gpio.cpp +++ b/esphome/components/zephyr/gpio.cpp @@ -7,7 +7,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; diff --git a/esphome/components/zephyr/hal.cpp b/esphome/components/zephyr/hal.cpp index 10e8340a40..96c7df896c 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 11994b68b7..8384225d33 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 ed22613625..6be5f1ce4a 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 24b32196db..f3f126537d 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 15993abcce..dd6ecd3b20 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 e69de29bb2..945cc5807d 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 f5ab04809e..ed7069a540 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 f1eac462bc..af126a29c7 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 4bcce84845..4d3382d617 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 2cb62b85b5..604cffb0e0 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 aa1393388a..71d54e8e48 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 e69de29bb2..c17009d722 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 2585ac1b57..afca7bf860 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 02f05ef8a2..0dc3b4b6f7 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 d6abd9d6c0..86cac665b4 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 b8122d1441..bb6c153752 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 5567295782..909c714c5e 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 3f14d0a62d..7f0a5f979c 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 acf0321b12..b73c26a836 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 2680fc2203..305febf171 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 464cc04d62..911159d091 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 b0f3d17bd6..88f49ef3c1 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 c6597ebfd6..89417e748d 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 f48bf9ef0b..103e901b13 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 22f014f3ef..e52513b740 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 ceb318480c..49b03d3ec9 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 25e1e083e0..9e16cb6a0c 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 935140e9df..286a036fc6 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 286c83b8f5..1b2d7392df 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 e69de29bb2..7b2178dc7c 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 1fa995b3dc..e03f2c5d31 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 660e3f49a0..e451fe79f4 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 b0d18a3e6a..bfa16f857a 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 e69de29bb2..dcabd7a6bd 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 0b834de90a..5f203b1893 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 bc69661d8f..28a688f15a 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 579da9a315..79d7068838 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 fb02d70281..7774722795 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 30c0372066..1668263e33 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 1da3162333..6f824b5383 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 50d1c61959..2ddc97efb5 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 2f399cbdcd..b91bc187e6 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 61f34d70f4..d3f1fe59e5 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 41dd32ea66..0aa2dc21a2 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: diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 210c0174d6..6c98106089 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 @@ -270,6 +272,10 @@ #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 @@ -278,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 @@ -467,6 +475,12 @@ #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) diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index dc27c1e56a..cbb14a3730 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/gpio.h b/esphome/core/gpio.h index 43db3b7c0c..7daf1917a6 100644 --- a/esphome/core/gpio.h +++ b/esphome/core/gpio.h @@ -2,6 +2,7 @@ #include #include +#include "esphome/core/defines.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -27,6 +28,7 @@ enum Flags : uint8_t { FLAG_OPEN_DRAIN = 0x04, FLAG_PULLUP = 0x08, FLAG_PULLDOWN = 0x10, + FLAG_HOLD = 0x20, }; class FlagsHelper { @@ -79,6 +81,13 @@ class GPIOPin { virtual size_t dump_summary(char *buffer, size_t len) const; 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) diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index a11b07808d..1b332515f6 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -28,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) { @@ -261,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; @@ -274,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; @@ -309,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. @@ -428,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'); } @@ -465,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; @@ -488,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; @@ -537,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. @@ -639,8 +626,6 @@ static constexpr uint8_t base64_char_value(uint8_t c) { return INVALID_BASE64_CHAR; } -// 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); } @@ -668,8 +653,6 @@ size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *b 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) @@ -790,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 b88a9d70e9..a2ede27341 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 @@ -1105,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) { @@ -1134,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. @@ -1150,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 @@ -1605,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; } @@ -1684,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, @@ -1703,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; @@ -1717,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); @@ -2177,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 362ed83b91..145ba3a6f8 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 2042c43804..8e68594a03 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 4ba2606d76..69798fd8eb 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 3aa8669e44..836d13d8f7 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 fbb089e906..0000000000 --- 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 afb323f78e..5d0b96c38f 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 25076228d5..1b0a49acaf 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 73b7a38a35..2f7ffc292f 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 458ef51f89..02c628718e 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 167a422c61..1df7d38765 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 8cb382a77e..9fc1c71703 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 9756ed4c39..8c58797df2 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 715e5aca0c..4f5439dbb8 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 577d53f5d9..2c46fbc951 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 c89cfc68e9..04cbfb230b 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 a5ffaefe0b..f5113d8098 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -269,6 +269,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",) @@ -468,6 +475,23 @@ 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 ) -> "MockObj": diff --git a/esphome/espidf/toolchain.py b/esphome/espidf/toolchain.py index effb817f35..af3fda6349 100644 --- a/esphome/espidf/toolchain.py +++ b/esphome/espidf/toolchain.py @@ -2,7 +2,6 @@ from dataclasses import dataclass, field import fnmatch -import hashlib import json import logging import os @@ -516,23 +515,21 @@ def _sync_component_mirror() -> None: def _builtin_component_cache_path() -> Path | None: - """Cache file for this build's built-in component list. + """Cache file for this target's built-in component list. The file lives inside the extracted framework directory so it is discarded together with that exact checkout (re-extract, source - override, clean-all); the target and the EXCLUDE_COMPONENTS set name it. - The sdkconfig is not part of the key: IDF components register regardless - of CONFIG_* options and only gate their sources on them. A checkout - supplied through IDF_PATH is not managed by ESPHome and is never cached. + override, clean-all). The discovery configure registers every built-in + component, so the list serves any EXCLUDE_COMPONENTS set (the project + write drops the excluded names) and any sdkconfig (IDF components + register regardless of CONFIG_* options and only gate their sources on + them). A checkout supplied through IDF_PATH is not managed by ESPHome + and is never cached. """ if not _esphome_manages_idf(): return None target = variant_to_idf_target(CORE.data[KEY_ESP32][KEY_VARIANT]) - excluded = CORE.cmake_args.get("EXCLUDE_COMPONENTS", "") - excluded_key = hashlib.sha256(excluded.encode()).hexdigest()[:12] - return ( - _get_idf_path() / ".esphome_component_lists" / f"{target}-{excluded_key}.json" - ) + return _get_idf_path() / ".esphome_component_lists" / f"{target}.json" def load_cached_builtin_components() -> list[str] | None: @@ -635,10 +632,9 @@ def has_outdated_files(): already deletes ``dependencies.lock`` on a change but that signal gets lost as soon as the lock is missing. - ``exclude_components.esphomeinternal`` -- the resolved - EXCLUDE_COMPONENTS set. Excluded components never register in - ``project_description.json``, so re-including one needs a fresh - discovery pass before it can appear in the builtin-components - property that ``src`` REQUIRES. + EXCLUDE_COMPONENTS set. The builtin-components property that + ``src`` REQUIRES drops excluded names, so re-including one needs a + reconfigure to rebuild that property from the discovery list. We deliberately don't watch: - The top-level/src ``CMakeLists.txt`` -- ESPHome owns those, and @@ -803,8 +799,7 @@ def run_compile(config, verbose: bool) -> int: Uses two-phase configure to auto-discover available components: 1. If no previous build, configure with minimal REQUIRES to discover - components (skipped when a cached list for this IDF/target/exclusion - set exists) + components (skipped when a cached list for this IDF/target exists) 2. Regenerate CMakeLists.txt with discovered components 3. Run full build """ diff --git a/esphome/helpers.py b/esphome/helpers.py index 3ccf0fe65a..1f9b140797 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -2,6 +2,7 @@ from __future__ import annotations from collections.abc import Callable, Iterable, MutableMapping from contextlib import suppress +import importlib import ipaddress import logging import os @@ -10,6 +11,7 @@ import platform import re import stat import sys +from types import ModuleType from typing import TYPE_CHECKING, TextIO from esphome.const import __version__ as ESPHOME_VERSION @@ -201,6 +203,21 @@ def cpp_string_escape(string, encoding="utf-8"): return f'"{result}"' +def cpp_u16string_escape(string: str) -> str: + """Escape a string as a C++ u"..." literal, which the compiler encodes as UTF-16.""" + result = "" + for character in string: + code = ord(character) + if code >= 127: + # Surrogate escapes are ill-formed in C++; the compiler splits astral code points + result += f"\\U{code:08X}" + elif code < 32 or character in ("\\", '"'): + result += f"\\{code:03o}" + else: + result += character + return f'u"{result}"' + + def run_system_command(*args): import subprocess @@ -812,3 +829,11 @@ def docs_url(path: str) -> str: path = path.removeprefix("/") return docs_format.format(path=path) + + +def zstd_module() -> ModuleType: + """The zstd module: the standard library one from Python 3.14, otherwise the backport.""" + try: + return importlib.import_module("compression.zstd") + except ImportError: + return importlib.import_module("backports.zstd") diff --git a/esphome/idf_component.yml b/esphome/idf_component.yml index f532e26249..8ac76cd41f 100644 --- a/esphome/idf_component.yml +++ b/esphome/idf_component.yml @@ -2,7 +2,7 @@ dependencies: bblanchon/arduinojson: version: "7.4.3" esphome/dlms_parser: - version: 1.1.0 + version: 2.2.0 esphome/esp-audio-libs: version: 4.0.1 esphome/esp-micro-speech-features: @@ -24,7 +24,7 @@ dependencies: espressif/esp32-camera: version: 2.1.7 espressif/mdns: - version: 1.12.0 + version: 1.14.0 espressif/esp_wifi_remote: version: 1.6.5 rules: @@ -101,8 +101,11 @@ dependencies: - if: "idf_version >=6.0.0 && target in [esp32s2, esp32s3, esp32s31, esp32p4, esp32h4]" esp32async/asynctcp: version: 3.4.91 + # sendspin is ESP-IDF only (it needs noise-c as an ESP-IDF component, see below) sendspin/sendspin-cpp: - version: 0.8.0 + version: 0.9.3 + rules: + - if: "$ESPHOME_ARDUINO_COMPONENT == 0" lvgl/lvgl: version: 9.5.0 fastled/FastLED: @@ -113,11 +116,11 @@ dependencies: # noise (api, ota). Arduino below IDF 6.0 bundles espressif/libsodium, which # clashes with esphome/libsodium, so those builds use the PlatformIO libraries. esphome/noise-c: - version: 0.1.30 + version: 0.1.31 rules: - if: "$ESPHOME_ARDUINO_COMPONENT == 0 || idf_version >= 6.0.0" # Declared alongside noise-c so the library converter skips wireguard's copy esphome/libsodium: - version: 1.10021.11 + version: 1.10021.12 rules: - if: "$ESPHOME_ARDUINO_COMPONENT == 0 || idf_version >= 6.0.0" diff --git a/esphome/pins.py b/esphome/pins.py index 34a406c273..d42cc489a1 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -1,4 +1,4 @@ -from collections.abc import Callable +from collections.abc import Callable, Hashable from functools import reduce from logging import Logger import operator @@ -6,6 +6,7 @@ from typing import Any import esphome.config_validation as cv from esphome.const import ( + CONF_ADDRESS, CONF_ALLOW_OTHER_USES, CONF_IGNORE_STRAPPING_WARNING, CONF_INPUT, @@ -17,8 +18,9 @@ from esphome.const import ( CONF_PULLDOWN, CONF_PULLUP, ) -from esphome.core import CORE +from esphome.core import CORE, ID from esphome.cpp_generator import MockObjClass +from esphome.schema_extractors import schema_extractor class PinRegistry(dict): @@ -69,11 +71,15 @@ class PinRegistry(dict): # evaluate here so a validation failure skips the rest result = self[key][1](conf) if CONF_NUMBER in result: - # key maps to the pin schema - if key != CORE.target_platform: - pin_key = (key, conf[key], result[CONF_NUMBER]) - else: - pin_key = (key, key, result[CONF_NUMBER]) + # key maps to the pin schema. The bucket used here doesn't need to + # identify the provider precisely -- final_validate() regroups pin + # usage by the *resolved* provider id once IDPassValidationStep has + # run, so this only has to be hashable. A hub selected by + # match_config (e.g. address) is given as a dict, which isn't. + ref = conf[key] if key != CORE.target_platform else key + if not isinstance(ref, Hashable): + ref = "match_config" + pin_key = (key, ref, result[CONF_NUMBER]) if pin_key not in self.pins_used: self.pins_used[pin_key] = [] # client_id identifies the instance of the providing component @@ -107,7 +113,18 @@ class PinRegistry(dict): Run the final validation for all pins, and check for reuse :param fconf: The full config """ - for (key, _, _), pin_list in self.pins_used.items(): + # Regroup by (schema key, resolved provider id, pin number) rather than + # the validate()-time bucket, so the same physical pin reached via an + # explicit id and via {address: ...} (or an omitted id) is recognized as + # one reuse group regardless of which syntax was used to reference the hub. + grouped: dict[tuple, list] = {} + for (key, _, number), pin_list in self.pins_used.items(): + for entry in pin_list: + client_id = entry[1] + provider_id = client_id.id if isinstance(client_id, ID) else client_id + grouped.setdefault((key, provider_id, number), []).append(entry) + + for (key, _, _), pin_list in grouped.items(): count = len(pin_list) # number of places same pin used. final_val_fun = self[key][2] # final validation function for pin_path, client_id, pin_config in pin_list: @@ -131,6 +148,40 @@ class PinRegistry(dict): PIN_SCHEMA_REGISTRY = PinRegistry() +def use_id_or_address(hub_type, address_key=CONF_ADDRESS): + """Build a validator for a pin-provider hub reference. + + Accepts everything `cv.use_id(hub_type)` accepts (an explicit id, or omitted to + auto-select the sole instance of `hub_type`), plus a mapping like + `{address: 0x21}` to instead select the instance of `hub_type` whose own + declared config has a matching `address_key`. Use this for pin schemas of + I2C-addressed hubs (I/O expanders) so a board with two instances of the + same hub type can pick one without assigning it an explicit id. + """ + id_validator = cv.use_id(hub_type) + + @schema_extractor("use_id") + def validator(value): + if isinstance(value, dict): + address = cv.Schema({cv.Required(address_key): cv.i2c_address})(value)[ + address_key + ] + return ID( + None, + is_declaration=False, + type=hub_type, + # The user explicitly gave selection criteria (unlike the plain + # omitted-id auto-pick), so treat the resolved id as manual: it + # must survive strip_default_ids() and appear in `esphome config` + # output, not be silently dropped as if it were a default. + is_manual=True, + match_config={address_key: address}, + ) + return id_validator(value) + + return validator + + def _set_mode(value, default_mode): if CONF_MODE not in value: return {**value, CONF_MODE: default_mode} diff --git a/esphome/platformio/prefetch.py b/esphome/platformio/prefetch.py index fbb31ae452..991164eb99 100644 --- a/esphome/platformio/prefetch.py +++ b/esphome/platformio/prefetch.py @@ -62,6 +62,9 @@ def _preserved_sys_path() -> Iterator[None]: # Concurrent registry resolutions / HEAD probes (each is network-bound) _RESOLVE_WORKERS = 8 +# Concurrent VCS clones; network-bound, so wider than the extraction pool +_CLONE_WORKERS = 8 + # A hung child must not block the build; downloads resume on the next run _PREFETCH_TIMEOUT = 20 * 60 @@ -372,42 +375,64 @@ def _registry_jobs( return jobs, failed, installable +def _is_vcs_spec_uri(url: str) -> bool: + """Whether pio's ``install_from_uri`` would clone this URI rather than + copy or download it (PackageSpec normalizes git URLs to ``git+``).""" + return not url.startswith(("file://", "symlink://", "http://", "https://")) + + +def _is_clone_entry(entry: tuple) -> bool: + """Whether this pre-install entry's spec is cloned, not extracted.""" + url = entry[1].uri + return bool(url) and _is_vcs_spec_uri(url) + + +def _spec_name(spec: Any, url: str) -> str: + """The spec's name; the URL basename fallback is defensive only + (PackageSpec derives a name from the URI itself).""" + return spec.name or url.split("#", 1)[0].rsplit("/", 1)[-1] + + def _uri_jobs( - manager: Any, specs: list[Any], seen: set[str] + manager: Any, specs: list[Any], seen: set[str], trusted_names: bool = False ) -> tuple[list[tuple[str, int, Any]], int, list[tuple[str, Any]]]: """Jobs for direct-URL specs; a HEAD sizes each for the combined bar. Also returns how many HEAD probes errored (an absent length is not an - error) and the ``(name, spec)`` pairs whose archives will be - installable. + error) and the ``(name, spec)`` pairs to pre-install, including VCS + specs, which the pre-install clones itself. ``trusted_names`` marks + platform packages, whose platform.json keys match their manifests. """ from esphome.net_retry import fetch_with_retry, http_request - candidates: list[tuple[str, str, Path, Any]] = [] + candidates: list[tuple[str, str, Path, Any, bool]] = [] installable: list[tuple[str, Any]] = [] for spec in specs: url = spec.uri - if not url or not url.startswith(("http://", "https://")): - continue # git+/file specs are cloned/copied, not downloaded - if url.split("#", 1)[0].endswith(".git"): - continue # bare-URL VCS spec; PlatformIO clones it + if not url: + continue + is_vcs = _is_vcs_spec_uri(url) + if not is_vcs and not url.startswith(("http://", "https://")): + continue # file/symlink specs are copied in place by pio run if manager.get_package(spec): continue - name = spec.name or url.rsplit("/", 1)[-1] + name = _spec_name(spec, url) + # Only a name that is also the install dir may pre-install + safe_name = trusted_names or spec.has_custom_name() + if is_vcs: + if safe_name: + installable.append((name, spec)) + continue # PlatformIO downloads URL specs with no checksum dl_path = Path(manager.compute_download_path(url, "")) if dl_path.is_file(): - if spec.has_custom_name(): - # Only a custom name (Foo=https://...) is the destination - # dir; a URI-derived name's destination comes from the - # archive manifest, so its dedupe key could collide with - # another name and race one directory. pio run installs it. + if safe_name: installable.append((name, spec)) # fetched by an earlier run continue if str(dl_path) in seen: continue # another spec already claimed this .part seen.add(str(dl_path)) - candidates.append((spec.name, url, dl_path, spec)) + candidates.append((name, url, dl_path, spec, safe_name)) errors: list[str] = [] @@ -434,16 +459,17 @@ def _uri_jobs( if not candidates: return [], 0, installable with ThreadPoolExecutor(max_workers=min(_RESOLVE_WORKERS, len(candidates))) as ex: - sizes = list(ex.map(_head_size, [url for _, url, _, _ in candidates])) + sizes = list(ex.map(_head_size, [url for _, url, _, _, _ in candidates])) jobs: list[tuple[str, int, Any]] = [] failed = 0 - for (name, url, dl_path, spec), size in zip(candidates, sizes, strict=True): + for (name, url, dl_path, spec, safe_name), size in zip( + candidates, sizes, strict=True + ): if size < 0: failed += 1 elif size: jobs.append((name, size, _uri_fetch_job(manager, url, dl_path, size))) - if spec.has_custom_name(): - # See above: derived-name specs stay with pio run's installer + if safe_name: installable.append((name, spec)) else: # Missing or unusable Content-Length; visible under -v @@ -703,7 +729,11 @@ def _preinstall( would hang, not fail). Waves skip dependencies; the installed manifests feed the next wave. Any failure falls back to pio run. """ - workers = extract_workers(len(entries)) + # Clones wait on the network: sort them first, dependency waves too + entries = sorted(entries, key=lambda entry: not _is_clone_entry(entry)) + clones = sum(map(_is_clone_entry, entries)) + # Network-bound clones get a wider pool than CPU-bound extraction + workers = max(extract_workers(len(entries)), min(clones, _CLONE_WORKERS)) # One manager per worker (_install mutates instance state); built # serially because construction rewires the shared manager logger managers: SimpleQueue = SimpleQueue() @@ -735,8 +765,9 @@ def _preinstall( raise _LOGGER.info( - "Installing %d PlatformIO package(s) with %d extraction worker(s): %s", + "Installing %d PlatformIO package(s)%s with %d worker(s): %s", len(entries), + f" ({clones} clone(s))" if clones else "", workers, ", ".join(name for name, *_ in entries), ) @@ -871,8 +902,10 @@ def _prefetch(build_dir: Path, env: str) -> None: unresolved = 0 for mgr, batch, is_platform in ((p.pm, specs, True), (lm, lib_specs, False)): entries: list[tuple[str, Any]] = [] - for build_jobs in (_registry_jobs, _uri_jobs): - batch_jobs, failed, installable = build_jobs(mgr, batch, seen) + for batch_jobs, failed, installable in ( + _registry_jobs(mgr, batch, seen), + _uri_jobs(mgr, batch, seen, trusted_names=is_platform), + ): jobs += batch_jobs unresolved += failed entries += installable diff --git a/esphome/writer.py b/esphome/writer.py index 0bd485bfd3..559d7d8582 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -212,14 +212,11 @@ BUILD_INFO_DATA_H_TARGET = "esphome/core/build_info_data.h" BUILD_INFO_DATA_CPP_TARGET = "esphome/core/build_info_data.cpp" ENTITY_TYPES_H_TARGET = "esphome/core/entity_types.h" # Headers that must not be included bare from esphome.h or the clang-tidy -# all-headers file: X-macro files, headers main.cpp includes itself, and -# deprecated headers that only resolve when their new component is loaded. +# all-headers file: X-macro files and headers main.cpp includes itself. ESPHOME_H_EXCLUDE = { Path(ENTITY_TYPES_H_TARGET), # main.cpp includes it after defining esphome_controllers() Path("esphome/core/controller_dispatch.h"), - # moved to components/ring_buffer/, removed in 2026.11.0 - Path("esphome/core/ring_buffer.h"), # build machinery, not user API Path("esphome/core/pch_prefix.h"), } diff --git a/platformio.ini b/platformio.ini index 82463b8953..7534b748d1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -79,7 +79,7 @@ lib_deps = ; noise (api, ota) -- on the ESP-IDF framework these come from the ; component registry instead (see esphome/idf_component.yml), so they ; are not in [common]. - esphome/noise-c@0.1.30 ; noise (api, ota) + esphome/noise-c@0.1.31 ; noise (api, ota) SPI ; spi (Arduino built-in) Wire ; i2c (Arduino built-int) heman/AsyncMqttClient-esphome@1.0.0 ; mqtt @@ -106,7 +106,7 @@ build_unflags = ; esp32 envs (which would double-include them). [common:idf-component-libs] lib_deps = - esphome/dlms_parser@1.1.0 ; dlms_meter + esphome/dlms_parser@2.2.0 ; dlms_meter bblanchon/ArduinoJson@7.4.3 ; json lvgl/lvgl@9.5.0 ; lvgl @@ -247,7 +247,7 @@ lib_deps = ${common:idf-component-libs.lib_deps} ESP32Async/ESPAsyncWebServer@3.9.6 ; web_server_base esphome/wireguard@0.4.8 ; wireguard - esphome/noise-c@0.1.30 ; noise (api, ota) + esphome/noise-c@0.1.31 ; noise (api, ota) ESP32Async/AsyncTCP@3.4.5 ; async_tcp DNSServer ; captive_portal heman/AsyncMqttClient-esphome@2.0.0 ; mqtt @@ -645,7 +645,7 @@ build_unflags = extends = common platform = platformio/native lib_deps = - esphome/noise-c@0.1.30 ; used by noise (api, ota) + esphome/noise-c@0.1.31 ; used by noise (api, ota) lvgl/lvgl@9.5.0 ; lvgl build_flags = ${common.build_flags} diff --git a/requirements.txt b/requirements.txt index 988bd62000..612bda6b1a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,24 +12,28 @@ pyserial==3.5 platformio==6.2.0 esptool==5.4.0 click==8.5.0 -aioesphomeapi==46.6.0 +aioesphomeapi==46.9.0 aiohappyeyeballs==2.7.1 # Happy Eyeballs for requests downloads; already pulled in by aioesphomeapi zeroconf==0.151.5 puremagic==2.2.0 ruamel.yaml==0.19.1 # dashboard_import ruamel.yaml.clib==0.2.15 # dashboard_import esphome-glyphsets==0.2.0 +backports.zstd==1.7.0; python_version < "3.14" # esphome.helpers.zstd_module; compression.zstd is in the standard library from 3.14 pillow==12.3.0 resvg-py==0.5.0 freetype-py==2.5.1 jinja2==3.1.6 bleak==3.0.2 smpclient==7.3.0 +# smp 4.2+ needs cbor2 6+, which ships no Intel macOS wheels (arm64 only). +smp==4.2.0; platform_system != "Darwin" or platform_machine != "x86_64" +smp==4.1.0; platform_system == "Darwin" and platform_machine == "x86_64" requests==2.34.2 py7zr==1.1.3 -platformdirs==4.12.2 # native esp-idf toolchain global cache dir +platformdirs==4.12.3 # native esp-idf toolchain global cache dir ninja==1.13.2 # native host and esp8266 arduino toolchain build driver -filelock==4.0.9 # inter-process locks (PlatformIO cache heal, git clone cache); >=3.32 for FileLock(fallback_to_soft=...), older versions silently drop the kwarg +filelock==4.0.12 # inter-process locks (PlatformIO cache heal, git clone cache); >=3.32 for FileLock(fallback_to_soft=...), older versions silently drop the kwarg # esp-idf >= 5.0 requires this pyparsing >= 3.3.3 diff --git a/requirements_test.txt b/requirements_test.txt index c81f2f83dc..0f7d376255 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -2,7 +2,7 @@ pylint==4.1.2 flake8==7.4.1 # .pre-commit-config.yaml rev synced by script/sync_dependency_versions.py ruff==0.16.10 # .pre-commit-config.yaml rev synced by script/sync_dependency_versions.py pyupgrade==3.21.2 # .pre-commit-config.yaml rev synced by script/sync_dependency_versions.py -prek==0.5.4 # .github/workflows/ci.yml reads this pin +prek==0.5.5 # .github/workflows/ci.yml reads this pin yamlrocks==0.6.1 # used by script/sync_dependency_versions.py # Unit tests diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index db548ae4f6..a143a85032 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -536,6 +536,16 @@ def create_field_type_info( f"track_presence on field '{field.name}' has no effect; it requires " "a non-repeated message field in a message that is decoded" ) + if get_field_opt(field, pb.progmem, False) and ( + field.label == FieldDescriptorProto.LABEL_REPEATED + or field.type != 12 + or not get_field_opt(field, pb.pointer_to_buffer, False) + or get_field_opt(field, pb.fixed_array_size) is not None + ): + raise ValueError( + f"progmem on field '{field.name}' requires a non-repeated bytes field " + "with pointer_to_buffer" + ) if field.label == FieldDescriptorProto.LABEL_REPEATED: # Check if this is a packed_buffer field (zero-copy packed repeated) if get_field_opt(field, pb.packed_buffer, False): @@ -941,7 +951,7 @@ class MessageType(TypeInfo): return False def encode_element(self, number: int, element: str) -> str: - return _encode_call("encode_sub_message", "buffer", str(number), element) + return _encode_call("encode_sub_message", str(number), element) @property def cpp_type(self) -> str: @@ -964,9 +974,8 @@ class MessageType(TypeInfo): @property def encode_content(self) -> str: - # Sub-message encoding needs buffer for backpatch/sync return _encode_call( - self.encode_func, "buffer", str(self.number), f"this->{self.field_name}" + self.encode_func, str(self.number), f"this->{self.field_name}" ) @property @@ -1159,6 +1168,11 @@ class PointerToBytesBufferType(PointerToBufferTypeBase): reference_type = "const uint8_t*" const_reference_type = "const uint8_t*" + @property + def progmem(self) -> bool: + """Whether the data is in flash, so encode and dump copy it with progmem_memcpy.""" + return get_field_opt(self._field, pb.progmem, False) + @property def public_content(self) -> list[str]: # Use uint16_t for length - max packet size is well below 65535 @@ -1169,6 +1183,14 @@ class PointerToBytesBufferType(PointerToBufferTypeBase): @property def encode_content(self) -> str: + if self.progmem: + return _encode_call( + "encode_progmem_bytes", + str(self.number), + f"this->{self.field_name}", + f"this->{self.field_name}_len", + force=self.force, + ) if result := self._encode_bytes_with_precomputed_tag( f"this->{self.field_name}", f"this->{self.field_name}_len" ): @@ -1195,8 +1217,9 @@ class PointerToBytesBufferType(PointerToBufferTypeBase): @property def dump_content(self) -> str: + dump_fn = "dump_progmem_bytes_field" if self.progmem else "dump_bytes_field" return ( - f'dump_bytes_field(out, ESPHOME_PSTR("{self.name}"), ' + f'{dump_fn}(out, ESPHOME_PSTR("{self.name}"), ' f"this->{self.field_name}, this->{self.field_name}_len);" ) @@ -2610,6 +2633,11 @@ def build_message_type( ): fixed_vector_fields.append((field.name, field.number)) + if needs_decode and get_field_opt(field, pb.progmem, False): + raise ValueError( + f"progmem on field '{field.name}' of {desc.name} requires a message " + "that is only encoded; received data is never in flash" + ) ti = create_field_type_info(field, needs_decode, needs_encode) # Skip field declarations for fields that are in the base class @@ -2722,19 +2750,18 @@ def build_message_type( ) for line in encode ] - o = f"{speed_attr}uint8_t *{desc.name}::encode_msg(const void *self, ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) {{\n" + o = f"{speed_attr}uint8_t *{desc.name}::encode_msg(const void *self, uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM) {{\n" o += f" const auto &msg = *static_cast(self);\n" - o += " uint8_t *__restrict__ pos = buffer.get_pos();\n" o += indent("\n".join(encode_debug)).replace("this->", "msg.") + "\n" o += " return pos;\n" o += "}\n" cpp += o public_content.append( - "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);" ) public_content.append( "uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {\n" - " return encode_msg(this, buffer PROTO_ENCODE_DEBUG_ARG);\n" + " return encode_msg(this, buffer.get_pos() PROTO_ENCODE_DEBUG_ARG);\n" "}" ) # If no fields to encode or message doesn't need encoding, the default implementation in ProtoMessage will be used @@ -3321,15 +3348,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 """ @@ -3508,7 +3546,7 @@ namespace esphome::api { namespace esphome::api { -static const char *const TAG = "api.service"; +ESPHOME_LOG_TAG(TAG, "api.service"); """ diff --git a/script/build_language_schema.py b/script/build_language_schema.py index 91c1de00cd..d8f692de63 100755 --- a/script/build_language_schema.py +++ b/script/build_language_schema.py @@ -456,7 +456,10 @@ def fix_lvgl_widgets(): def get_logger_tags(): - pattern = re.compile(r'^static const char \*const TAG = "(\w.*)";', re.MULTILINE) + pattern = re.compile( + r'^(?:static const char \*const TAG = "|ESPHOME_LOG_TAG\(TAG, ")(\w[^"]*)"', + re.MULTILINE, + ) # tags not in components dir tags = [ "app", diff --git a/script/ci-custom.py b/script/ci-custom.py index 692393b221..1b3abc40f2 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import argparse +import ast import codecs import collections from collections.abc import Iterator @@ -811,6 +812,40 @@ def lint_const_py_frozen(fname, content): return None +@lint_content_check(include=["esphome/components/*/__init__.py"]) +def lint_component_domain(fname: Path, content: str) -> str | None: + """Require every component's __init__.py to define DOMAIN as its own name.""" + if len(fname.parts) != 4: + # Platform packages such as esphome/components//sensor/__init__.py + return None + domain = fname.parts[2] + expected = f'DOMAIN = "{domain}"' + try: + tree = ast.parse(content, filename=str(fname)) + except SyntaxError: + # Reported by the Python linters; nothing useful to add here. + return None + for node in tree.body: + if isinstance(node, ast.Assign): + targets = node.targets + elif isinstance(node, ast.AnnAssign): + targets = [node.target] + else: + continue + if ( + any(isinstance(t, ast.Name) and t.id == "DOMAIN" for t in targets) + and isinstance(node.value, ast.Constant) + and node.value.value == domain + ): + return None + return ( + f"Component is missing the {highlight(expected)} constant. " + "Add it in alphabetical order with the other component metadata such as " + "CODEOWNERS and DEPENDENCIES, so other code can refer to the component by " + "name, e.g. CORE.data[DOMAIN]." + ) + + def relative_cpp_search_text(fname: Path, content) -> str: parts = fname.parts integration = parts[2] @@ -1036,9 +1071,6 @@ HEAP_ALLOCATING_HELPERS = { # The definitions themselves "esphome/core/alloc_helpers.h", "esphome/core/alloc_helpers.cpp", - # Backward compatibility re-exports (remove before 2026.11.0) - "esphome/core/helpers.h", - "esphome/core/helpers.cpp", # Vendored third-party library "esphome/components/http_request/httplib.h", ], @@ -1091,7 +1123,6 @@ def lint_no_sprintf(fname, match): # Vendored library "esphome/components/http_request/httplib.h", # Deprecated helpers that return std::string - "esphome/core/helpers.cpp", "esphome/core/alloc_helpers.cpp", # The using declaration itself "esphome/core/helpers.h", @@ -1237,6 +1268,45 @@ def lint_no_std_nothrow(fname, match): ) +@lint_re_check( + r"^\s*(?:(?:static|constexpr|inline)\s+)*(?:const\s+)?char\s*(?:\*\s*(?:const\s+)?TAG|\s(?:const\s+)?TAG\s*\[\w*\])" + r"\s*(?:=\s*\{?|[{(])\s*\"", + prefilter="TAG", + include=["esphome/components/*.cpp", "esphome/components/**/*.cpp"], + # Deprecated components, converted in a follow-up PR + exclude=[ + "esphome/components/st7735/*", + "esphome/components/st7789v/*", + "esphome/components/waveshare_epaper/*", + ], +) +def lint_log_tag_macro(fname, match): + return ( + f"Declare log tags with {highlight('ESPHOME_LOG_TAG(TAG, "name");')}, which keeps the tag in flash on " + f"ESP8266.\n" + f" Before: {highlight('static const char *const TAG = "name";')}\n" + f" After: {highlight('ESPHOME_LOG_TAG(TAG, "name");')}\n" + f"Never pass such a TAG to set_timeout/set_interval names or string functions.\n" + f"(If strictly necessary, add `// NOLINT` to the end of the line)" + ) + + +@lint_re_check( + r"(?:\b(?:set_timeout|set_interval|set_retry|cancel_timeout|cancel_interval|cancel_retry|defer)" + r"|\b(?:strcmp|strncmp|strcasecmp|strlen|strcpy|strncpy)|std::string)\s*\((?:(?:[^();]|\([^()]*\))*,)?\s*TAG\b" + r"|\bstd::string\s+\w+\s*[={(]\s*TAG\b", + mask=True, + prefilter="TAG", + include=["esphome/components/*.cpp", "esphome/components/**/*.cpp"], +) +def lint_log_tag_as_string(fname, match): + return ( + f"{highlight('TAG')} is in flash on ESP8266 (ESPHOME_LOG_TAG), so it can only be used for logging.\n" + f"Use a separate name or a numeric id for scheduler calls, and do not pass it to string functions.\n" + f"(If strictly necessary, add `// NOLINT` to the end of the line)" + ) + + LOG_CALL_START_RE = re.compile(r"ESP_LOG\w+\s*\(") # Comments, raw/plain string literals and single char literals are consumed whole so ; ( ) ? : # inside them are never seen. A char literal is exactly one (escaped) char so a digit separator diff --git a/script/clang-tidy b/script/clang-tidy index ad6c99d637..a2becb59cc 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import argparse +import heapq import os from pathlib import Path import queue @@ -11,6 +12,7 @@ import subprocess import sys import tempfile import threading +import time from typing import Any import click @@ -277,10 +279,21 @@ def clang_options(idedata, environment): pids = set() +# Generated files the static analyzer is skipped on. The analyzer inlines the +# small dump helpers into every generated dump function and takes +# over 200 s on this file in CI, 99% of its clang-tidy time; any finding there +# would be fixed in script/api_protobuf anyway. The other checks still run. +ANALYZER_SKIPPED_FILES: frozenset[str] = frozenset( + {"esphome/components/api/api_pb2_dump.cpp"} +) -def run_tidy(executable, args, options, tmpdir, path_queue, lock, failed_files): + +def run_tidy( + executable, args, options, tmpdir, path_queue, lock, failed_files, timings +): while True: path = path_queue.get() + started = time.monotonic() invocation = [executable] if tmpdir is not None: @@ -297,16 +310,22 @@ def run_tidy(executable, args, options, tmpdir, path_queue, lock, failed_files): if sys.stdout.isatty(): invocation.append("--use-color") + disabled_checks = [] if args.environment.startswith(("rp2", "bk72xx", "ln882h", "rtl87xx")): # MMIO peripheral access on these bare-metal platforms is all # fixed-address. # bugprone-pointer-arithmetic-on-polymorphic-object (and its # cert-ctr56-cpp alias) crashes clang-tidy 22 with infinite matcher # recursion on lvgl_esphome.h under the RP2 defines. - invocation.append( - "--checks=-clang-analyzer-core.FixedAddressDereference," - "-bugprone-pointer-arithmetic-on-polymorphic-object,-cert-ctr56-cpp" - ) + disabled_checks += [ + "-clang-analyzer-core.FixedAddressDereference", + "-bugprone-pointer-arithmetic-on-polymorphic-object", + "-cert-ctr56-cpp", + ] + if os.path.normpath(path) in ANALYZER_SKIPPED_FILES: + disabled_checks.append("-clang-analyzer-*") + if disabled_checks: + invocation.append("--checks=" + ",".join(disabled_checks)) invocation.append(f"--header-filter={Path(basepath).resolve()}/.*") invocation.append(str(Path(path).resolve())) invocation.append("--") @@ -319,8 +338,9 @@ def run_tidy(executable, args, options, tmpdir, path_queue, lock, failed_files): check=False, close_fds=False, ) - if proc.returncode != 0: - with lock: + with lock: + timings.append((time.monotonic() - started, path)) + if proc.returncode != 0: print_error_for_file(path, proc.stdout) failed_files.append(path) path_queue.task_done() @@ -333,8 +353,27 @@ def progress_bar_show(value): def split_list(a, n): - k, m = divmod(len(a), n) - return [a[i * k + min(i, m) : (i + 1) * k + min(i + 1, m)] for i in range(n)] + """Deal the sorted files out to n splits in turn. + + Contiguous slices put whole directories on one split; expensive + neighbours such as esphome/core then land together and that split takes + several times longer than the others. Dealing spreads them out. + """ + return [a[i::n] for i in range(n)] + + +SLOWEST_FILES_SHOWN = 15 + + +def print_slowest_files(timings): + """Print the files clang-tidy spent the longest on, so the splits can be + rebalanced from CI logs.""" + if not timings: + return + total = sum(seconds for seconds, _ in timings) + print(f"\nclang-tidy spent {total:.0f} s on {len(timings)} files; slowest:") + for seconds, path in heapq.nlargest(SLOWEST_FILES_SHOWN, timings): + print(f" {seconds:6.1f} s {path}") def main(): @@ -458,6 +497,7 @@ def main(): tmpdir = tempfile.mkdtemp() failed_files = [] + timings = [] try: executable = get_binary("clang-tidy", 22) task_queue = queue.Queue(args.jobs) @@ -473,6 +513,7 @@ def main(): task_queue, lock, failed_files, + timings, ), ) t.daemon = True @@ -487,6 +528,7 @@ def main(): # Wait for all threads to be done. task_queue.join() + print_slowest_files(timings) except FileNotFoundError: return 1 diff --git a/script/helpers.py b/script/helpers.py index fb2c964bdd..11605fcef0 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -1616,7 +1616,7 @@ def lpt_partition( Heaviest item first into the lightest group. Ties keep input order, so pass pre-sorted items for deterministic output. script/clang-tidy's - split_list is the unweighted contiguous sibling. + split_list is the unweighted sibling that deals files out in turn. """ groups: list[list[str]] = [[] for _ in range(count)] group_weights = [0.0] * count diff --git a/script/split_components_for_ci.py b/script/split_components_for_ci.py index cc458d5d66..663f2c6d5d 100755 --- a/script/split_components_for_ci.py +++ b/script/split_components_for_ci.py @@ -13,15 +13,20 @@ from __future__ import annotations import argparse from collections import defaultdict +from collections.abc import Set as AbstractSet +import contextlib from dataclasses import dataclass, field +import functools import json import math from pathlib import Path import sys +from typing import Any, NamedTuple # Add esphome to path sys.path.insert(0, str(Path(__file__).parent.parent)) +from esphome import yaml_util from script.analyze_component_buses import ( ISOLATED_COMPONENTS, ISOLATED_SIGNATURE_PREFIX, @@ -31,29 +36,52 @@ from script.analyze_component_buses import ( merge_compatible_bus_groups, ) from script.helpers import ( + _extract_components_from_yaml, get_component_test_files, parse_test_filename, split_conflicting_groups, ) -# Estimated CI seconds for one build per test platform, read from batch job -# logs. Absolute accuracy does not matter, only the ratios between platforms. -# A full run takes over an hour, so recording real timings is not practical. +# Estimated CI seconds for one build per test platform, fitted to the batch +# job logs of a full run (every component, PR #20389). Absolute accuracy does +# not matter, only the ratios between platforms. A full run takes over an +# hour, so recording real timings is not practical. PLATFORM_BUILD_SECONDS = { "host": 10, "esp8266-ard": 30, - "esp32-idf": 45, - "rtl87xx-ard": 35, - "ln882x-ard": 50, - "rp2040-ard": 50, - "rp2350-ard": 50, - "bk72xx-ard": 60, + "rtl87xx-ard": 40, + "esp32-c3-idf": 40, + "esp32-s2-idf": 40, + "rp2350-ard": 40, + "rp2040-ard": 45, + "esp32-idf": 55, + "esp32-p4-idf": 55, + "ln882x-ard": 55, + "esp32-s3-idf": 60, + "bk72xx-ard": 70, + "esp32-c6-idf": 70, } -NRF52_BUILD_SECONDS = 45 -# Other ESP32 chips and Arduino on ESP32 share less of the ccache -DEFAULT_BUILD_SECONDS = 90 +NRF52_BUILD_SECONDS = 50 +# Arduino on any ESP32 chip builds the IDF plus the Arduino core on top +ESP32_ARDUINO_BUILD_SECONDS = 90 +# The remaining ESP32 chips on ESP-IDF +DEFAULT_BUILD_SECONDS = 50 # Each extra component merged into a grouped build makes it larger -GROUPED_COMPONENT_SECONDS = 5 +GROUPED_COMPONENT_SECONDS = 3 +# Components that pull in a large library the build compiles from source +# (TensorFlow Lite Micro, FastLED, LVGL, the HTTP server), added to every +# build whose test config enables them. Fitted the same way as the platforms. +HEAVY_COMPONENT_SECONDS: dict[str, int] = { + "micro_wake_word": 200, + "fastled_clockless": 90, + "fastled_spi": 90, + "wled": 60, + "lvgl": 50, + "web_server": 50, + "online_image": 45, + "prometheus": 35, + "bluetooth_proxy": 30, +} # Estimated build seconds per CI runner; sets how many runners are used. # Approximate: the runner count charges each grouped build once, but a large # group spreads over several runners, which each pay for that build. @@ -90,9 +118,62 @@ def build_seconds(platform: str) -> int: return seconds if platform.startswith("nrf52"): return NRF52_BUILD_SECONDS + if platform.startswith("esp32") and platform.endswith("-ard"): + return ESP32_ARDUINO_BUILD_SECONDS return DEFAULT_BUILD_SECONDS +def _config_components(data: Any) -> set[str]: + """Return the components a loaded test config and its ``packages:`` use.""" + if not isinstance(data, dict): + return set() + found = _extract_components_from_yaml(data) + if isinstance(packages := data.get("packages"), dict): + for package in packages.values(): + if isinstance(package, yaml_util.IncludeFile): + found |= _file_components( + (package.parent_file.parent / package.file).resolve() + ) + else: + found |= _config_components(package) + return found + + +@functools.cache +def _file_components(path: Path) -> frozenset[str]: + """Return the components a config file uses; shared packages load once.""" + return frozenset( + _config_components(yaml_util.load_yaml(path, track_document_range=False)) + ) + + +def heavy_components(component: str, test_file: Path) -> frozenset[str]: + """Return the heavy components a test compiles. + + A test counts as heavy when it belongs to a heavy component or when its + config enables one, such as a display test that draws with lvgl. + """ + heavy = {component} & HEAVY_COMPONENT_SECONDS.keys() + # A config that does not load is left to the build to report + with contextlib.suppress(Exception): + heavy |= _file_components(test_file.resolve()) & HEAVY_COMPONENT_SECONDS.keys() + return frozenset(heavy) + + +def heavy_seconds(heavy: AbstractSet[str]) -> int: + """Return the library seconds a set of heavy components adds to a build.""" + return sum(HEAVY_COMPONENT_SECONDS[name] for name in heavy) + + +class _GroupedShare(NamedTuple): + """A component's part in one (signature, platform) shared build.""" + + # Seconds of its files on that platform when no other member shares it + solo: int + # Heavy components its base test brings into the shared build + heavy: frozenset[str] + + @dataclass class _BatchItem: """A component and the builds it adds to the batch it lands in.""" @@ -101,40 +182,52 @@ class _BatchItem: # Builds that always run on their own (isolated tests, variants on a # platform without a base test) own_seconds: int - # Per (signature, platform) shared build: the seconds of this component's - # files on that platform when no other batch member shares the build - grouped_builds: dict[tuple[str, str], int] = field(default_factory=dict) + grouped_builds: dict[tuple[str, str], _GroupedShare] = field(default_factory=dict) def standalone_seconds(self) -> int: - return self.own_seconds + sum(self.grouped_builds.values()) + return self.own_seconds + sum( + share.solo for share in self.grouped_builds.values() + ) -def _grouped_build_seconds(platform: str, solo_seconds: list[int]) -> int: +def _grouped_build_seconds(platform: str, members: list[_GroupedShare]) -> int: """Return the seconds of one (signature, platform) build in a batch. + A library two members both enable compiles once, so the union is charged. test_build_components only groups when two or more members share the build; a grouped member then skips its variants on that platform. """ - if len(solo_seconds) <= 1: - return sum(solo_seconds) - return build_seconds(platform) + GROUPED_COMPONENT_SECONDS * (len(solo_seconds) - 1) + if len(members) <= 1: + return sum(share.solo for share in members) + return ( + build_seconds(platform) + + GROUPED_COMPONENT_SECONDS * (len(members) - 1) + + heavy_seconds(set().union(*(share.heavy for share in members))) + ) def _make_item( tests_dir: Path, component: str, signature: str, is_isolated: bool ) -> _BatchItem: - files_by_platform: dict[str, list[str]] = defaultdict(list) + # Per platform: the seconds of its test files, and the base test's heavy + # components; a platform without a base test cannot join a shared build + seconds_by_platform: dict[str, int] = defaultdict(int) + base_heavy: dict[str, frozenset[str]] = {} for test_file in (tests_dir / component).glob("test[.-]*.yaml"): test_name, platform = parse_test_filename(test_file) - files_by_platform[platform].append(test_name) + heavy = heavy_components(component, test_file) + seconds_by_platform[platform] += build_seconds(platform) + heavy_seconds(heavy) + if test_name == "test": + base_heavy[platform] = heavy own_seconds = 0 - grouped: dict[tuple[str, str], int] = {} - for platform, test_names in files_by_platform.items(): - seconds = build_seconds(platform) * len(test_names) - if is_isolated or "test" not in test_names: + grouped: dict[tuple[str, str], _GroupedShare] = {} + for platform, seconds in seconds_by_platform.items(): + if is_isolated or platform not in base_heavy: own_seconds += seconds else: - grouped[(signature, platform)] = seconds + grouped[(signature, platform)] = _GroupedShare( + seconds, base_heavy[platform] + ) return _BatchItem(component, own_seconds, grouped) @@ -144,22 +237,24 @@ class _Batch: def __init__(self) -> None: self.components: list[str] = [] self.seconds = 0 - self.grouped_builds: dict[tuple[str, str], list[int]] = defaultdict(list) + self.grouped_builds: dict[tuple[str, str], list[_GroupedShare]] = defaultdict( + list + ) def added_seconds(self, item: _BatchItem) -> int: """Return the seconds item would add; joining an existing build is cheap.""" added = item.own_seconds - for (signature, platform), solo in item.grouped_builds.items(): + for (signature, platform), share in item.grouped_builds.items(): members = self.grouped_builds.get((signature, platform), []) added += _grouped_build_seconds( - platform, [*members, solo] + platform, [*members, share] ) - _grouped_build_seconds(platform, members) return added def add(self, item: _BatchItem) -> None: self.seconds += self.added_seconds(item) - for build, solo in item.grouped_builds.items(): - self.grouped_builds[build].append(solo) + for build, share in item.grouped_builds.items(): + self.grouped_builds[build].append(share) self.components.append(item.component) @@ -167,25 +262,36 @@ def balance_batches(items: list[_BatchItem], target_seconds: int) -> list[list[s """Spread items over enough runners to stay near target_seconds each. The runner count comes from the total estimate with every grouped build - counted once. Heaviest items go first, each to the runner that ends up - lightest, so grouped components follow the builds they can join. + counted once. Heaviest items go first, each to the runner it adds the + fewest seconds to among those ending near the lightest, so grouped + components follow the builds they can join. """ if not items: return [] - grouped_builds: dict[tuple[str, str], list[int]] = defaultdict(list) + # One batch holding every item counts each grouped build once + whole = _Batch() for item in items: - for build, solo in item.grouped_builds.items(): - grouped_builds[build].append(solo) - total = sum(item.own_seconds for item in items) + sum( - _grouped_build_seconds(platform, solos) - for (_, platform), solos in grouped_builds.items() - ) + whole.add(item) + total = whole.seconds count = min(len(items), max(1, math.ceil(total / target_seconds))) batches = [_Batch() for _ in range(count)] + # Among the runners ending within slack of the lightest, the one the item + # adds the fewest seconds to wins, so a component lands with the bus + # group partners it shares builds with; placed away from them it builds + # alone on every platform they only share there. + slack = target_seconds // 4 for item in sorted(items, key=lambda i: (-i.standalone_seconds(), i.component)): - min(batches, key=lambda b: (b.seconds + b.added_seconds(item), b.seconds)).add( - item + costs = [(batch, batch.added_seconds(item)) for batch in batches] + ceiling = min(batch.seconds + added for batch, added in costs) + slack + batch, _ = min( + ( + (batch, added) + for batch, added in costs + if batch.seconds + added <= ceiling + ), + key=lambda cost: (cost[1], cost[0].seconds + cost[1]), ) + batch.add(item) return [batch.components for batch in batches if batch.components] diff --git a/sdkconfig.defaults b/sdkconfig.defaults index f4fe331df4..25c4cc4d36 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -13,6 +13,7 @@ CONFIG_LIBC_NEWLIB=y # esp32 CONFIG_COMPILER_OPTIMIZATION_SIZE=y CONFIG_FREERTOS_HZ=1000 +CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 CONFIG_ESP_TASK_WDT_INIT=y CONFIG_ESP_TASK_WDT_PANIC=y CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n diff --git a/tests/benchmarks/components/api/bench_proto_proxy.cpp b/tests/benchmarks/components/api/bench_proto_proxy.cpp index e45966999b..c8b56044de 100644 --- a/tests/benchmarks/components/api/bench_proto_proxy.cpp +++ b/tests/benchmarks/components/api/bench_proto_proxy.cpp @@ -149,7 +149,7 @@ BENCHMARK(Decode_SerialProxyWriteRequest); // --- InfraredRFReceiveEvent encode (100 sint32 timings) + // InfraredRFTransmitRawTimingsRequest decode (hand-built wire bytes) --- -#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY) +#ifdef USE_IR_RF // Mark/space pairs simulating a typical RC-5 / NEC capture (100 timings). static std::vector make_ir_timings_100() { @@ -275,6 +275,6 @@ static void Decode_InfraredRFTransmitRawTimingsRequest(benchmark::State &state) } BENCHMARK(Decode_InfraredRFTransmitRawTimingsRequest); -#endif // USE_IR_RF || USE_RADIO_FREQUENCY +#endif // USE_IR_RF } // namespace esphome::api::benchmarks diff --git a/tests/benchmarks/python/fixtures/bluetooth_proxy_device.yaml b/tests/benchmarks/python/fixtures/bluetooth_proxy_device.yaml index dfa5a487b8..c74846689b 100644 --- a/tests/benchmarks/python/fixtures/bluetooth_proxy_device.yaml +++ b/tests/benchmarks/python/fixtures/bluetooth_proxy_device.yaml @@ -25,7 +25,9 @@ ethernet: type: LAN8720 mdc_pin: GPIO23 mdio_pin: GPIO18 - clk_mode: GPIO17_OUT + clk: + mode: CLK_OUT + pin: GPIO17 phy_addr: 0 power_pin: GPIO12 diff --git a/tests/benchmarks/stubs/esphome/components/infrared/infrared.h b/tests/benchmarks/stubs/esphome/components/infrared/infrared.h index 874e7a270b..c6ed7fe203 100644 --- a/tests/benchmarks/stubs/esphome/components/infrared/infrared.h +++ b/tests/benchmarks/stubs/esphome/components/infrared/infrared.h @@ -19,7 +19,8 @@ class InfraredCall { return *this; } InfraredCall &set_repeat_count(uint32_t /*count*/) { return *this; } - void perform() {} + template InfraredCall &set_api_connection(T * /*conn*/) { return *this; } + bool perform() { return false; } protected: Infrared *parent_; @@ -37,6 +38,7 @@ class Infrared : public Component, public EntityBase { const InfraredTraits &get_traits() const { return this->traits_; } InfraredCall make_call() { return InfraredCall(this); } uint32_t get_capability_flags() const { return 0; } + template void on_api_connection_closed(T * /*conn*/) {} protected: InfraredTraits traits_; diff --git a/tests/benchmarks/stubs/esphome/components/radio_frequency/radio_frequency.h b/tests/benchmarks/stubs/esphome/components/radio_frequency/radio_frequency.h index 72fc08034b..b1cdd53c5b 100644 --- a/tests/benchmarks/stubs/esphome/components/radio_frequency/radio_frequency.h +++ b/tests/benchmarks/stubs/esphome/components/radio_frequency/radio_frequency.h @@ -23,7 +23,8 @@ class RadioFrequencyCall { RadioFrequencyCall &set_raw_timings_packed(const uint8_t * /*data*/, uint16_t /*length*/, uint16_t /*count*/) { return *this; } - void perform() {} + template RadioFrequencyCall &set_api_connection(T * /*conn*/) { return *this; } + bool perform() { return false; } protected: RadioFrequency *parent_; @@ -43,6 +44,7 @@ class RadioFrequency : public Component, public EntityBase { const RadioFrequencyTraits &get_traits() const { return this->traits_; } RadioFrequencyCall make_call() { return RadioFrequencyCall(this); } uint32_t get_capability_flags() const { return 0; } + template void on_api_connection_closed(T * /*conn*/) {} protected: RadioFrequencyTraits traits_; diff --git a/tests/benchmarks/stubs/esphome/components/serial_proxy/serial_proxy.h b/tests/benchmarks/stubs/esphome/components/serial_proxy/serial_proxy.h index 7da6fff017..e2c2efaa6b 100644 --- a/tests/benchmarks/stubs/esphome/components/serial_proxy/serial_proxy.h +++ b/tests/benchmarks/stubs/esphome/components/serial_proxy/serial_proxy.h @@ -49,6 +49,7 @@ class SerialProxy { uint32_t get_modem_pins() const { return 0; } uint32_t get_configured_modem_pins() const { return 0; } SerialProxyResult flush_port(api::APIConnection *api_connection) { return SerialProxyResult::SERIAL_PROXY_RESULT_OK; } + void send_identity(api::APIConnection *api_connection) {} protected: uint32_t instance_index_{0}; diff --git a/tests/component_tests/api/test_homeassistant_fields.py b/tests/component_tests/api/test_homeassistant_fields.py new file mode 100644 index 0000000000..0dec970ae3 --- /dev/null +++ b/tests/component_tests/api/test_homeassistant_fields.py @@ -0,0 +1,33 @@ +"""Tests for the Home Assistant action field tables.""" + +from collections.abc import Callable +from pathlib import Path + +import pytest + +from esphome.components.api import DATA_FIELDS_SCHEMA, VARIABLES_FIELDS_SCHEMA +import esphome.config_validation as cv + +CONFIG = "tests/component_tests/api/test_homeassistant_fields.yaml" + + +def test_field_tables(generate_main: Callable[[str | Path], str]) -> None: + """Strings are single PROGMEM arrays on ESP8266; only tables without lambdas are shared.""" + main_cpp = generate_main(CONFIG) + + assert main_cpp.count('PROGMEM = "message";') == 1 + assert main_cpp.count('PROGMEM = "notify.notify";') == 1 + assert 'ESPHOME_F("message")' not in main_cpp + + assert main_cpp.count("(api_apiserver_id, false, ha_action_fields, 1, 0, 0);") == 2 + + assert "(api_apiserver_id, true, ha_action_fields_2, 1, 0, 0);" in main_cpp + assert "(api_apiserver_id, true, ha_action_fields_3, 1, 0, 0);" in main_cpp + + +@pytest.mark.parametrize("schema", [DATA_FIELDS_SCHEMA, VARIABLES_FIELDS_SCHEMA]) +def test_field_map_limit(schema: cv.Schema) -> None: + """Each map is counted in a uint8_t, so 256 entries fail validation.""" + schema({f"key{i}": "value" for i in range(255)}) + with pytest.raises(cv.Invalid): + schema({f"key{i}": "value" for i in range(256)}) diff --git a/tests/component_tests/api/test_homeassistant_fields.yaml b/tests/component_tests/api/test_homeassistant_fields.yaml new file mode 100644 index 0000000000..270a925a7e --- /dev/null +++ b/tests/component_tests/api/test_homeassistant_fields.yaml @@ -0,0 +1,29 @@ +esphome: + name: test + on_boot: + then: + - homeassistant.action: + action: notify.notify + data: + message: Booted + - homeassistant.action: + action: notify.notify + data: + message: Booted + - homeassistant.event: + event: esphome.booted + data: + message: !lambda 'static int n = 0; return ++n;' + - homeassistant.event: + event: esphome.booted + data: + message: !lambda 'static int n = 0; return ++n;' + +esp8266: + board: esp01_1m + +wifi: + ssid: SomeNetwork + password: SomePassword + +api: diff --git a/tests/component_tests/api/test_homeassistant_variables.py b/tests/component_tests/api/test_homeassistant_variables.py index 48e53d8f4c..6d15438212 100644 --- a/tests/component_tests/api/test_homeassistant_variables.py +++ b/tests/component_tests/api/test_homeassistant_variables.py @@ -17,7 +17,7 @@ def test_plain_string_with_return_is_compiled_as_lambda_with_warning( with caplog.at_level(logging.WARNING): main_cpp = generate_main(CONFIG) - assert main_cpp.count('add_variable(ESPHOME_F("lambda_var"), []() {') == 2 + assert main_cpp.count('from_lambda("lambda_var", []() {') == 2 assert "return millis();" in main_cpp # The source text must not be sent as a static string value. assert '"return millis();"' not in main_cpp @@ -28,16 +28,11 @@ def test_static_string_is_kept_as_static_value( generate_main: Callable[[str | Path], str], caplog: pytest.LogCaptureFixture, ) -> None: - """A static string stays static, PROGMEM wrapped, with no warning.""" + """A static string stays a constant table entry, with no warning.""" with caplog.at_level(logging.WARNING): main_cpp = generate_main(CONFIG) - assert ( - main_cpp.count( - 'add_variable(ESPHOME_F("static_var"), ESPHOME_F("static value"));' - ) - == 2 - ) + assert main_cpp.count('{"static_var", "static value", nullptr}') == 2 assert "static value" not in caplog.text @@ -49,7 +44,7 @@ def test_static_id_value_stays_literal_with_hint( with caplog.at_level(logging.WARNING): main_cpp = generate_main(CONFIG) - assert 'ESPHOME_F("id(test_sensor).state")' in main_cpp + assert '{"hint_var", "id(test_sensor).state", nullptr}' in main_cpp assert "sent as literal text" in caplog.text @@ -59,5 +54,5 @@ def test_explicit_lambda_tag_is_compiled_as_lambda( """A !lambda value keeps working unchanged.""" main_cpp = generate_main(CONFIG) - assert 'add_variable(ESPHOME_F("tagged_var"), []() {' in main_cpp + assert 'from_lambda("tagged_var", []() {' in main_cpp assert "return App.get_name();" in main_cpp diff --git a/tests/component_tests/api/test_wizard.py b/tests/component_tests/api/test_wizard.py new file mode 100644 index 0000000000..112092482b --- /dev/null +++ b/tests/component_tests/api/test_wizard.py @@ -0,0 +1,927 @@ +"""Tests for the api wizard: schema, final validation and generated tables.""" + +from collections.abc import Callable +import json +from pathlib import Path +import random +import re +import string +import textwrap + +import pytest + +from esphome.components.api import wizard +from esphome.components.homeassistant.switch import SUPPORTED_DOMAINS as SWITCH_DOMAINS +from esphome.config import load_config +from esphome.core import CORE +from esphome.helpers import fnv1_hash, fnv1a_32bit_hash, zstd_module +from tests.component_tests.helpers import get_define_value + +ESP32_HEADER = """ + esphome: + name: test + + esp32: + variant: esp32 + + wifi: + ssid: test + password: testtest + + logger: + """ + +ESP8266_HEADER = """ + esphome: + name: test + + esp8266: + board: d1_mini + + wifi: + ssid: test + password: testtest + + logger: + """ + +ENTITIES = """ + switch: + - platform: template + id: sw + name: Switch + optimistic: true + - platform: template + id: internal_sw + name: Internal switch + internal: true + optimistic: true + - platform: template + id: unnamed_sw + optimistic: true + - platform: homeassistant + id: ha_switch + %(light)s + sensor: + - platform: template + id: template_sensor + name: Template sensor + - platform: homeassistant + id: ha_plain_sensor + entity_id: sensor.plain + - platform: homeassistant + id: ha_sensor + %(sensor)s + binary_sensor: + - platform: homeassistant + id: ha_binary + %(binary)s + text_sensor: + - platform: homeassistant + id: ha_text + %(text)s + number: + - platform: homeassistant + id: ha_number + %(number)s + text: + - platform: homeassistant + id: ha_txt + %(text_entity)s + select: + - platform: homeassistant + id: ha_select + %(select)s + button: + - platform: homeassistant + id: ha_button + %(button)s + """ + + +def build_entities(inputs: set[str]) -> str: + """The test entities. The homeassistant ones that are wizard inputs set no entity_id, the others do.""" + ids = { + "light": ("ha_switch", "entity_id: light.lamp"), + "sensor": ("ha_sensor", "entity_id: sensor.a"), + "binary": ("ha_binary", "entity_id: binary_sensor.a"), + "text": ("ha_text", "entity_id: sensor.b"), + "number": ("ha_number", "entity_id: number.a"), + "text_entity": ("ha_txt", "entity_id: text.a"), + "select": ("ha_select", "entity_id: select.a"), + "button": ("ha_button", "entity_id: button.a"), + } + return ENTITIES % { + key: "" if name in inputs else yaml for key, (name, yaml) in ids.items() + } + + +GOOD_PAGES = """ + api: + wizard: + pages: + - title: Setup + description: "[%key:component::domain::section::name%]" + entities: + - id: sw + description: Enable + - id: sw + - inputs: + - entity: ha_sensor + description: Weather + target: + entity: + - integration: met + domain: sensor + device_class: [temperature, humidity] + supported_features: weather.WeatherEntityFeature.FORECAST_DAILY + - domain: [sensor, number] + - entity: ha_binary + - entity: ha_text + - entity: ha_number + - entity: ha_switch + - entity: ha_txt + - entity: ha_select + - entity: ha_button + - id: weather_input + target: + entity: + - domain: weather + - id: unset_input + """ + + +def write_config( + tmp_path: Path, header: str, api: str, yaml_entities: str | None = None +) -> Path: + """Write a config. By default the homeassistant entities that the wizard does not use as inputs set an entity_id.""" + path = tmp_path / "test.yaml" + if yaml_entities is None: + yaml_entities = build_entities(set(re.findall(r"entity: (\w+)", api))) + path.write_text( + textwrap.dedent(header) + textwrap.dedent(yaml_entities) + textwrap.dedent(api) + ) + return path + + +def write_input_config(tmp_path: Path, header: str) -> Path: + """Write a config with GOOD_PAGES.""" + return write_config(tmp_path, header, GOOD_PAGES) + + +def config_errors(path: Path) -> list[str]: + CORE.config_path = path + return [str(err.msg) for err in load_config({}).errors] + + +def wizard_api(pages: str) -> str: + return "api:\n wizard:\n pages:\n" + textwrap.indent( + textwrap.dedent(pages).strip("\n"), " " + ) + + +def test_no_wizard_emits_nothing( + tmp_path: Path, generate_main: Callable[[str | Path], str] +) -> None: + main_cpp = generate_main( + write_config( + tmp_path, + ESP32_HEADER, + "api:\n", + "sensor:\n - platform: homeassistant\n id: s\n entity_id: sensor.s\n", + ) + ) + + assert "API_WIZARD_PAGES" not in main_cpp + assert 's->set_entity_id("sensor.s");' in main_cpp + assert not any(define.name == "USE_API_WIZARD" for define in CORE.defines) + + +def test_valid_wizard_has_no_errors(tmp_path: Path) -> None: + assert config_errors(write_input_config(tmp_path, ESP32_HEADER)) == [] + + +def wizard_inputs(inputs: str) -> str: + return wizard_api("- inputs:\n" + textwrap.indent(textwrap.dedent(inputs), " ")) + + +def test_switch_input_with_supported_domains_is_valid(tmp_path: Path) -> None: + api = wizard_inputs( + """ + - entity: ha_switch + target: + entity: + - domain: [light, fan] + """ + ) + + assert config_errors(write_config(tmp_path, ESP32_HEADER, api)) == [] + + +@pytest.mark.parametrize( + ("api", "message"), + [ + pytest.param( + wizard_inputs("- entity: template_sensor"), + "must be a homeassistant", + id="input-not-homeassistant", + ), + pytest.param( + wizard_inputs("- entity: sw"), + "must be a homeassistant", + id="input-not-homeassistant-switch", + ), + pytest.param( + wizard_inputs( + """ + - entity: ha_switch + target: + entity: + - domain: [light, valve] + """ + ), + "does not support the domain(s) valve", + id="switch-unsupported-domain", + ), + pytest.param( + wizard_inputs( + """ + - entity: ha_switch + target: + entity: + - integration: hue + """ + ), + "must set domain", + id="switch-filter-without-domain", + ), + pytest.param( + wizard_inputs("- entity: ha_sensor\n- entity: ha_sensor"), + "'ha_sensor' is used more than once", + id="duplicate-input", + ), + ], +) +def test_invalid_inputs_are_rejected(tmp_path: Path, api: str, message: str) -> None: + errors = config_errors(write_config(tmp_path, ESP32_HEADER, api)) + + assert any(message in error for error in errors), errors + + +def test_hash_collision_is_rejected(tmp_path: Path) -> None: + entities = "".join( + f"sensor:\n - platform: homeassistant\n id: {name}\n" + if index == 0 + else f" - platform: homeassistant\n id: {name}\n" + for index, name in enumerate(("a179599", "a362382")) + ) + api = wizard_inputs("- entity: a179599\n- entity: a362382") + errors = config_errors(write_config(tmp_path, ESP32_HEADER, api, entities)) + + assert any("have the same hash" in error for error in errors), errors + + +@pytest.mark.parametrize( + "extra", + [ + "", + " entity_id: sensor.default\n", + ], +) +def test_entity_id_is_optional_only_for_wizard_inputs( + tmp_path: Path, extra: str +) -> None: + entities = f"sensor:\n - platform: homeassistant\n id: lonely\n{extra}" + errors = config_errors(write_config(tmp_path, ESP32_HEADER, "api:\n", entities)) + + if extra: + assert errors == [] + else: + assert any( + "entity_id is required unless this entity is a wizard input" in error + for error in errors + ), errors + + +def test_entity_id_is_optional_for_an_input_and_its_domain_is_checked( + tmp_path: Path, +) -> None: + entities = "switch:\n - platform: homeassistant\n id: ha_switch\n entity_id: climate.heater\n" + errors = config_errors( + write_config( + tmp_path, ESP32_HEADER, wizard_inputs("- entity: ha_switch"), entities + ) + ) + + # The YAML entity ID is still checked against the platform's domains + assert any("not supported by the switch platform" in error for error in errors) + + +@pytest.mark.parametrize( + ("pages", "message"), + [ + pytest.param( + "- title: Empty", + "at least one of entities, inputs", + id="page-without-fields", + ), + pytest.param( + """ + - entities: [] + """, + "length of value must be at least 1", + id="empty-entities-list", + ), + pytest.param( + """ + - entities: [{id: internal_sw}] + """, + "Entity 'internal_sw' is internal", + id="internal-entity", + ), + pytest.param( + """ + - entities: [{id: unnamed_sw}] + """, + "Entity 'unnamed_sw' is internal", + id="unnamed-entity-is-internal", + ), + pytest.param( + """ + - inputs: + - entity: ha_sensor + target: + entity: + - {} + """, + "at least one of", + id="empty-filter", + ), + pytest.param( + """ + - inputs: + - entity: ha_sensor + target: + entity: [] + """, + "length of value must be at least 1", + id="no-filters", + ), + pytest.param( + """ + - inputs: + - entity: ha_sensor + target: + entity: + - domain: [] + """, + "length of value must be at least 1", + id="empty-domain-list", + ), + pytest.param( + """ + - inputs: + - entity: ha_sensor + target: + entity: + - supported_features: FEATURE + """.replace("FEATURE", "x" * 128), + "length of value must be at most 127", + id="long-feature", + ), + pytest.param( + """ + - description: TEXT + entities: [{id: sw}] + """.replace("TEXT", "x" * 256), + "length of value must be at most 255", + id="long-description", + ), + ], +) +def test_invalid_wizards_are_rejected(tmp_path: Path, pages: str, message: str) -> None: + errors = config_errors(write_config(tmp_path, ESP32_HEADER, wizard_api(pages))) + + assert any(message in error for error in errors), errors + + +def test_no_pages_is_rejected(tmp_path: Path) -> None: + errors = config_errors( + write_config(tmp_path, ESP32_HEADER, "api:\n wizard:\n pages: []\n") + ) + + assert any("length of value must be at least 1" in error for error in errors), ( + errors + ) + + +def test_input_is_set_condition( + tmp_path: Path, generate_main: Callable[[str | Path], str] +) -> None: + interval = """ + interval: + - interval: 1h + then: + - if: + condition: + api.wizard.input_is_set: weather_input + then: + - logger.log: set + - if: + condition: + api.wizard.input_is_set: + id: unset_input + then: + - logger.log: set + """ + path = write_input_config(tmp_path, ESP32_HEADER) + path.write_text(path.read_text() + textwrap.dedent(interval)) + main_cpp = generate_main(path) + + assert "weather_input->has_entity_id()" in main_cpp + assert "unset_input->has_entity_id()" in main_cpp + + +@pytest.mark.parametrize( + ("inputs", "message"), + [ + pytest.param( + "- description: Neither", + "exactly one of id, entity", + id="neither-id-nor-entity", + ), + pytest.param( + "- id: standalone\n entity: ha_sensor", + "more than one of id, entity", + id="both-id-and-entity", + ), + pytest.param( + "- entity: ha_sensor\n entity_id: sensor.a", + "extra keys not allowed", + id="entity-id-on-linked-input", + ), + pytest.param( + "- id: standalone\n entity_id: weather.home", + "extra keys not allowed", + id="entity-id-on-standalone-input", + ), + pytest.param( + "- entity: ha_sensor\n- entity: ha_sensor", + "used more than once", + id="duplicate-linked-input", + ), + ], +) +def test_standalone_and_linked_input_rules( + tmp_path: Path, inputs: str, message: str +) -> None: + errors = config_errors(write_config(tmp_path, ESP32_HEADER, wizard_inputs(inputs))) + + assert any(message in error for error in errors), errors + + +def test_standalone_input_is_valid(tmp_path: Path) -> None: + api = wizard_inputs("- id: standalone") + + assert config_errors(write_config(tmp_path, ESP32_HEADER, api)) == [] + + +WIZARD_DEFINES = { + "USE_API_WIZARD", + "USE_API_WIZARD_INPUTS", + "USE_API_WIZARD_LINKED_INPUTS", + "USE_API_WIZARD_STANDALONE_INPUTS", +} + + +@pytest.mark.parametrize( + ("pages", "expected"), + [ + pytest.param( + "- entities: [{id: sw}]", + {"USE_API_WIZARD"}, + id="entity-only", + ), + pytest.param( + "- inputs: [{id: standalone}]", + { + "USE_API_WIZARD", + "USE_API_WIZARD_INPUTS", + "USE_API_WIZARD_STANDALONE_INPUTS", + }, + id="standalone-input-without-filter", + ), + pytest.param( + "- inputs: [{id: standalone, target: {entity: [{domain: weather}]}}]", + { + "USE_API_WIZARD", + "USE_API_WIZARD_INPUTS", + "USE_API_WIZARD_STANDALONE_INPUTS", + }, + id="standalone-input-with-filter", + ), + pytest.param( + "- inputs: [{entity: ha_sensor}]", + { + "USE_API_WIZARD", + "USE_API_WIZARD_INPUTS", + "USE_API_WIZARD_LINKED_INPUTS", + }, + id="linked-sensor-without-filter", + ), + pytest.param( + "- inputs: [{entity: ha_sensor, target: {entity: [{domain: sensor}]}}]", + { + "USE_API_WIZARD", + "USE_API_WIZARD_INPUTS", + "USE_API_WIZARD_LINKED_INPUTS", + }, + id="linked-sensor-with-filter", + ), + pytest.param( + # A switch without a target gets the default filter + "- inputs: [{entity: ha_switch}]", + { + "USE_API_WIZARD", + "USE_API_WIZARD_INPUTS", + "USE_API_WIZARD_LINKED_INPUTS", + }, + id="linked-switch-default-filter", + ), + pytest.param( + "- entities: [{id: sw}]\n inputs: [{entity: ha_sensor, target: {entity: [{domain: sensor}]}}, {id: standalone}]", + WIZARD_DEFINES, + id="everything", + ), + ], +) +def test_only_the_defines_the_wizard_needs_are_emitted( + tmp_path: Path, + generate_main: Callable[[str | Path], str], + pages: str, + expected: set[str], +) -> None: + api = wizard_api(pages) + generate_main(write_config(tmp_path, ESP32_HEADER, api)) + + assert {d.name for d in CORE.defines} & WIZARD_DEFINES == expected + + +@pytest.mark.parametrize( + ("entity", "bad_domain"), + [ + ("ha_txt", "select"), + ("ha_select", "text"), + ("ha_button", "switch"), + ("ha_number", "sensor"), + ], +) +def test_text_select_and_button_inputs_keep_to_their_domains( + tmp_path: Path, entity: str, bad_domain: str +) -> None: + api = wizard_inputs( + f"- entity: {entity}\n target:\n entity:\n - domain: {bad_domain}" + ) + errors = config_errors(write_config(tmp_path, ESP32_HEADER, api)) + + assert any( + f"does not support the domain(s) {bad_domain}" in error for error in errors + ), errors + + +@pytest.mark.parametrize("entity", ["ha_txt", "ha_select", "ha_button", "ha_number"]) +def test_text_select_and_button_filters_must_set_a_domain( + tmp_path: Path, entity: str +) -> None: + api = wizard_inputs( + f"- entity: {entity}\n target:\n entity:\n - integration: hue" + ) + errors = config_errors(write_config(tmp_path, ESP32_HEADER, api)) + + assert any("must set domain" in error for error in errors), errors + + +def test_number_input_accepts_input_number(tmp_path: Path) -> None: + api = wizard_inputs( + "- entity: ha_number\n target:\n entity:\n - domain: input_number" + ) + + assert config_errors(write_config(tmp_path, ESP32_HEADER, api)) == [] + + +def test_text_select_and_button_default_to_their_domains( + tmp_path: Path, generate_main: Callable[[str | Path], str] +) -> None: + main_cpp = generate_main(write_input_config(tmp_path, ESP32_HEADER)) + document = json.loads(zstd_module().decompress(blob_in(main_cpp))) + filters = { + entry["key"]: entry.get("entity_filters") + for page in document["pages"] + for entry in page.get("inputs", []) + } + + # ha_txt and ha_button have no target, so they take every supported domain + assert filters[fnv1_hash("ha_txt")] == [{"domain": ["input_text", "text"]}] + assert filters[fnv1_hash("ha_button")] == [{"domain": ["button", "input_button"]}] + assert filters[fnv1_hash("ha_select")] == [{"domain": ["input_select", "select"]}] + # A number takes number and input_number entities, and the other sensors take anything + assert filters[fnv1_hash("ha_number")] == [{"domain": ["input_number", "number"]}] + assert filters[fnv1_hash("ha_binary")] is None + for entity in ("ha_txt", "ha_select", "ha_button"): + assert f"{entity}->set_entity_id(api_wizard_input_{entity});" in main_cpp + + +@pytest.mark.parametrize( + "entity_yaml", + [ + "text:\n - platform: homeassistant\n id: lonely\n", + "select:\n - platform: homeassistant\n id: lonely\n", + "button:\n - platform: homeassistant\n id: lonely\n", + ], +) +def test_new_platforms_need_an_entity_id_unless_they_are_inputs( + tmp_path: Path, entity_yaml: str +) -> None: + errors = config_errors(write_config(tmp_path, ESP32_HEADER, "api:\n", entity_yaml)) + + assert any( + "entity_id is required unless this entity is a wizard input" in error + for error in errors + ), errors + + +DEVICES_HEADER = """ + esphome: + name: test + devices: + - id: kitchen_dev + name: Kitchen + + esp32: + variant: esp32 + + wifi: + ssid: test + password: testtest + + logger: + """ + +JSON_ENTITIES = """ + switch: + - platform: template + id: sw + name: Switch + optimistic: true + - platform: template + id: sw2 + name: Kitchen Switch + device_id: kitchen_dev + optimistic: true + - platform: homeassistant + id: ha_switch + sensor: + - platform: homeassistant + id: ha_sensor + """ + +JSON_PAGES = """ + - title: Audio + description: Pick + entities: + - id: sw + description: Enable + - id: sw2 + inputs: + - id: weather + description: Weather + target: + entity: + - integration: met + domain: weather + device_class: [temperature, humidity] + - domain: [weather, sensor] + - entity: ha_switch + - inputs: [{entity: ha_sensor}] + """ + + +def blob_in(main_cpp: str) -> bytes: + """The compressed wizard that the generated code puts in flash.""" + match = re.search( + r"const uint8_t esphome::api::API_WIZARD_DATA\[\] PROGMEM = \{([^}]*)\};", + main_cpp, + ) + assert match is not None + return bytes(int(byte) for byte in match.group(1).split(", ")) + + +def entity_hash(main_cpp: str, variable: str) -> int: + """The key the generated code passes to configure_entity_, which ListEntities then sends.""" + match = re.search( + rf'App\.register_switch\({variable}, "[^"]*", (\d+)UL, \d+\)', main_cpp + ) + assert match is not None, variable + return int(match.group(1)) + + +def test_the_blob_is_the_exact_json_document( + tmp_path: Path, generate_main: Callable[[str | Path], str] +) -> None: + main_cpp = generate_main( + write_config(tmp_path, DEVICES_HEADER, wizard_api(JSON_PAGES), JSON_ENTITIES) + ) + blob = blob_in(main_cpp) + + text = zstd_module().decompress(blob).decode("utf-8") + expected = { + "version": 1, + "pages": [ + { + "title": "Audio", + "description": "Pick", + "entities": [ + # No device_id for the main device, and no description when unset + {"key": entity_hash(main_cpp, "sw"), "description": "Enable"}, + { + "key": entity_hash(main_cpp, "sw2"), + "device_id": fnv1a_32bit_hash("kitchen_dev"), + }, + ], + "inputs": [ + { + "key": fnv1_hash("weather"), + "description": "Weather", + "entity_filters": [ + { + "integration": "met", + "domain": ["weather"], + "device_class": ["temperature", "humidity"], + }, + {"domain": ["weather", "sensor"]}, + ], + }, + { + "key": fnv1_hash("ha_switch"), + # The default filter of a switch + "entity_filters": [{"domain": SWITCH_DOMAINS}], + }, + ], + }, + {"inputs": [{"key": fnv1_hash("ha_sensor")}]}, + ], + } + assert text == json.dumps( + expected, separators=(",", ":"), sort_keys=True, ensure_ascii=False + ) + # The device id is the one the generated code gives the device + assert f"set_device_id({fnv1a_32bit_hash('kitchen_dev')})" in main_cpp + assert get_define_value("API_WIZARD_DATA_SIZE") == str(len(blob)) + + +def test_the_blob_is_deterministic_and_the_same_on_every_platform( + tmp_path: Path, generate_main: Callable[[str | Path], str] +) -> None: + api = wizard_api(JSON_PAGES) + first = blob_in( + generate_main(write_config(tmp_path, DEVICES_HEADER, api, JSON_ENTITIES)) + ) + # Compressing the same document again gives the same bytes + document = wizard.wizard_document(CORE.config["api"]["wizard"], CORE.config) + again = zstd_module().compress( + json.dumps( + document, separators=(",", ":"), sort_keys=True, ensure_ascii=False + ).encode("utf-8"), + level=wizard.WIZARD_ZSTD_LEVEL, + ) + + assert again == first + assert first[:4] == b"\x28\xb5\x2f\xfd" # a zstd frame + + +def test_the_blob_is_in_flash_on_esp8266( + tmp_path: Path, generate_main: Callable[[str | Path], str] +) -> None: + main_cpp = generate_main(write_input_config(tmp_path, ESP8266_HEADER)) + + assert "esphome::api::API_WIZARD_DATA[] PROGMEM = {" in main_cpp + assert ( + "const api::WizardInputEntry esphome::api::API_WIZARD_INPUTS[] PROGMEM = {" + in main_cpp + ) + # Only the compressed data and the input table are emitted, no strings or row tables + assert "api_wizard_str" not in main_cpp + assert "WizardEntityRow" not in main_cpp + + +def test_inputs_have_buffers_hashes_and_a_table( + tmp_path: Path, generate_main: Callable[[str | Path], str] +) -> None: + main_cpp = generate_main(write_input_config(tmp_path, ESP32_HEADER)) + + # The buffer holds the YAML entity ID until the wizard sets another + assert 'static char api_wizard_input_ha_sensor[256] = "";' in main_cpp + entities = ( + "ha_sensor", + "ha_binary", + "ha_text", + "ha_number", + "ha_switch", + "ha_txt", + "ha_select", + "ha_button", + "weather_input", + "unset_input", + ) + for entity in entities: + assert f"{{{fnv1_hash(entity)}u, api_wizard_input_{entity}}}" in main_cpp + assert get_define_value("API_WIZARD_INPUT_COUNT") == str(len(entities)) + for entity in entities[:-2]: + assert f"{entity}->set_entity_id(api_wizard_input_{entity});" in main_cpp + # The standalone inputs are objects that read the same buffer + assert "WizardInput(api_wizard_input_weather_input)" in main_cpp + assert "WizardInput(api_wizard_input_unset_input)" in main_cpp + # Not an input, so it keeps its constant + assert 'ha_plain_sensor->set_entity_id("sensor.plain");' in main_cpp + + +def test_an_entity_without_a_name_cannot_be_in_the_wizard(tmp_path: Path) -> None: + yaml_entities = 'switch:\n - platform: template\n id: nameless\n name: ""\n optimistic: true\n' + errors = config_errors( + write_config( + tmp_path, + ESP32_HEADER, + wizard_api("- entities: [{id: nameless}]"), + yaml_entities, + ) + ) + + assert any("has no name of its own" in error for error in errors), errors + + +def test_a_wizard_too_big_for_one_message_is_rejected(tmp_path: Path) -> None: + # Random text does not compress, so this needs more than the limit even compressed + rng = random.Random(1) + alphabet = string.ascii_letters + string.digits + entities = ",\n".join( + "{id: sw, description: " + + "".join(rng.choice(alphabet) for _ in range(255)) + + "}" + for _ in range(400) + ) + api = wizard_api(f"- entities: [\n{entities}\n]") + errors = config_errors(write_config(tmp_path, ESP32_HEADER, api)) + + assert any( + "The compressed wizard is" in error and "bytes over the 65512 bytes" in error + for error in errors + ), errors + + +def test_repeated_text_compresses_well_inside_the_limit(tmp_path: Path) -> None: + # 400 identical entities are far over the limit as JSON, but compress to very little + entities = ",\n".join(["{id: sw, description: " + "x" * 255 + "}"] * 400) + api = wizard_api(f"- entities: [\n{entities}\n]") + + assert config_errors(write_config(tmp_path, ESP32_HEADER, api)) == [] + + +@pytest.mark.parametrize( + ("platform", "entity_id"), + [ + ("sensor", "sensor.a"), + ("switch", "light.a"), + ("text", "text.a"), + ("button", "button.a"), + ], +) +def test_a_linked_entity_must_not_set_an_entity_id( + tmp_path: Path, platform: str, entity_id: str +) -> None: + yaml_entities = ( + f"{platform}:\n - platform: homeassistant\n id: fixed\n" + f" entity_id: {entity_id}\n" + ) + errors = config_errors( + write_config( + tmp_path, ESP32_HEADER, wizard_inputs("- entity: fixed"), yaml_entities + ) + ) + + assert any( + "'fixed' has an entity_id set in its configuration" in error + and "Remove entity_id" in error + for error in errors + ), errors + + +def test_an_entity_with_an_entity_id_that_is_no_input_is_valid( + tmp_path: Path, generate_main: Callable[[str | Path], str] +) -> None: + main_cpp = generate_main(write_input_config(tmp_path, ESP32_HEADER)) + + # Static entries keep their literal and are kept apart from the wizard buffers + assert 'ha_plain_sensor->set_entity_id("sensor.plain");' in main_cpp + assert "api_wizard_input_ha_plain_sensor" not in main_cpp + # Every buffer starts empty + assert '] = "sensor.' not in main_cpp.split("API_WIZARD_INPUTS")[0] diff --git a/tests/component_tests/automation/__init__.py b/tests/component_tests/automation/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/component_tests/automation/config/condition_groups.yaml b/tests/component_tests/automation/config/condition_groups.yaml new file mode 100644 index 0000000000..9624531862 --- /dev/null +++ b/tests/component_tests/automation/config/condition_groups.yaml @@ -0,0 +1,44 @@ +--- +esphome: + name: test + +esp32: + board: nodemcu-32s + +logger: + +binary_sensor: + - platform: template + id: single_and + on_press: + - if: + condition: + - lambda: return true; + then: + - logger.log: single and + - platform: template + id: single_or + on_press: + - if: + any: + - lambda: return true; + then: + - logger.log: single or + - platform: template + id: double_and + on_press: + - if: + condition: + - lambda: return true; + - lambda: return false; + then: + - logger.log: double and + - platform: template + id: single_xor + on_press: + - if: + condition: + xor: + - lambda: return true; + then: + - logger.log: single xor diff --git a/tests/component_tests/automation/test_condition_groups.py b/tests/component_tests/automation/test_condition_groups.py new file mode 100644 index 0000000000..effe7b5b0b --- /dev/null +++ b/tests/component_tests/automation/test_condition_groups.py @@ -0,0 +1,25 @@ +"""Tests for and/or condition group codegen.""" + +from collections.abc import Callable +from pathlib import Path +import re + + +def test_single_condition_groups_are_unwrapped( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """A group of one condition is passed to the action directly.""" + main_cpp = generate_main(component_config_path("condition_groups.yaml")) + + assert "IfAction(lambdacondition_id);" in main_cpp + assert "IfAction(lambdacondition_id_2);" in main_cpp + group = re.search( + r"new\((\w+)\) AndCondition<2>\(\{lambdacondition_id_3, lambdacondition_id_4\}\);", + main_cpp, + ) + assert group is not None + assert f"IfAction({group.group(1)});" in main_cpp + # xor of one condition equals that condition, so it is unwrapped too. + assert "IfAction(lambdacondition_id_5);" in main_cpp + assert "XorCondition<" not in main_cpp diff --git a/tests/component_tests/canbus/__init__.py b/tests/component_tests/canbus/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/component_tests/canbus/config/payload_tables.yaml b/tests/component_tests/canbus/config/payload_tables.yaml new file mode 100644 index 0000000000..abbf0bab8a --- /dev/null +++ b/tests/component_tests/canbus/config/payload_tables.yaml @@ -0,0 +1,21 @@ +--- +esphome: + name: test + on_boot: + then: + - canbus.send: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + - canbus.send: + can_id: 0x100 + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + - canbus.send: !lambda return {0x09}; + +esp32: + board: esp32dev + +canbus: + - platform: esp32_can + id: can_bus + rx_pin: 4 + tx_pin: 5 + can_id: 4 + bit_rate: 50kbps diff --git a/tests/component_tests/canbus/test_canbus_payload_tables.py b/tests/component_tests/canbus/test_canbus_payload_tables.py new file mode 100644 index 0000000000..958ec082e4 --- /dev/null +++ b/tests/component_tests/canbus/test_canbus_payload_tables.py @@ -0,0 +1,41 @@ +"""Tests for canbus constant payloads in shared PROGMEM tables.""" + +from collections.abc import Callable +from pathlib import Path +import re + +import pytest + +from esphome.components.canbus import CANBUS_SEND_ACTION_SCHEMA +import esphome.config_validation as cv + + +def test_constant_payloads_share_progmem_tables( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Equal send payloads share one table; lambdas stay templates.""" + main_cpp = generate_main(component_config_path("payload_tables.yaml")) + + tables = re.findall( + r"static constexpr uint8_t (\w+)\[\] PROGMEM = (\{[^}]*\});", main_cpp + ) + assert [v for _, v in tables] == [ + "{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}" + ] + assert main_cpp.count(f"set_data_static({tables[0][0]}, 8);") == 2 + assert "set_data_template(" in main_cpp + + +@pytest.mark.parametrize("size", [8, 9]) +@pytest.mark.parametrize("make", [lambda n: [0x01] * n, lambda n: "a" * n]) +def test_send_one_frame_limit( + size: int, make: Callable[[int], list[int] | str] +) -> None: + """Payloads up to one 8 byte CAN frame are accepted, longer ones rejected.""" + config = {"canbus_id": "can_bus", "data": make(size)} + if size <= 8: + assert len(CANBUS_SEND_ACTION_SCHEMA(config)["data"]) == size + else: + with pytest.raises(cv.Invalid, match="at most 8"): + CANBUS_SEND_ACTION_SCHEMA(config) diff --git a/tests/component_tests/cc1101/__init__.py b/tests/component_tests/cc1101/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/component_tests/cc1101/config/payload_tables.yaml b/tests/component_tests/cc1101/config/payload_tables.yaml new file mode 100644 index 0000000000..b478cd6bdb --- /dev/null +++ b/tests/component_tests/cc1101/config/payload_tables.yaml @@ -0,0 +1,25 @@ +--- +esphome: + name: test + on_boot: + then: + - cc1101.send_packet: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + - cc1101.send_packet: + data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] + - cc1101.send_packet: !lambda return {0x09}; + +esp32: + board: esp32dev + +spi: + clk_pin: 18 + mosi_pin: 23 + miso_pin: 19 + +cc1101: + id: transceiver + cs_pin: 5 + gdo0_pin: 4 + frequency: 433.92MHz + packet_mode: true + packet_length: 8 diff --git a/tests/component_tests/cc1101/test_cc1101_payload_tables.py b/tests/component_tests/cc1101/test_cc1101_payload_tables.py new file mode 100644 index 0000000000..f2d3264a32 --- /dev/null +++ b/tests/component_tests/cc1101/test_cc1101_payload_tables.py @@ -0,0 +1,41 @@ +"""Tests for cc1101 constant payloads in shared PROGMEM tables.""" + +from collections.abc import Callable +from pathlib import Path +import re + +import pytest + +from esphome.components.cc1101 import SEND_PACKET_ACTION_SCHEMA +import esphome.config_validation as cv + + +def test_constant_payloads_share_progmem_tables( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Equal send_packet payloads share one table; lambdas stay templates.""" + main_cpp = generate_main(component_config_path("payload_tables.yaml")) + + tables = re.findall( + r"static constexpr uint8_t (\w+)\[\] PROGMEM = (\{[^}]*\});", main_cpp + ) + assert [v for _, v in tables] == [ + "{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}" + ] + assert main_cpp.count(f"set_data_static({tables[0][0]}, 8);") == 2 + assert "set_data_template(" in main_cpp + + +@pytest.mark.parametrize("size", [64, 65]) +@pytest.mark.parametrize("make", [lambda n: [0x01] * n, lambda n: "a" * n]) +def test_send_packet_tx_fifo_limit( + size: int, make: Callable[[int], list[int] | str] +) -> None: + """Payloads up to the 64 byte TX FIFO are accepted, longer ones rejected.""" + config = {"id": "transceiver", "data": make(size)} + if size <= 64: + assert len(SEND_PACKET_ACTION_SCHEMA(config)["data"]) == size + else: + with pytest.raises(cv.Invalid, match="at most 64"): + SEND_PACKET_ACTION_SCHEMA(config) diff --git a/tests/component_tests/dlms_meter/__init__.py b/tests/component_tests/dlms_meter/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/component_tests/dlms_meter/test_provider.py b/tests/component_tests/dlms_meter/test_provider.py new file mode 100644 index 0000000000..9ce07f118a --- /dev/null +++ b/tests/component_tests/dlms_meter/test_provider.py @@ -0,0 +1,17 @@ +"""Tests for the dlms_meter provider option removed in 2026.11.0.""" + +import pytest +from voluptuous import Invalid, MultipleInvalid + +from esphome.components.dlms_meter import CONFIG_SCHEMA + + +def test_removed_provider_shows_the_netznoe_patterns() -> None: + with pytest.raises(Invalid) as exc_info: + CONFIG_SCHEMA({"provider": "netznoe"}) + errors = ( + exc_info.value.errors + if isinstance(exc_info.value, MultipleInvalid) + else [exc_info.value] + ) + assert any('pattern: "L, TSTR"' in str(error) for error in errors) diff --git a/tests/component_tests/dlms_meter/test_removed_keys.py b/tests/component_tests/dlms_meter/test_removed_keys.py new file mode 100644 index 0000000000..f82ec79bff --- /dev/null +++ b/tests/component_tests/dlms_meter/test_removed_keys.py @@ -0,0 +1,33 @@ +"""Tests for the dlms_meter predefined keys removed in 2026.11.0.""" + +from collections.abc import Callable + +import pytest +from voluptuous import Invalid, MultipleInvalid + +from esphome.components.dlms_meter.sensor import CONFIG_SCHEMA as SENSOR_SCHEMA +from esphome.components.dlms_meter.text_sensor import ( + CONFIG_SCHEMA as TEXT_SENSOR_SCHEMA, +) +from esphome.types import ConfigType + + +def _errors(schema: Callable[[ConfigType], ConfigType], config: ConfigType) -> str: + with pytest.raises(Invalid) as exc_info: + schema(config) + errors = ( + exc_info.value.errors + if isinstance(exc_info.value, MultipleInvalid) + else [exc_info.value] + ) + return "\n".join(str(error) for error in errors) + + +def test_removed_sensor_key_names_its_replacement() -> None: + errors = _errors(SENSOR_SCHEMA, {"voltage_l1": {"name": "Voltage"}}) + assert 'obis_code: "1.0.32.7.0.255" and unit_of_measurement: V' in errors + + +def test_removed_text_sensor_key_names_its_replacement() -> None: + errors = _errors(TEXT_SENSOR_SCHEMA, {"timestamp": {"name": "Timestamp"}}) + assert 'obis_code: "0.0.1.0.0.255"' in errors diff --git a/tests/component_tests/epaper_spi/config/full_update_next_test.yaml b/tests/component_tests/epaper_spi/config/full_update_next_test.yaml index de5e678a70..ab8389798b 100644 --- a/tests/component_tests/epaper_spi/config/full_update_next_test.yaml +++ b/tests/component_tests/epaper_spi/config/full_update_next_test.yaml @@ -27,3 +27,8 @@ binary_sensor: name: Trigger on_press: - epaper_spi.full_update_next: epaper_display + - if: + condition: + epaper_spi.is_updating: epaper_display + then: + - component.update: epaper_display diff --git a/tests/component_tests/epaper_spi/test_init.py b/tests/component_tests/epaper_spi/test_init.py index 9d6ebe831a..4b8f578892 100644 --- a/tests/component_tests/epaper_spi/test_init.py +++ b/tests/component_tests/epaper_spi/test_init.py @@ -551,6 +551,16 @@ def test_full_update_next_action_code_generation( assert "epaper_display->request_full_update();" in main_cpp +def test_is_updating_condition_code_generation( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """The epaper_spi.is_updating condition checks the configured display.""" + main_cpp = generate_main(component_config_path("full_update_next_test.yaml")) + + assert "epaper_display->is_updating()" in main_cpp + + def test_model_with_no_default_init_sequence_generates( generate_main: Callable[[str | Path], str], component_config_path: Callable[[str], Path], diff --git a/tests/component_tests/esp32/config/mbedtls_tls_wifi_eap.yaml b/tests/component_tests/esp32/config/mbedtls_tls_wifi_eap.yaml index 6c78e06265..ab77a1808e 100644 --- a/tests/component_tests/esp32/config/mbedtls_tls_wifi_eap.yaml +++ b/tests/component_tests/esp32/config/mbedtls_tls_wifi_eap.yaml @@ -12,6 +12,3 @@ wifi: identity: "user@example.org" username: "user" password: "secret" - -http_request: - verify_ssl: true diff --git a/tests/component_tests/esp32/config/tls_keep_opt_out.yaml b/tests/component_tests/esp32/config/tls_keep_opt_out.yaml new file mode 100644 index 0000000000..58b6692a8d --- /dev/null +++ b/tests/component_tests/esp32/config/tls_keep_opt_out.yaml @@ -0,0 +1,9 @@ +esphome: + name: test + +esp32: + board: esp32dev + framework: + type: esp-idf + advanced: + disable_mbedtls_tls: false diff --git a/tests/component_tests/esp32/config/tls_sdkconfig_esp_tls.yaml b/tests/component_tests/esp32/config/tls_sdkconfig_esp_tls.yaml new file mode 100644 index 0000000000..888a62b849 --- /dev/null +++ b/tests/component_tests/esp32/config/tls_sdkconfig_esp_tls.yaml @@ -0,0 +1,9 @@ +esphome: + name: test + +esp32: + board: esp32dev + framework: + type: esp-idf + sdkconfig_options: + CONFIG_ESP_TLS_INSECURE: y diff --git a/tests/component_tests/esp32/config/tls_sdkconfig_tls_enabled_n.yaml b/tests/component_tests/esp32/config/tls_sdkconfig_tls_enabled_n.yaml new file mode 100644 index 0000000000..5bd4e97130 --- /dev/null +++ b/tests/component_tests/esp32/config/tls_sdkconfig_tls_enabled_n.yaml @@ -0,0 +1,9 @@ +esphome: + name: test + +esp32: + board: esp32dev + framework: + type: esp-idf + sdkconfig_options: + CONFIG_MBEDTLS_TLS_ENABLED: n diff --git a/tests/component_tests/esp32/test_esp32.py b/tests/component_tests/esp32/test_esp32.py index b41234db1a..04416d7f57 100644 --- a/tests/component_tests/esp32/test_esp32.py +++ b/tests/component_tests/esp32/test_esp32.py @@ -11,25 +11,30 @@ from typing import Any import pytest from esphome.components.esp32 import ( + _ESP_TLS_LINKING_COMPONENTS, + DEFAULT_EXCLUDED_IDF_COMPONENTS, ESP32_FLASH_CHIPS, KEY_FATFS_REQUIRED, - KEY_MBEDTLS_TLS_EXTRAS_REQUIRED, - KEY_MBEDTLS_TLS_SERVER_REQUIRED, KEY_VFS_DIR_REQUIRED, KEY_VFS_SELECT_REQUIRED, KEY_VFS_TERMIOS_REQUIRED, MBEDTLS_TLS_EXTRA_OPTIONS, VARIANT_ESP32, VARIANTS, + MbedtlsSdkconfigData, NetworkSdkconfigData, RawSdkconfigValue, _ota_downgrade_protection_errors, + _reconcile_mbedtls_sdkconfig, _reconcile_network_sdkconfig, _reconcile_vfs_fatfs_sdkconfig, + _user_sdkconfig_wants_tls, ) from esphome.components.esp32.const import ( KEY_ESP32, KEY_EXCLUDE_COMPONENTS, + KEY_IDF_VERSION, + KEY_MBEDTLS_SDKCONFIG, KEY_NETWORK_SDKCONFIG, KEY_SDKCONFIG_OPTIONS, KEY_VARIANT, @@ -322,8 +327,8 @@ def test_esp32_configuration_errors( ("esp_driver_i2c", "esp_driver_ledc", "esp_driver_gptimer"), id="i2c_ledc_ac_dimmer", ), - # esp-tls has three owners; a per-owner config makes a dropped - # re-include from any single one fail the test. + # esp-tls comes back through request_tls(); a per-owner config makes + # a dropped request from any single one fail the test. pytest.param( "exclusion_reincludes_http_request.yaml", ("esp-tls", "esp_http_client"), @@ -337,8 +342,9 @@ def test_esp32_configuration_errors( id="mqtt", ), pytest.param( + # Basic auth uses mbedtls_base64_encode directly, so no esp-tls. "exclusion_reincludes_web_server.yaml", - ("esp-tls", "esp_http_server"), + ("esp_http_server",), id="web_server_idf", ), pytest.param( @@ -480,6 +486,303 @@ def test_user_sdkconfig_certificate_bundle_wins( assert sdkconfig.get("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL") is False +_TLS_OFF_CRYPTO = { + "CONFIG_MBEDTLS_ECP_C": False, + "CONFIG_MBEDTLS_PEM_WRITE_C": False, + "CONFIG_MBEDTLS_X509_CRL_PARSE_C": False, + "CONFIG_MBEDTLS_X509_CSR_PARSE_C": False, +} +_TLS_OFF_IDF5 = { + "CONFIG_MBEDTLS_TLS_DISABLED": True, + "CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT": False, + **_TLS_OFF_CRYPTO, +} +_TLS_OFF_IDF6 = { + "CONFIG_MBEDTLS_TLS_ENABLED": False, + "CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT": False, + **_TLS_OFF_CRYPTO, +} +_PEER_CERT_PKCS7_OFF = { + "CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE": False, + "CONFIG_MBEDTLS_PKCS7_C": False, +} +_TLS_EXTRAS_OFF = dict.fromkeys(MBEDTLS_TLS_EXTRA_OPTIONS, False) +_TLS_CLIENT_ONLY = { + "CONFIG_MBEDTLS_TLS_CLIENT_ONLY": True, + "CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT": False, +} +_IDF5 = cv.Version(5, 5, 5) +_IDF6 = cv.Version(6, 0, 0) + + +@pytest.mark.parametrize( + ("framework", "idf", "data", "preset", "expected", "excluded"), + [ + pytest.param( + PlatformFramework.ESP32_IDF, + _IDF5, + MbedtlsSdkconfigData(), + {}, + {**_TLS_OFF_IDF5, **_TLS_EXTRAS_OFF, **_PEER_CERT_PKCS7_OFF}, + set(_ESP_TLS_LINKING_COMPONENTS), + id="idf5_no_tls_user", + ), + pytest.param( + # An external component that only re-included esp-tls keeps TLS. + PlatformFramework.ESP32_IDF, + _IDF5, + MbedtlsSdkconfigData(), + {}, + {**_TLS_CLIENT_ONLY, **_TLS_EXTRAS_OFF, **_PEER_CERT_PKCS7_OFF}, + set(_ESP_TLS_LINKING_COMPONENTS) - {"esp-tls"}, + id="idf_esp_tls_reincluded", + ), + pytest.param( + # esp_http_client links esp_tls itself, so re-including it counts too. + PlatformFramework.ESP32_IDF, + _IDF5, + MbedtlsSdkconfigData(), + {}, + {**_TLS_CLIENT_ONLY, **_TLS_EXTRAS_OFF, **_PEER_CERT_PKCS7_OFF}, + set(_ESP_TLS_LINKING_COMPONENTS) - {"esp_http_client"}, + id="idf_http_client_reincluded", + ), + pytest.param( + PlatformFramework.ESP32_IDF, + _IDF6, + MbedtlsSdkconfigData(), + {}, + { + **_TLS_OFF_IDF6, + **_TLS_EXTRAS_OFF, + **_PEER_CERT_PKCS7_OFF, + "CONFIG_MBEDTLS_SHA384_C": False, + "CONFIG_MBEDTLS_SHA512_C": False, + }, + set(_ESP_TLS_LINKING_COMPONENTS), + id="idf6_drops_sha512", + ), + pytest.param( + PlatformFramework.ESP32_IDF, + _IDF6, + MbedtlsSdkconfigData(sha512_required=True), + {}, + {**_TLS_OFF_IDF6, **_TLS_EXTRAS_OFF, **_PEER_CERT_PKCS7_OFF}, + set(_ESP_TLS_LINKING_COMPONENTS), + id="idf6_sha512_required", + ), + pytest.param( + PlatformFramework.ESP32_IDF, + _IDF5, + MbedtlsSdkconfigData(ecp_required=True), + {}, + { + **{ + k: v + for k, v in _TLS_OFF_IDF5.items() + if k != "CONFIG_MBEDTLS_ECP_C" + }, + **_TLS_EXTRAS_OFF, + **_PEER_CERT_PKCS7_OFF, + }, + set(_ESP_TLS_LINKING_COMPONENTS), + id="idf_ecp_without_tls", + ), + pytest.param( + PlatformFramework.ESP32_IDF, + _IDF5, + MbedtlsSdkconfigData(), + {"CONFIG_MBEDTLS_ECP_C": RawSdkconfigValue("y")}, + { + **_TLS_OFF_IDF5, + "CONFIG_MBEDTLS_ECP_C": RawSdkconfigValue("y"), + **_TLS_EXTRAS_OFF, + **_PEER_CERT_PKCS7_OFF, + }, + set(_ESP_TLS_LINKING_COMPONENTS), + id="idf_user_ecp_wins", + ), + pytest.param( + PlatformFramework.ESP32_IDF, + _IDF5, + MbedtlsSdkconfigData(peer_cert_required=True, pkcs7_required=True), + {}, + { + **_TLS_OFF_IDF5, + **_TLS_EXTRAS_OFF, + "CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE": True, + "CONFIG_MBEDTLS_PKCS7_C": True, + }, + set(_ESP_TLS_LINKING_COMPONENTS), + id="idf_peer_cert_pkcs7_required", + ), + pytest.param( + PlatformFramework.ESP32_IDF, + _IDF5, + MbedtlsSdkconfigData(disable_peer_cert=False, disable_pkcs7=False), + {}, + {**_TLS_OFF_IDF5, **_TLS_EXTRAS_OFF}, + set(_ESP_TLS_LINKING_COMPONENTS), + id="idf_advanced_disables_off", + ), + pytest.param( + # advanced: disable_mbedtls_tls: false keeps TLS with no requester. + PlatformFramework.ESP32_IDF, + _IDF5, + MbedtlsSdkconfigData(disable_tls=False), + {}, + {**_TLS_CLIENT_ONLY, **_TLS_EXTRAS_OFF, **_PEER_CERT_PKCS7_OFF}, + set(_ESP_TLS_LINKING_COMPONENTS), + id="idf_disable_tls_opt_out", + ), + pytest.param( + # require_mbedtls_tls() keeps TLS with every wrapper still excluded. + PlatformFramework.ESP32_IDF, + _IDF5, + MbedtlsSdkconfigData(tls_required=True), + {}, + {**_TLS_CLIENT_ONLY, **_TLS_EXTRAS_OFF, **_PEER_CERT_PKCS7_OFF}, + set(_ESP_TLS_LINKING_COMPONENTS), + id="idf_require_mbedtls_tls", + ), + pytest.param( + # TLS kept: a required server role blocks the client-only trim. + PlatformFramework.ESP32_IDF, + _IDF5, + MbedtlsSdkconfigData(tls_server_required=True), + {}, + {**_TLS_EXTRAS_OFF, **_PEER_CERT_PKCS7_OFF}, + set(_ESP_TLS_LINKING_COMPONENTS) - {"esp-tls"}, + id="idf_tls_server_required", + ), + pytest.param( + PlatformFramework.ESP32_ARDUINO, + _IDF5, + MbedtlsSdkconfigData(), + {}, + {**_TLS_CLIENT_ONLY, **_TLS_EXTRAS_OFF, **_PEER_CERT_PKCS7_OFF}, + set(_ESP_TLS_LINKING_COMPONENTS), + id="arduino_keeps_tls", + ), + ], +) +def test_reconcile_mbedtls_sdkconfig( + set_core_config: SetCoreConfigCallable, + framework: PlatformFramework, + idf: cv.Version, + data: MbedtlsSdkconfigData, + preset: dict[str, Any], + expected: dict[str, Any], + excluded: set[str], +) -> None: + """The FINAL-priority reconciler turns TLS off only when nothing requested it; + user sdkconfig_options always win.""" + set_core_config(framework) + CORE.data[KEY_ESP32] = { + KEY_IDF_VERSION: idf, + KEY_SDKCONFIG_OPTIONS: dict(preset), + KEY_MBEDTLS_SDKCONFIG: data, + KEY_EXCLUDE_COMPONENTS: excluded, + } + + asyncio.run(_reconcile_mbedtls_sdkconfig()) + + assert CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] == expected + + +def test_esp_tls_linking_components_are_excluded_by_default() -> None: + """The fallback scan is only a real signal while every name is excluded by default.""" + assert set(_ESP_TLS_LINKING_COMPONENTS) <= set(DEFAULT_EXCLUDED_IDF_COMPONENTS) + + +@pytest.mark.parametrize( + ("options", "wants_tls"), + [ + pytest.param({}, False, id="empty"), + pytest.param({"CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT": "y"}, True, id="role_y"), + pytest.param({"CONFIG_MBEDTLS_TLS_ENABLED": "n"}, False, id="enabled_n"), + pytest.param({"CONFIG_MBEDTLS_TLS_DISABLED": "n"}, True, id="disabled_n"), + pytest.param({"CONFIG_ESP_TLS_INSECURE": "y"}, True, id="esp_tls_prefix"), + pytest.param( + {"CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE": "n"}, + False, + id="prefix_n_is_not_a_request", + ), + pytest.param({"CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP": "y"}, True, id="https_prefix"), + pytest.param({"CONFIG_OPENTHREAD_COMMISSIONER": "y"}, True, id="ot_dtls_y"), + pytest.param({"CONFIG_OPENTHREAD_JOINER": "n"}, False, id="ot_dtls_n"), + pytest.param({"CONFIG_OPENTHREAD_BORDER_ROUTER": "y"}, True, id="ot_br_y"), + pytest.param( + {"CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT": "y"}, True, id="wifi_enterprise_y" + ), + pytest.param({"CONFIG_LWIP_IPV6": "y"}, False, id="unrelated"), + ], +) +def test_user_sdkconfig_wants_tls(options: dict[str, Any], wants_tls: bool) -> None: + """The sdkconfig escape hatch reads values, never bare key presence.""" + assert _user_sdkconfig_wants_tls(options) is wants_tls + + +@pytest.mark.parametrize( + ("config_file", "tls_off", "ecp_off", "esp_tls_excluded"), + [ + pytest.param("network_ethernet_only.yaml", True, True, True, id="ethernet_api"), + pytest.param( + "exclusion_reincludes_web_server.yaml", + True, + True, + True, + id="web_server_idf", + ), + pytest.param( + "exclusion_reincludes_http_request.yaml", + False, + False, + False, + id="http_request", + ), + pytest.param("exclusion_reincludes_mqtt.yaml", False, False, False, id="mqtt"), + pytest.param( + "exclusion_reincludes_nextion.yaml", False, False, False, id="nextion" + ), + pytest.param("mbedtls_tls_wifi_eap.yaml", False, False, True, id="wifi_eap"), + # zigbee requests ECP for the esp-zigbee-lib blobs, without TLS. + pytest.param("tls_zigbee_c6.yaml", True, False, True, id="zigbee"), + # A raw bundle keeps the TLS role but no longer compiles esp-tls. + pytest.param( + "certificate_bundle_sdkconfig.yaml", False, False, True, id="raw_bundle" + ), + pytest.param( + "tls_sdkconfig_esp_tls.yaml", False, False, False, id="raw_esp_tls" + ), + # A role option set to n is not a request. + pytest.param( + "tls_sdkconfig_tls_enabled_n.yaml", True, True, True, id="raw_tls_enabled_n" + ), + # ECDSA signed OTA requests ECP itself (SECURE_SIGNED_APPS selects it too). + pytest.param( + "signed_ota_ecdsa256_c6.yaml", True, False, True, id="signed_ota_ecdsa" + ), + ], +) +def test_tls_disabled_sdkconfig( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], + config_file: str, + tls_off: bool, + ecp_off: bool, + esp_tls_excluded: bool, +) -> None: + """TLS is compiled out unless a component or a raw sdkconfig option asks for it.""" + generate_main(component_config_path(config_file)) + sdkconfig = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] + assert (sdkconfig.get("CONFIG_MBEDTLS_TLS_DISABLED") is True) is tls_off + assert sdkconfig.get("CONFIG_MBEDTLS_ECP_C") is (False if ecp_off else None) + assert ( + "esp-tls" in CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS] + ) is esp_tls_excluded + + def test_execute_from_psram_s3_sdkconfig( generate_main: Callable[[str | Path], str], component_config_path: Callable[[str], Path], @@ -1496,9 +1799,14 @@ def test_mbedtls_tls_openthread_keeps_only_what_it_uses( generate_main: Callable[[str | Path], str], component_config_path: Callable[[str], Path], ) -> None: - """The OpenThread config keeps the DTLS server, CCM and deterministic ECDSA; the rest is trimmed.""" + """Nothing in the OpenThread config links TLS, so the stack is compiled out + and no TLS role is written; the extras trim still runs because CCM and + deterministic ECDSA are plain crypto, and OpenThread keeps those two.""" generate_main(component_config_path("mbedtls_tls_openthread.yaml")) sdkconfig = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] + assert sdkconfig.get("CONFIG_MBEDTLS_TLS_DISABLED") is True + # require_mbedtls_ecp() keeps ECP for the SRP host key while TLS is off + assert "CONFIG_MBEDTLS_ECP_C" not in sdkconfig assert tuple(sdkconfig.get(name) for name in _TLS_SERVER_OPTIONS) == (None, None) for name in MBEDTLS_TLS_EXTRA_OPTIONS: assert sdkconfig.get(name) is (None if name in _CCM_ECDSA_EXTRAS else False) @@ -1508,14 +1816,31 @@ def test_mbedtls_tls_zigbee_keeps_only_what_it_uses( generate_main: Callable[[str | Path], str], component_config_path: Callable[[str], Path], ) -> None: - """The Zigbee config keeps CCM and deterministic ECDSA; the rest is trimmed.""" + """Nothing in the Zigbee config links TLS, so the stack is compiled out and + no role is written; the extras trim still runs and Zigbee keeps CCM and + deterministic ECDSA.""" generate_main(component_config_path("tls_zigbee_c6.yaml")) sdkconfig = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] - assert tuple(sdkconfig.get(name) for name in _TLS_SERVER_OPTIONS) == (True, False) + assert sdkconfig.get("CONFIG_MBEDTLS_TLS_DISABLED") is True + assert tuple(sdkconfig.get(name) for name in _TLS_SERVER_OPTIONS) == (None, None) for name in MBEDTLS_TLS_EXTRA_OPTIONS: assert sdkconfig.get(name) is (None if name in _CCM_ECDSA_EXTRAS else False) +def test_mbedtls_tls_opt_out_keeps_stack_and_trims_role( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """disable_mbedtls_tls: false keeps TLS with no requester; the client-only + and extras trims then still apply.""" + generate_main(component_config_path("tls_keep_opt_out.yaml")) + sdkconfig = CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] + assert "CONFIG_MBEDTLS_TLS_DISABLED" not in sdkconfig + assert "CONFIG_MBEDTLS_ECP_C" not in sdkconfig + assert sdkconfig.get("CONFIG_MBEDTLS_TLS_CLIENT_ONLY") is True + assert sdkconfig.get("CONFIG_MBEDTLS_SSL_RENEGOTIATION") is False + + def test_mbedtls_tls_user_sdkconfig_wins( generate_main: Callable[[str | Path], str], component_config_path: Callable[[str], Path], @@ -1541,8 +1866,9 @@ def test_mbedtls_tls_openthread_requires_server_and_extras( ) -> None: """The OpenThread hooks mark the DTLS server and CCM/deterministic ECDSA as required.""" generate_main(component_config_path("mbedtls_tls_openthread.yaml")) - assert CORE.data[KEY_ESP32][KEY_MBEDTLS_TLS_SERVER_REQUIRED] is True - assert CORE.data[KEY_ESP32][KEY_MBEDTLS_TLS_EXTRAS_REQUIRED] == _CCM_ECDSA_EXTRAS + mbedtls = CORE.data[KEY_ESP32][KEY_MBEDTLS_SDKCONFIG] + assert mbedtls.tls_server_required is True + assert mbedtls.tls_extras_required == _CCM_ECDSA_EXTRAS def test_mbedtls_tls_zigbee_requires_extras( @@ -1551,7 +1877,8 @@ def test_mbedtls_tls_zigbee_requires_extras( ) -> None: """The Zigbee hooks mark the CCM/deterministic ECDSA as required.""" generate_main(component_config_path("tls_zigbee_c6.yaml")) - assert CORE.data[KEY_ESP32][KEY_MBEDTLS_TLS_EXTRAS_REQUIRED] == _CCM_ECDSA_EXTRAS + mbedtls = CORE.data[KEY_ESP32][KEY_MBEDTLS_SDKCONFIG] + assert mbedtls.tls_extras_required == _CCM_ECDSA_EXTRAS _VASPRINTF_STUB_FLAGS = {"-Wl,--wrap=vasprintf", "-Wl,--undefined=__wrap_vasprintf"} diff --git a/tests/component_tests/esp8266/__init__.py b/tests/component_tests/esp8266/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/component_tests/esp8266/config/minimal.yaml b/tests/component_tests/esp8266/config/minimal.yaml new file mode 100644 index 0000000000..79fde0157b --- /dev/null +++ b/tests/component_tests/esp8266/config/minimal.yaml @@ -0,0 +1,5 @@ +esphome: + name: test + +esp8266: + board: d1_mini diff --git a/tests/component_tests/esp8266/test_build_flags.py b/tests/component_tests/esp8266/test_build_flags.py new file mode 100644 index 0000000000..938089333b --- /dev/null +++ b/tests/component_tests/esp8266/test_build_flags.py @@ -0,0 +1,17 @@ +"""Tests for ESP8266 build flags.""" + +from __future__ import annotations + +from collections.abc import Callable +from pathlib import Path + +from esphome.core import CORE + + +def test_esp8266_keeps_switches_out_of_ram( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + generate_main(component_config_path("minimal.yaml")) + + assert "-fno-tree-switch-conversion" in CORE.build_flags diff --git a/tests/component_tests/espnow/config/payload_tables.yaml b/tests/component_tests/espnow/config/payload_tables.yaml new file mode 100644 index 0000000000..b94ef5bf87 --- /dev/null +++ b/tests/component_tests/espnow/config/payload_tables.yaml @@ -0,0 +1,22 @@ +--- +esphome: + name: test + on_boot: + then: + - espnow.send: + address: 11:22:33:44:55:66 + data: [0x01, 0x02, 0x03] + - espnow.broadcast: + data: [0x01, 0x02, 0x03] + - espnow.broadcast: "OK" + - espnow.broadcast: + data: !lambda return {0x09}; + +esp32: + board: esp32dev + +wifi: + ssid: test + password: password1 + +espnow: diff --git a/tests/component_tests/espnow/test_payload_tables.py b/tests/component_tests/espnow/test_payload_tables.py new file mode 100644 index 0000000000..d696b2e979 --- /dev/null +++ b/tests/component_tests/espnow/test_payload_tables.py @@ -0,0 +1,23 @@ +"""Tests for ESP-NOW constant send payloads in shared flash tables.""" + +from collections.abc import Callable +from pathlib import Path +import re + + +def test_constant_payloads_share_tables( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Equal payloads share one table; lambdas stay templates.""" + main_cpp = generate_main(component_config_path("payload_tables.yaml")) + + tables = dict( + re.findall( + r"static constexpr uint8_t (\w+)\[\] PROGMEM = (\{[^}]*\});", main_cpp + ) + ) + assert sorted(tables.values()) == sorted(["{0x01, 0x02, 0x03}", "{0x4F, 0x4B}"]) + shared = next(k for k, v in tables.items() if v == "{0x01, 0x02, 0x03}") + assert main_cpp.count(f"set_data_static({shared}, 3);") == 2 + assert "set_data_template(" in main_cpp diff --git a/tests/component_tests/ethernet/test_ethernet.py b/tests/component_tests/ethernet/test_ethernet.py index 522357f972..73985597d9 100644 --- a/tests/component_tests/ethernet/test_ethernet.py +++ b/tests/component_tests/ethernet/test_ethernet.py @@ -8,6 +8,7 @@ from esphome.components.esp32 import ( KEY_BOARD, KEY_IDF_VERSION, KEY_VARIANT, + VARIANT_ESP32, VARIANT_ESP32S3, ) from esphome.components.ethernet import CONF_CLOCK_SPEED, CONFIG_SCHEMA, _final_validate @@ -82,3 +83,40 @@ def test_ch390_rejects_clock_speed_above_the_datasheet_maximum( CORE.name = "ch390-test" with pytest.raises(Invalid, match="value must be at most 72000000"): CONFIG_SCHEMA({**_CH390_CONFIG, CONF_CLOCK_SPEED: "80MHz"}) + + +_LAN8720_CONFIG = {"type": "LAN8720", "mdc_pin": 23, "mdio_pin": 18} + + +def _set_esp32_core(set_core_config: SetCoreConfigCallable) -> None: + set_core_config( + PlatformFramework.ESP32_IDF, + platform_data={ + KEY_BOARD: "esp32dev", + KEY_VARIANT: VARIANT_ESP32, + KEY_IDF_VERSION: cv.Version(5, 3, 2), + }, + ) + CORE.name = "rmii-test" + + +def test_rmii_rejects_removed_clk_mode( + set_core_config: SetCoreConfigCallable, +) -> None: + """The removed clk_mode option points at the clk: replacement.""" + _set_esp32_core(set_core_config) + with pytest.raises(Invalid, match="GPIO17_OUT -> mode: CLK_OUT, pin: GPIO17"): + CONFIG_SCHEMA( + { + **_LAN8720_CONFIG, + "clk_mode": "GPIO17_OUT", + "clk": {"mode": "CLK_OUT", "pin": 17}, + } + ) + + +def test_rmii_requires_clk(set_core_config: SetCoreConfigCallable) -> None: + """An RMII PHY has no default clock, so clk must be given.""" + _set_esp32_core(set_core_config) + with pytest.raises(Invalid, match=r"required key not provided @ data\['clk'\]"): + CONFIG_SCHEMA(_LAN8720_CONFIG) diff --git a/tests/component_tests/mipi_dsi/test_mipi_dsi_config.py b/tests/component_tests/mipi_dsi/test_mipi_dsi_config.py index ac385cc2d8..0b15be3e3a 100644 --- a/tests/component_tests/mipi_dsi/test_mipi_dsi_config.py +++ b/tests/component_tests/mipi_dsi/test_mipi_dsi_config.py @@ -10,9 +10,11 @@ import pytest from esphome import config_validation as cv from esphome.components.esp32 import KEY_BOARD, VARIANT_ESP32P4 -# Importing xl9535 registers its pin schema with pins.PIN_SCHEMA_REGISTRY so that -# models (e.g. SEEED-RETERMINAL-D1001) that reference xl9535-backed pins in their -# defaults can be validated by the mipi_dsi CONFIG_SCHEMA in this test. +# Importing the I/O expanders registers their pin schemas with +# pins.PIN_SCHEMA_REGISTRY so that models whose defaults reference expander-backed +# pins (e.g. SEEED-RETERMINAL-D1001 uses xl9535, the M5Stack Tab5 models use +# pi4ioe5v6408) can be validated by the mipi_dsi CONFIG_SCHEMA in this test. +import esphome.components.pi4ioe5v6408 # noqa: F401 import esphome.components.xl9535 # noqa: F401 from esphome.const import ( CONF_DIMENSIONS, diff --git a/tests/component_tests/remote_transmitter/config/ir_codes.yaml b/tests/component_tests/remote_transmitter/config/ir_codes.yaml new file mode 100644 index 0000000000..69cf1fb039 --- /dev/null +++ b/tests/component_tests/remote_transmitter/config/ir_codes.yaml @@ -0,0 +1,43 @@ +esphome: + name: test + +esp32: + board: esp32dev + +remote_transmitter: + pin: GPIO4 + carrier_duty_percent: 50% + +button: + - platform: template + name: Midea A + on_press: + - remote_transmitter.transmit_midea: + code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF] + - platform: template + name: Midea B + on_press: + - remote_transmitter.transmit_midea: + code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF] + - platform: template + name: AEHA + on_press: + - remote_transmitter.transmit_aeha: + address: 0x8008 + data: [0x00, 0x02, 0xFD, 0xFF, 0x00, 0x33] + - platform: template + name: Haier lambda + on_press: + - remote_transmitter.transmit_haier: + code: !lambda |- + return {0xA6, 0xDA, 0x00, 0x00}; + - platform: template + name: Haier static + on_press: + - remote_transmitter.transmit_haier: + code: [0xA6, 0xDA, 0x00, 0x00, 0x40, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x05] + - platform: template + name: Mirage static + on_press: + - remote_transmitter.transmit_mirage: + code: [0x56, 0x6C, 0x00, 0x00, 0x20, 0x1A, 0x00, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x00, 0x42] diff --git a/tests/component_tests/remote_transmitter/test_ir_codes.py b/tests/component_tests/remote_transmitter/test_ir_codes.py new file mode 100644 index 0000000000..9be34941b6 --- /dev/null +++ b/tests/component_tests/remote_transmitter/test_ir_codes.py @@ -0,0 +1,24 @@ +"""Constant IR byte codes live in shared PROGMEM tables; lambdas stay lambdas.""" + +from collections.abc import Callable +from pathlib import Path +import re + + +def test_ir_codes_use_shared_progmem_tables( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + main_cpp = generate_main(component_config_path("ir_codes.yaml")) + + tables = re.findall( + r"static constexpr uint8_t (remote_base_code\w*)\[\] PROGMEM", main_cpp + ) + assert len(tables) == 4 # Midea (shared by two actions), AEHA, Haier, Mirage + midea = tables[0] + assert main_cpp.count(f"->set_code_static({midea}, 5);") == 2 + assert re.search(r"->set_data_static\(remote_base_code\w*, 6\);", main_cpp) + assert re.search(r"->set_code_static\(remote_base_code\w*, 13\);", main_cpp) + assert re.search(r"->set_code_static\(remote_base_code\w*, 14\);", main_cpp) + assert "->set_code_template([](" in main_cpp + assert "std::vector{" not in main_cpp diff --git a/tests/component_tests/rp2040_ble/test_connection_slots.py b/tests/component_tests/rp2040_ble/test_connection_slots.py index f33180e2d0..84f849760f 100644 --- a/tests/component_tests/rp2040_ble/test_connection_slots.py +++ b/tests/component_tests/rp2040_ble/test_connection_slots.py @@ -20,7 +20,7 @@ def test_proxy_claims_its_slots_through_the_shared_accounting( # A default (3-slot) proxy build records one claim per slot, attributed # to the consumer, and passes final validation. generate_main(component_config_path("rp2_proxy_default.yaml")) - used = CORE.data[rp2040_ble.KEY_RP2040_BLE][rp2040_ble.KEY_USED_CONNECTION_SLOTS] + used = CORE.data[rp2040_ble.DOMAIN][rp2040_ble.KEY_USED_CONNECTION_SLOTS] assert used == ["bluetooth_proxy"] * 3 diff --git a/tests/component_tests/sendspin/test_framework.py b/tests/component_tests/sendspin/test_framework.py new file mode 100644 index 0000000000..0add0d0f0a --- /dev/null +++ b/tests/component_tests/sendspin/test_framework.py @@ -0,0 +1,27 @@ +"""Validation tests for the frameworks the sendspin hub builds on. + +sendspin-cpp needs noise-c as an ESP-IDF component, which Arduino below IDF 6.0 +cannot use, so the hub is ESP-IDF only. +""" + +import pytest + +from esphome import config_validation as cv +from esphome.components.sendspin import CONFIG_SCHEMA +from esphome.const import PlatformFramework +from tests.component_tests.types import SetCoreConfigCallable + +HUB_CONFIG = {"id": "sendspin_hub_id"} + + +def test_arduino_rejected(set_core_config: SetCoreConfigCallable) -> None: + set_core_config(PlatformFramework.ESP32_ARDUINO) + + with pytest.raises(cv.Invalid, match="esp-idf"): + CONFIG_SCHEMA(HUB_CONFIG) + + +def test_esp_idf_accepted(set_core_config: SetCoreConfigCallable) -> None: + set_core_config(PlatformFramework.ESP32_IDF) + + assert CONFIG_SCHEMA(HUB_CONFIG)["id"].id == "sendspin_hub_id" diff --git a/tests/component_tests/sendspin/test_image.py b/tests/component_tests/sendspin/test_image.py index be3b7d6684..dc9e4978c6 100644 --- a/tests/component_tests/sendspin/test_image.py +++ b/tests/component_tests/sendspin/test_image.py @@ -53,6 +53,14 @@ def test_jpeg_alias_maps_to_one_enum( assert _get_data().artwork_preferences[0]["format"] == IMAGE_FORMAT_JPEG +def test_bmp_rejected(set_core_config: SetCoreConfigCallable) -> None: + """The Sendspin protocol only allows JPEG and PNG artwork.""" + set_core_config(PlatformFramework.ESP32_IDF) + + with pytest.raises(cv.Invalid, match="BMP artwork is no longer supported"): + CONFIG_SCHEMA(_slot_config(format="BMP")) + + def test_too_many_slots_rejected(set_core_config: SetCoreConfigCallable) -> None: """Slot numbers run out after MAX_ARTWORK_SLOTS entries.""" set_core_config(PlatformFramework.ESP32_IDF) diff --git a/tests/component_tests/sendspin/test_media_source.py b/tests/component_tests/sendspin/test_media_source.py index 6c2f79198d..db576b37af 100644 --- a/tests/component_tests/sendspin/test_media_source.py +++ b/tests/component_tests/sendspin/test_media_source.py @@ -88,3 +88,13 @@ def test_opus_at_wrong_sample_rate_rejected( with pytest.raises(cv.Invalid, match="requires a sample_rate of 48000"): CONFIG_SCHEMA(_media_source_config(codecs=["opus"], sample_rate=44100)) + + +def test_opus_only_codec_list_rejected( + set_core_config: SetCoreConfigCallable, +) -> None: + """Every server supports FLAC or PCM, so sendspin-cpp requires one of them.""" + set_core_config(PlatformFramework.ESP32_IDF) + + with pytest.raises(cv.Invalid, match="must include 'flac' or 'pcm'"): + CONFIG_SCHEMA(_media_source_config(codecs=["opus"])) diff --git a/tests/component_tests/sendspin/test_pairing.py b/tests/component_tests/sendspin/test_pairing.py new file mode 100644 index 0000000000..79612ccff5 --- /dev/null +++ b/tests/component_tests/sendspin/test_pairing.py @@ -0,0 +1,95 @@ +"""Validation tests for the sendspin hub's pairing options. + +These cover the rejection branches, which a compile test cannot reach: a +`test*.yaml` can only assert that a configuration is accepted. +""" + +from typing import Any + +import pytest + +from esphome import config_validation as cv +from esphome.components.sendspin import ( + CONF_STATIC_PAIRING_CODE, + CONFIG_SCHEMA, + FINAL_VALIDATE_SCHEMA, + request_pairing_code_display_support, +) +from esphome.const import PlatformFramework +from esphome.types import ConfigType +from tests.component_tests.types import SetCoreConfigCallable + + +def _hub_config(**overrides: Any) -> ConfigType: + """Build a minimal valid hub config, allowing field overrides.""" + config: ConfigType = {"id": "sendspin_hub_id"} + config.update(overrides) + return config + + +def test_minimal_config_is_accepted(set_core_config: SetCoreConfigCallable) -> None: + """The baseline the rejection tests vary is itself valid.""" + set_core_config(PlatformFramework.ESP32_IDF) + + config = CONFIG_SCHEMA(_hub_config()) + + assert CONF_STATIC_PAIRING_CODE not in config + + +@pytest.mark.parametrize("code", ["01234567", "00000000", "99999999"]) +def test_static_pairing_code_accepted( + set_core_config: SetCoreConfigCallable, code: str +) -> None: + """Eight decimal digits, leading zeros included, pass through unchanged.""" + set_core_config(PlatformFramework.ESP32_IDF) + + assert ( + CONFIG_SCHEMA(_hub_config(static_pairing_code=code))[CONF_STATIC_PAIRING_CODE] + == code + ) + + +@pytest.mark.parametrize( + "code", + [ + "0123456", # too short + "012345678", # too long + "0123456a", # not all decimal digits + "0123 567", # whitespace is not a digit + "๏ผ๏ผ‘๏ผ’๏ผ“๏ผ”๏ผ•๏ผ–๏ผ—", # full-width digits are not ASCII + "", + ], +) +def test_static_pairing_code_rejected( + set_core_config: SetCoreConfigCallable, code: str +) -> None: + """Anything that is not exactly eight decimal digits is refused here, since the + library would otherwise refuse to start the client.""" + set_core_config(PlatformFramework.ESP32_IDF) + + with pytest.raises(cv.Invalid, match="exactly 8 decimal digits"): + CONFIG_SCHEMA(_hub_config(static_pairing_code=code)) + + +def test_unquoted_static_pairing_code_rejected( + set_core_config: SetCoreConfigCallable, +) -> None: + """An unquoted YAML code arrives as an int, having already lost its leading + zeros, so it is refused rather than silently pairing with the wrong value.""" + set_core_config(PlatformFramework.ESP32_IDF) + + with pytest.raises(cv.Invalid): + CONFIG_SCHEMA(_hub_config(static_pairing_code=1234567)) + + +def test_static_code_with_dynamic_code_rejected( + set_core_config: SetCoreConfigCallable, +) -> None: + """Only one pairing code method can be offered, so a static code next to the dynamic + code would be silently ignored.""" + set_core_config(PlatformFramework.ESP32_IDF) + request_pairing_code_display_support() + config = CONFIG_SCHEMA(_hub_config(static_pairing_code="01234567")) + + with pytest.raises(cv.Invalid, match="cannot be used with a dynamic pairing code"): + FINAL_VALIDATE_SCHEMA(config) diff --git a/tests/component_tests/sendspin/test_text_sensor.py b/tests/component_tests/sendspin/test_text_sensor.py new file mode 100644 index 0000000000..6dc073a59c --- /dev/null +++ b/tests/component_tests/sendspin/test_text_sensor.py @@ -0,0 +1,60 @@ +"""Validation tests for the sendspin text sensor platform. + +These cover behavior a compile test cannot observe: which roles a sensor type requests. +""" + +from typing import Any + +import pytest + +from esphome import config_validation as cv +from esphome.components.sendspin import _get_data +from esphome.components.sendspin.text_sensor import ( + CONF_PAIRING_CODE, + CONFIG_SCHEMA, + SENDSPIN_TEXT_METADATA_TYPES, +) +from esphome.const import PlatformFramework +from esphome.types import ConfigType +from tests.component_tests.types import SetCoreConfigCallable + + +def _sensor_config(**overrides: Any) -> ConfigType: + """Build a minimal valid text sensor config, allowing field overrides.""" + config: ConfigType = {"name": "Sendspin Text", "type": "title"} + config.update(overrides) + return config + + +@pytest.mark.parametrize("sensor_type", sorted(SENDSPIN_TEXT_METADATA_TYPES)) +def test_metadata_types_request_metadata_role( + set_core_config: SetCoreConfigCallable, sensor_type: str +) -> None: + """Metadata sensors need the metadata role, and say nothing about pairing codes.""" + set_core_config(PlatformFramework.ESP32_IDF) + + CONFIG_SCHEMA(_sensor_config(type=sensor_type)) + + assert _get_data().metadata_support is True + assert _get_data().pairing_code_display_support is False + + +def test_pairing_code_requests_code_display( + set_core_config: SetCoreConfigCallable, +) -> None: + """A pairing_code sensor is a way to show the dynamic code, so it alone makes the + hub offer it. It needs no role, so a pairing-only config builds without metadata.""" + set_core_config(PlatformFramework.ESP32_IDF) + + CONFIG_SCHEMA(_sensor_config(type=CONF_PAIRING_CODE)) + + assert _get_data().pairing_code_display_support is True + assert _get_data().metadata_support is False + + +def test_unknown_type_rejected(set_core_config: SetCoreConfigCallable) -> None: + """A misspelled type must fail rather than fall through to a default.""" + set_core_config(PlatformFramework.ESP32_IDF) + + with pytest.raises(cv.Invalid): + CONFIG_SCHEMA(_sensor_config(type="pairing_secret")) diff --git a/tests/component_tests/sendspin/test_unpaired_access.py b/tests/component_tests/sendspin/test_unpaired_access.py new file mode 100644 index 0000000000..8660ba9f7c --- /dev/null +++ b/tests/component_tests/sendspin/test_unpaired_access.py @@ -0,0 +1,185 @@ +"""Validation tests for the unpaired access policy. + +The hub's `unpaired_access` key and an `unpaired_access` switch both set the +policy, so the switch platform rejects a config that has both. +""" + +import logging + +import pytest + +from esphome import config_validation as cv +from esphome.components.sendspin import ( + CONF_UNPAIRED_ACCESS, + CONFIG_SCHEMA as HUB_CONFIG_SCHEMA, + DOMAIN, + FINAL_VALIDATE_SCHEMA as HUB_FINAL_VALIDATE_SCHEMA, + _get_data, + request_pairing_code_display_support, +) +from esphome.components.sendspin.switch import ( + CONFIG_SCHEMA as SWITCH_CONFIG_SCHEMA, + FINAL_VALIDATE_SCHEMA as SWITCH_FINAL_VALIDATE_SCHEMA, +) +from esphome.const import PlatformFramework +from esphome.types import ConfigType +from tests.component_tests.types import SetCoreConfigCallable + +HUB_ID = "sendspin_hub_id" +NO_PAIRING_WARNING = "no new server can pair with this device" + + +def _switch_config(switch_type: str) -> ConfigType: + return SWITCH_CONFIG_SCHEMA( + {"name": "Sendspin Switch", "type": switch_type, "sendspin_id": HUB_ID} + ) + + +def test_hub_key_absent_by_default(set_core_config: SetCoreConfigCallable) -> None: + """With no default in the schema, the switch can tell whether the key was set.""" + set_core_config(PlatformFramework.ESP32_IDF) + + assert CONF_UNPAIRED_ACCESS not in HUB_CONFIG_SCHEMA({"id": HUB_ID}) + + +@pytest.mark.parametrize("value", [True, False]) +def test_hub_key_accepted(set_core_config: SetCoreConfigCallable, value: bool) -> None: + set_core_config(PlatformFramework.ESP32_IDF) + + config = HUB_CONFIG_SCHEMA({"id": HUB_ID, CONF_UNPAIRED_ACCESS: value}) + + assert config[CONF_UNPAIRED_ACCESS] is value + + +def test_off_without_pairing_method_warns( + set_core_config: SetCoreConfigCallable, caplog: pytest.LogCaptureFixture +) -> None: + """With unpaired access off and no way to pair, no new server could pair.""" + set_core_config(PlatformFramework.ESP32_IDF) + config = HUB_CONFIG_SCHEMA({"id": HUB_ID, CONF_UNPAIRED_ACCESS: False}) + + with caplog.at_level(logging.WARNING): + HUB_FINAL_VALIDATE_SCHEMA(config) + + assert NO_PAIRING_WARNING in caplog.text + + +@pytest.mark.parametrize("method", ["static_pairing_code", "pairing_code"]) +def test_off_with_pairing_method_does_not_warn( + set_core_config: SetCoreConfigCallable, + caplog: pytest.LogCaptureFixture, + method: str, +) -> None: + set_core_config(PlatformFramework.ESP32_IDF) + hub_config = {"id": HUB_ID, CONF_UNPAIRED_ACCESS: False} + if method == "static_pairing_code": + hub_config["static_pairing_code"] = "01234567" + else: + request_pairing_code_display_support() + config = HUB_CONFIG_SCHEMA(hub_config) + + with caplog.at_level(logging.WARNING): + HUB_FINAL_VALIDATE_SCHEMA(config) + + assert NO_PAIRING_WARNING not in caplog.text + + +def test_on_does_not_warn( + set_core_config: SetCoreConfigCallable, caplog: pytest.LogCaptureFixture +) -> None: + set_core_config(PlatformFramework.ESP32_IDF) + config = HUB_CONFIG_SCHEMA({"id": HUB_ID}) + + with caplog.at_level(logging.WARNING): + HUB_FINAL_VALIDATE_SCHEMA(config) + + assert NO_PAIRING_WARNING not in caplog.text + + +def test_switch_with_hub_key_rejected(set_core_config: SetCoreConfigCallable) -> None: + """Both would set the policy, and the hub's value would be silently ignored.""" + set_core_config( + PlatformFramework.ESP32_IDF, + full_config={DOMAIN: {"id": HUB_ID, CONF_UNPAIRED_ACCESS: False}}, + ) + config = _switch_config(CONF_UNPAIRED_ACCESS) + + with pytest.raises(cv.Invalid, match="set the switch's restore_mode instead"): + SWITCH_FINAL_VALIDATE_SCHEMA(config) + + +def test_switch_without_hub_key_accepted( + set_core_config: SetCoreConfigCallable, +) -> None: + set_core_config(PlatformFramework.ESP32_IDF, full_config={DOMAIN: {"id": HUB_ID}}) + config = _switch_config(CONF_UNPAIRED_ACCESS) + + assert SWITCH_FINAL_VALIDATE_SCHEMA(config) is config + + +def test_switch_without_pairing_method_warns( + set_core_config: SetCoreConfigCallable, caplog: pytest.LogCaptureFixture +) -> None: + """Turning the switch off would leave no way for a new server to pair.""" + set_core_config(PlatformFramework.ESP32_IDF, full_config={DOMAIN: {"id": HUB_ID}}) + config = _switch_config(CONF_UNPAIRED_ACCESS) + + with caplog.at_level(logging.WARNING): + SWITCH_FINAL_VALIDATE_SCHEMA(config) + + assert NO_PAIRING_WARNING in caplog.text + + +def test_switch_with_pairing_method_does_not_warn( + set_core_config: SetCoreConfigCallable, caplog: pytest.LogCaptureFixture +) -> None: + set_core_config( + PlatformFramework.ESP32_IDF, + full_config={DOMAIN: {"id": HUB_ID, "static_pairing_code": "01234567"}}, + ) + config = _switch_config(CONF_UNPAIRED_ACCESS) + + with caplog.at_level(logging.WARNING): + SWITCH_FINAL_VALIDATE_SCHEMA(config) + + assert NO_PAIRING_WARNING not in caplog.text + + +def test_enabled_switch_ignores_hub_key(set_core_config: SetCoreConfigCallable) -> None: + """Only the unpaired access switch competes with the hub key.""" + set_core_config( + PlatformFramework.ESP32_IDF, + full_config={DOMAIN: {"id": HUB_ID, CONF_UNPAIRED_ACCESS: False}}, + ) + config = _switch_config("enabled") + + assert SWITCH_FINAL_VALIDATE_SCHEMA(config) is config + + +@pytest.mark.parametrize("switch_type", ["enabled", CONF_UNPAIRED_ACCESS]) +def test_duplicate_switch_type_rejected( + set_core_config: SetCoreConfigCallable, switch_type: str +) -> None: + """Two switches of one type would fight over the same hub setting.""" + entry = {"platform": DOMAIN, "type": switch_type} + set_core_config( + PlatformFramework.ESP32_IDF, + full_config={DOMAIN: {"id": HUB_ID}, "switch": [entry, dict(entry)]}, + ) + config = _switch_config(switch_type) + + with pytest.raises(cv.Invalid, match="Only one sendspin"): + SWITCH_FINAL_VALIDATE_SCHEMA(config) + + +@pytest.mark.parametrize("switch_type", ["enabled", CONF_UNPAIRED_ACCESS]) +def test_switch_requests_its_type( + set_core_config: SetCoreConfigCallable, switch_type: str +) -> None: + """The hub skips the codegen value for a setting a switch drives, so the client + waits for that switch to restore before its first start.""" + set_core_config(PlatformFramework.ESP32_IDF) + + _switch_config(switch_type) + + assert _get_data().switch_types == {switch_type} diff --git a/tests/component_tests/sensor/test_timeout_throttle.py b/tests/component_tests/sensor/test_timeout_throttle.py new file mode 100644 index 0000000000..8154471e47 --- /dev/null +++ b/tests/component_tests/sensor/test_timeout_throttle.py @@ -0,0 +1,24 @@ +"""timeout (value last) followed by throttle_with_priority with the same period becomes one filter.""" + +from collections.abc import Callable +from pathlib import Path +import re + + +def test_timeout_throttle_pair_is_merged( + generate_main: Callable[[str | Path], str], +) -> None: + main_cpp = generate_main("tests/component_tests/sensor/timeout_throttle.yaml") + + # Only the `merged` sensor's adjacent pair becomes one object + assert ( + len(re.findall(r"new\(\w+\) sensor::TimeoutThrottleFilter\(1000\);", main_cpp)) + == 1 + ) + merged = re.search(r"merged->set_filters\(\{(\w+)\}\);", main_cpp) + assert merged is not None + assert f"new({merged.group(1)}) sensor::TimeoutThrottleFilter(1000);" in main_cpp + # Every other shape keeps its two filters + for sensor_id in ("other_periods", "other_order", "value_list", "configured_value"): + call = re.search(rf"{sensor_id}->set_filters\(\{{([^}}]*)\}}\);", main_cpp) + assert call is not None and len(call.group(1).split(",")) == 2, sensor_id diff --git a/tests/component_tests/sensor/timeout_throttle.yaml b/tests/component_tests/sensor/timeout_throttle.yaml new file mode 100644 index 0000000000..2557db683c --- /dev/null +++ b/tests/component_tests/sensor/timeout_throttle.yaml @@ -0,0 +1,52 @@ +esphome: + name: test + +esp32: + board: esp32dev + +sensor: + - platform: template + id: merged + filters: + - timeout: + timeout: 1s + value: last + - throttle_with_priority: 1s + - platform: template + id: other_periods + filters: + - timeout: + timeout: 1s + value: last + - throttle_with_priority: 2s + - platform: template + id: other_order + filters: + - throttle_with_priority: 1s + - timeout: + timeout: 1s + value: last + - platform: template + id: value_list + filters: + - timeout: + timeout: 1s + value: last + - throttle_with_priority: + timeout: 1s + value: [0.0] + - platform: template + id: configured_value + filters: + - timeout: + timeout: 1s + value: 0.0 + - throttle_with_priority: 1s + - platform: template + id: inside_or + filters: + - or: + - timeout: + timeout: 1s + value: last + - throttle_with_priority: 1s diff --git a/tests/component_tests/sntp/test_init.py b/tests/component_tests/sntp/test_init.py index 9197ff55d0..db0d3c9d9c 100644 --- a/tests/component_tests/sntp/test_init.py +++ b/tests/component_tests/sntp/test_init.py @@ -8,7 +8,8 @@ from typing import Any import pytest from esphome import config_validation as cv -from esphome.components.sntp.time import CONF_SNTP, _sntp_final_validate +from esphome.components.sntp import DOMAIN as SNTP_DOMAIN +from esphome.components.sntp.time import _sntp_final_validate from esphome.const import CONF_ID, CONF_PLATFORM, CONF_SERVERS, CONF_TIME from esphome.core import ID import esphome.final_validate as fv @@ -20,7 +21,7 @@ import esphome.final_validate as fv pytest.param( [ { - CONF_PLATFORM: CONF_SNTP, + CONF_PLATFORM: SNTP_DOMAIN, CONF_ID: ID("sntp_time", is_manual=False), CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"], } @@ -33,12 +34,12 @@ import esphome.final_validate as fv pytest.param( [ { - CONF_PLATFORM: CONF_SNTP, + CONF_PLATFORM: SNTP_DOMAIN, CONF_ID: ID("sntp_time_1", is_manual=False), CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"], }, { - CONF_PLATFORM: CONF_SNTP, + CONF_PLATFORM: SNTP_DOMAIN, CONF_ID: ID("sntp_time_2", is_manual=False), CONF_SERVERS: ["192.168.1.2"], }, @@ -51,12 +52,12 @@ import esphome.final_validate as fv pytest.param( [ { - CONF_PLATFORM: CONF_SNTP, + CONF_PLATFORM: SNTP_DOMAIN, CONF_ID: ID("sntp_time_1", is_manual=False), CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"], }, { - CONF_PLATFORM: CONF_SNTP, + CONF_PLATFORM: SNTP_DOMAIN, CONF_ID: ID("sntp_time_2", is_manual=False), CONF_SERVERS: ["pool.ntp.org", "192.168.1.2"], }, @@ -69,17 +70,17 @@ import esphome.final_validate as fv pytest.param( [ { - CONF_PLATFORM: CONF_SNTP, + CONF_PLATFORM: SNTP_DOMAIN, CONF_ID: ID("sntp_time_1", is_manual=False), CONF_SERVERS: ["192.168.1.1", "pool.ntp.org"], }, { - CONF_PLATFORM: CONF_SNTP, + CONF_PLATFORM: SNTP_DOMAIN, CONF_ID: ID("sntp_time_2", is_manual=False), CONF_SERVERS: ["192.168.1.2", "pool2.ntp.org"], }, { - CONF_PLATFORM: CONF_SNTP, + CONF_PLATFORM: SNTP_DOMAIN, CONF_ID: ID("sntp_time_3", is_manual=False), CONF_SERVERS: ["pool3.ntp.org"], }, @@ -95,7 +96,7 @@ import esphome.final_validate as fv pytest.param( [ { - CONF_PLATFORM: CONF_SNTP, + CONF_PLATFORM: SNTP_DOMAIN, CONF_ID: ID("sntp_time_1", is_manual=False), CONF_SERVERS: [ "192.168.1.1", @@ -105,7 +106,7 @@ import esphome.final_validate as fv ], }, { - CONF_PLATFORM: CONF_SNTP, + CONF_PLATFORM: SNTP_DOMAIN, CONF_ID: ID("sntp_time_2", is_manual=False), CONF_SERVERS: ["pool.ntp.org", "192.168.1.2"], }, @@ -143,7 +144,7 @@ def test_sntp_instance_merging( sntp_instances = [ tc for tc in updated_conf[CONF_TIME] - if tc.get(CONF_PLATFORM) == CONF_SNTP + if tc.get(CONF_PLATFORM) == SNTP_DOMAIN ] assert len(sntp_instances) == expected_count @@ -169,12 +170,12 @@ def test_sntp_inconsistent_manual_ids() -> None: # Create configs with manual IDs that are inconsistent time_configs = [ { - CONF_PLATFORM: CONF_SNTP, + CONF_PLATFORM: SNTP_DOMAIN, CONF_ID: ID("sntp_time_1", is_manual=True), CONF_SERVERS: ["192.168.1.1"], }, { - CONF_PLATFORM: CONF_SNTP, + CONF_PLATFORM: SNTP_DOMAIN, CONF_ID: ID("sntp_time_2", is_manual=True), CONF_SERVERS: ["192.168.1.2"], }, @@ -197,7 +198,7 @@ def test_sntp_with_other_time_platforms(caplog: pytest.LogCaptureFixture) -> Non """Test that SNTP merging doesn't affect other time platforms.""" time_configs = [ { - CONF_PLATFORM: CONF_SNTP, + CONF_PLATFORM: SNTP_DOMAIN, CONF_ID: ID("sntp_time_1", is_manual=False), CONF_SERVERS: ["192.168.1.1"], }, @@ -206,7 +207,7 @@ def test_sntp_with_other_time_platforms(caplog: pytest.LogCaptureFixture) -> Non CONF_ID: ID("homeassistant_time", is_manual=False), }, { - CONF_PLATFORM: CONF_SNTP, + CONF_PLATFORM: SNTP_DOMAIN, CONF_ID: ID("sntp_time_2", is_manual=False), CONF_SERVERS: ["192.168.1.2"], }, @@ -226,11 +227,11 @@ def test_sntp_with_other_time_platforms(caplog: pytest.LogCaptureFixture) -> Non # Find the platforms platforms = {tc[CONF_PLATFORM] for tc in updated_conf[CONF_TIME]} - assert platforms == {CONF_SNTP, "homeassistant"} + assert platforms == {SNTP_DOMAIN, "homeassistant"} # Verify SNTP was merged sntp_instances = [ - tc for tc in updated_conf[CONF_TIME] if tc[CONF_PLATFORM] == CONF_SNTP + tc for tc in updated_conf[CONF_TIME] if tc[CONF_PLATFORM] == SNTP_DOMAIN ] assert len(sntp_instances) == 1 assert sntp_instances[0][CONF_SERVERS] == ["192.168.1.1", "192.168.1.2"] diff --git a/tests/component_tests/sx126x/__init__.py b/tests/component_tests/sx126x/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/component_tests/sx126x/config/packet_tables.yaml b/tests/component_tests/sx126x/config/packet_tables.yaml new file mode 100644 index 0000000000..d8b2f04281 --- /dev/null +++ b/tests/component_tests/sx126x/config/packet_tables.yaml @@ -0,0 +1,27 @@ +esphome: + name: test + on_boot: + then: + - sx126x.send_packet: [0xC5, 0x51, 0x78, 0x82] + - sx126x.send_packet: [0xC5, 0x51, 0x78, 0x82] + - sx126x.send_packet: "hi" + - sx126x.send_packet: !lambda return {0x09}; + +esp32: + board: esp32dev + +spi: + clk_pin: 18 + mosi_pin: 23 + miso_pin: 19 + +sx126x: + cs_pin: 12 + rst_pin: 13 + busy_pin: 25 + dio1_pin: 26 + frequency: 433920000 + hw_version: sx1262 + modulation: LORA + bandwidth: 125_0kHz + rf_switch: true diff --git a/tests/component_tests/sx126x/test_packet_tables.py b/tests/component_tests/sx126x/test_packet_tables.py new file mode 100644 index 0000000000..9ee3379f08 --- /dev/null +++ b/tests/component_tests/sx126x/test_packet_tables.py @@ -0,0 +1,39 @@ +"""Tests for SX126x constant packets in shared PROGMEM tables.""" + +from collections.abc import Callable +from pathlib import Path +import re + +import pytest + +from esphome.components.sx126x import validate_packet_data +import esphome.config_validation as cv + + +def test_constant_packets_share_progmem_tables( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Equal constant packets share one table; lambdas stay templates.""" + main_cpp = generate_main(component_config_path("packet_tables.yaml")) + + tables = dict( + re.findall( + r"static constexpr uint8_t (\w+)\[\] PROGMEM = (\{[^}]*\});", main_cpp + ) + ) + assert sorted(tables.values()) == sorted( + ["{0xC5, 0x51, 0x78, 0x82}", "{0x68, 0x69}"] + ) + shared = next(k for k, v in tables.items() if v == "{0xC5, 0x51, 0x78, 0x82}") + assert main_cpp.count(f"set_data_static({shared}, 4);") == 2 + assert "set_data_template(" in main_cpp + + +def test_packet_data_length_limit() -> None: + """Constant packets must be 1 to 255 bytes long.""" + assert len(validate_packet_data([0x01] * 255)) == 255 + with pytest.raises(cv.Invalid): + validate_packet_data([0x01] * 256) + with pytest.raises(cv.Invalid): + validate_packet_data([]) diff --git a/tests/component_tests/sx127x/__init__.py b/tests/component_tests/sx127x/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/component_tests/sx127x/config/packet_tables.yaml b/tests/component_tests/sx127x/config/packet_tables.yaml new file mode 100644 index 0000000000..a1cf7b3f00 --- /dev/null +++ b/tests/component_tests/sx127x/config/packet_tables.yaml @@ -0,0 +1,24 @@ +esphome: + name: test + on_boot: + then: + - sx127x.send_packet: [0xC5, 0x51, 0x78, 0x82] + - sx127x.send_packet: [0xC5, 0x51, 0x78, 0x82] + - sx127x.send_packet: "hi" + - sx127x.send_packet: !lambda return {0x09}; + +esp32: + board: esp32dev + +spi: + clk_pin: 18 + mosi_pin: 23 + miso_pin: 19 + +sx127x: + cs_pin: 12 + rst_pin: 13 + dio0_pin: 26 + frequency: 433920000 + modulation: LORA + preamble_size: 8 diff --git a/tests/component_tests/sx127x/test_packet_tables.py b/tests/component_tests/sx127x/test_packet_tables.py new file mode 100644 index 0000000000..f7573bb1ca --- /dev/null +++ b/tests/component_tests/sx127x/test_packet_tables.py @@ -0,0 +1,51 @@ +"""Tests for SX127x constant packets in shared PROGMEM tables.""" + +from collections.abc import Callable +from pathlib import Path +import re + +import pytest + +from esphome.components.sx127x import CONFIG_SCHEMA, validate_packet_data +import esphome.config_validation as cv + + +def test_constant_packets_share_progmem_tables( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Equal constant packets share one table; lambdas stay templates.""" + main_cpp = generate_main(component_config_path("packet_tables.yaml")) + + tables = dict( + re.findall( + r"static constexpr uint8_t (\w+)\[\] PROGMEM = (\{[^}]*\});", main_cpp + ) + ) + assert sorted(tables.values()) == sorted( + ["{0xC5, 0x51, 0x78, 0x82}", "{0x68, 0x69}"] + ) + shared = next(k for k, v in tables.items() if v == "{0xC5, 0x51, 0x78, 0x82}") + assert main_cpp.count(f"set_data_static({shared}, 4);") == 2 + assert "set_data_template(" in main_cpp + + +def test_packet_data_length_limit() -> None: + """Constant packets are limited to the 255 bytes the length register holds.""" + assert len(validate_packet_data([0x01] * 255)) == 255 + with pytest.raises(cv.Invalid): + validate_packet_data([0x01] * 256) + with pytest.raises(cv.Invalid): + validate_packet_data("x" * 256) + with pytest.raises(cv.Invalid): + validate_packet_data([]) + + +def test_payload_length_limit() -> None: + """payload_length goes into an 8 bit register, so 256 is rejected.""" + validator = next( + v for k, v in CONFIG_SCHEMA.schema.items() if str(k) == "payload_length" + ) + assert validator(255) == 255 + with pytest.raises(cv.Invalid): + validator(256) diff --git a/tests/component_tests/template/config/alarm_codes.yaml b/tests/component_tests/template/config/alarm_codes.yaml new file mode 100644 index 0000000000..afeb45e1d2 --- /dev/null +++ b/tests/component_tests/template/config/alarm_codes.yaml @@ -0,0 +1,23 @@ +esphome: + name: test + +esp8266: + board: d1_mini + +alarm_control_panel: + - platform: template + id: panel_a + name: Panel A + codes: + - "1234" + - "5678" + - platform: template + id: panel_b + name: Panel B + codes: + - "1234" + - "5678" + - platform: template + id: panel_none + name: Panel None + codes: [] diff --git a/tests/component_tests/template/test_template_alarm_codes.py b/tests/component_tests/template/test_template_alarm_codes.py new file mode 100644 index 0000000000..1a8e94be90 --- /dev/null +++ b/tests/component_tests/template/test_template_alarm_codes.py @@ -0,0 +1,20 @@ +"""Tests for the template alarm control panel codes codegen.""" + +from collections.abc import Callable +from pathlib import Path + + +def test_codes_share_one_progmem_table( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Identical code lists share one nullptr-terminated flash table; empty lists set none.""" + main_cpp = generate_main(component_config_path("alarm_codes.yaml")) + + assert ( + 'static constexpr const char * alarm_codes[] PROGMEM = {"1234", "5678", nullptr};' + in main_cpp + ) + assert "panel_a->set_codes(alarm_codes);" in main_cpp + assert "panel_b->set_codes(alarm_codes);" in main_cpp + assert "panel_none->set_codes(" not in main_cpp diff --git a/tests/component_tests/udp/config/udp_write.yaml b/tests/component_tests/udp/config/udp_write.yaml new file mode 100644 index 0000000000..645879bfcd --- /dev/null +++ b/tests/component_tests/udp/config/udp_write.yaml @@ -0,0 +1,22 @@ +esphome: + name: test + +esp8266: + board: d1_mini + +wifi: + ssid: test + password: password1 + +udp: + addresses: + - 10.0.0.1 + +button: + - platform: template + name: send + on_press: + - udp.write: [0xAA, 0x55, 0x01, 0x02] + - udp.write: [0xAA, 0x55, 0x01, 0x02] + - udp.write: "" + - udp.write: !lambda return {0x10, 0x20}; diff --git a/tests/component_tests/udp/test_udp_write.py b/tests/component_tests/udp/test_udp_write.py new file mode 100644 index 0000000000..d999891d95 --- /dev/null +++ b/tests/component_tests/udp/test_udp_write.py @@ -0,0 +1,17 @@ +"""Tests for the udp.write action codegen.""" + +from collections.abc import Callable +from pathlib import Path + + +def test_write_payloads_share_a_progmem_table( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Identical constant payloads share one flash table; an empty one needs none.""" + main_cpp = generate_main(component_config_path("udp_write.yaml")) + + assert main_cpp.count("udp_data[] PROGMEM = {0xAA, 0x55, 0x01, 0x02};") == 1 + assert main_cpp.count("->set_data_static(udp_data, 4);") == 2 + assert "->set_data_static(nullptr, 0);" in main_cpp + assert "->set_data_template(" in main_cpp diff --git a/tests/component_tests/web_server/config/include.css b/tests/component_tests/web_server/config/include.css new file mode 100644 index 0000000000..991912d894 --- /dev/null +++ b/tests/component_tests/web_server/config/include.css @@ -0,0 +1 @@ +body { color: red; } diff --git a/tests/component_tests/web_server/config/include.js b/tests/component_tests/web_server/config/include.js new file mode 100644 index 0000000000..d914c6066c --- /dev/null +++ b/tests/component_tests/web_server/config/include.js @@ -0,0 +1 @@ +console.log("hi"); diff --git a/tests/component_tests/web_server/config/includes.yaml b/tests/component_tests/web_server/config/includes.yaml new file mode 100644 index 0000000000..0b80652dde --- /dev/null +++ b/tests/component_tests/web_server/config/includes.yaml @@ -0,0 +1,13 @@ +esphome: + name: test + +esp32: + board: esp32dev + +wifi: + ssid: test + password: testtest + +web_server: + css_include: include.css + js_include: include.js diff --git a/tests/component_tests/web_server/test_progmem_resources.py b/tests/component_tests/web_server/test_progmem_resources.py new file mode 100644 index 0000000000..95875c2267 --- /dev/null +++ b/tests/component_tests/web_server/test_progmem_resources.py @@ -0,0 +1,33 @@ +"""Tests for the web_server resources emitted as flash tables.""" + +from collections.abc import Callable +import gzip +from pathlib import Path +import re + + +def _table(main_cpp: str, name: str) -> bytes: + # Anchored so a static table does not match + match = re.search( + rf"^const uint8_t ESPHOME_WEBSERVER_{name}\[\] PROGMEM = \{{([^}}]*)\}};", + main_cpp, + re.MULTILINE, + ) + assert match is not None + return bytes(int(byte) for byte in match.group(1).split(",")) + + +def test_resources_are_externally_linked_tables( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """web_server.cpp reads these through extern declarations, so they must not be static.""" + main_cpp = generate_main(component_config_path("includes.yaml")) + + assert b"" in _table(main_cpp, "INDEX_HTML") + assert gzip.decompress(_table(main_cpp, "CSS_INCLUDE")) == b"body { color: red; }\n" + assert gzip.decompress(_table(main_cpp, "JS_INCLUDE")) == b'console.log("hi");\n' + css_size = len(_table(main_cpp, "CSS_INCLUDE")) + assert ( + f"constexpr size_t ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE = {css_size};" in main_cpp + ) diff --git a/tests/component_tests/wireguard/config/allowed_ips.yaml b/tests/component_tests/wireguard/config/allowed_ips.yaml new file mode 100644 index 0000000000..7f191f90fb --- /dev/null +++ b/tests/component_tests/wireguard/config/allowed_ips.yaml @@ -0,0 +1,26 @@ +esphome: + name: test + +esp32: + board: esp32dev + framework: + type: esp-idf + +wifi: + ssid: test + password: password1 + +time: + - platform: sntp + id: sntp_time + +wireguard: + time_id: sntp_time + address: 172.16.34.100 + netmask: 255.255.255.0 + private_key: wPBMxtNYH3mChicrbpsRpZIasIdPq3yZuthn23FbGG8= + peer_public_key: Hs2JfikvYU03/Kv3YoAs1hrUIPPTEkpsZKSPUljE9yc= + peer_endpoint: wg.server.example + peer_allowed_ips: + - 172.16.34.0/24 + - 192.168.0.0/16 diff --git a/tests/component_tests/wireguard/test_allowed_ips.py b/tests/component_tests/wireguard/test_allowed_ips.py new file mode 100644 index 0000000000..ff490c516e --- /dev/null +++ b/tests/component_tests/wireguard/test_allowed_ips.py @@ -0,0 +1,21 @@ +"""Tests for the wireguard allowed IPs codegen.""" + +from collections.abc import Callable +from pathlib import Path + + +def test_allowed_ips_are_a_progmem_table( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Allowed IPs live in a flash table ended by an empty entry instead of a heap vector.""" + main_cpp = generate_main(component_config_path("allowed_ips.yaml")) + + table = main_cpp.split( + "wireguard::AllowedIP wireguard_allowed_ips[] PROGMEM = ", 1 + )[1] + table = table.split(";", 1)[0] + assert '.ip = "172.16.34.0"' in table + assert '.netmask = "255.255.0.0"' in table + assert ".ip = nullptr" in table + assert "->set_allowed_ips(wireguard_allowed_ips);" in main_cpp diff --git a/tests/components/adc/test.nrf52-mcumgr.yaml b/tests/components/adc/test.nrf52-mcumgr.yaml deleted file mode 100644 index 4be5b4b5c2..0000000000 --- a/tests/components/adc/test.nrf52-mcumgr.yaml +++ /dev/null @@ -1,23 +0,0 @@ -sensor: - - platform: adc - pin: VDDHDIV5 - name: "VDDH Voltage" - update_interval: 5sec - filters: - - multiply: 5 - - platform: adc - pin: VDD - name: "VDD Voltage" - update_interval: 5sec - - platform: adc - pin: AIN0 - name: "AIN0 Voltage" - update_interval: 5sec - - platform: adc - pin: P0.03 - name: "AIN1 Voltage" - update_interval: 5sec - - platform: adc - name: "AIN2 Voltage" - update_interval: 5sec - pin: 4 diff --git a/tests/components/adc/test.bk72xx-ard.yaml b/tests/components/adc/validate.bk72xx-ard.yaml similarity index 64% rename from tests/components/adc/test.bk72xx-ard.yaml rename to tests/components/adc/validate.bk72xx-ard.yaml index 09ef0e1fad..0064276e30 100644 --- a/tests/components/adc/test.bk72xx-ard.yaml +++ b/tests/components/adc/validate.bk72xx-ard.yaml @@ -1,3 +1,5 @@ +# Config only: every LibreTiny chip compiles the same ADC code, covered by the +# ln882x-ard build; this checks the BK72xx analog pin names. sensor: - id: adc_my_sensor platform: adc diff --git a/tests/components/adc/test.esp32-p4-idf.yaml b/tests/components/adc/validate.esp32-p4-idf.yaml similarity index 67% rename from tests/components/adc/test.esp32-p4-idf.yaml rename to tests/components/adc/validate.esp32-p4-idf.yaml index 77cf50d17c..708ab03f76 100644 --- a/tests/components/adc/test.esp32-p4-idf.yaml +++ b/tests/components/adc/validate.esp32-p4-idf.yaml @@ -1,3 +1,5 @@ +# Config only: the P4 compiles the same ADC code as the esp32-s3-idf build +# (curve fitting calibration), only the pin table differs. sensor: - id: adc_my_sensor platform: adc diff --git a/tests/components/adc/test.esp32-s2-idf.yaml b/tests/components/adc/validate.esp32-s2-idf.yaml similarity index 58% rename from tests/components/adc/test.esp32-s2-idf.yaml rename to tests/components/adc/validate.esp32-s2-idf.yaml index a3019466b5..b8d6923bc2 100644 --- a/tests/components/adc/test.esp32-s2-idf.yaml +++ b/tests/components/adc/validate.esp32-s2-idf.yaml @@ -1,3 +1,6 @@ +# Config only: the S2 shares the C2's line fitting path (no default_vref) and +# the original ESP32's ADC init (no clk_src); both are compiled by those +# builds, only the S2 pin table differs. sensor: - id: adc_my_sensor platform: adc diff --git a/tests/components/ags10/common.yaml b/tests/components/ags10/common.yaml index 8009c56295..8441729a8f 100644 --- a/tests/components/ags10/common.yaml +++ b/tests/components/ags10/common.yaml @@ -1,6 +1,7 @@ sensor: - platform: ags10 id: ags10_1 + i2c_id: i2c_bus tvoc: name: AGS10 TVOC update_interval: 60s diff --git a/tests/components/api/__init__.py b/tests/components/api/__init__.py index a5e3731c75..bd84c8d9bb 100644 --- a/tests/components/api/__init__.py +++ b/tests/components/api/__init__.py @@ -1,6 +1,12 @@ +from pathlib import Path + import esphome.codegen as cg +from esphome.const import CONF_ESPHOME, CONF_INCLUDES +from esphome.core import CORE from tests.testing_helpers import ComponentManifestOverride +TESTS_DIR = Path(__file__).parent + def override_manifest(manifest: ComponentManifestOverride) -> None: # USE_API compiles every api source, so emit what they need. No socket @@ -8,6 +14,20 @@ def override_manifest(manifest: ComponentManifestOverride) -> None: async def to_code_testing(config): cg.add_define("USE_API") cg.add_define("USE_API_PLAINTEXT") + # Linked wizard inputs only exist next to homeassistant entities, which need this + cg.add_define("USE_API_HOMEASSISTANT_STATES") + # test_wizard.cpp supplies the tables that codegen emits for a real build; + # it is only compiled when api's own tests are, not when api is a dependency + if any(Path(p) == TESTS_DIR for p in CORE.config[CONF_ESPHOME][CONF_INCLUDES]): + for define in ( + "USE_API_WIZARD", + "USE_API_WIZARD_INPUTS", + "USE_API_WIZARD_LINKED_INPUTS", + "USE_API_WIZARD_STANDALONE_INPUTS", + ): + cg.add_define(define) + cg.add_define("API_WIZARD_DATA_SIZE", 200) + cg.add_define("API_WIZARD_INPUT_COUNT", 2) cg.add_define("API_MAX_SEND_QUEUE", 8) cg.add_define("MAX_API_CONNECTIONS", 1) cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS") diff --git a/tests/components/api/common-wizard.yaml b/tests/components/api/common-wizard.yaml new file mode 100644 index 0000000000..5e1936a042 --- /dev/null +++ b/tests/components/api/common-wizard.yaml @@ -0,0 +1,108 @@ +api: + homeassistant_services: true + wizard: + pages: + - title: Features + description: Choose which features to enable + entities: + - id: feature_enable_switch + description: "[%key:component::example::feature::enable_switch%]" + - id: guest_mode_switch + description: Guest Mode + - id: plain_switch + - title: Sources + inputs: + - entity: outdoor_temp + description: Outdoor temperature + target: + entity: + - integration: met + domain: sensor + device_class: [temperature, humidity] + supported_features: weather.WeatherEntityFeature.FORECAST_DAILY + - domain: [sensor, number] + - entity: default_temp + - entity: door_open + - entity: house_mode + - entity: target_temp + - entity: ha_light_switch + target: + entity: + - domain: light + - entity: ha_text + - entity: ha_select + target: + entity: + - domain: input_select + - entity: ha_button + - id: weather_input + description: Weather source + target: + entity: + - domain: weather + - id: unset_input + +switch: + - platform: template + id: feature_enable_switch + name: Feature Enable + optimistic: true + - platform: template + id: guest_mode_switch + name: Guest Mode + optimistic: true + - platform: template + id: plain_switch + name: Plain Switch + optimistic: true + - platform: homeassistant + id: ha_light_switch + +sensor: + - platform: homeassistant + id: outdoor_temp + - platform: homeassistant + id: default_temp + - platform: homeassistant + id: fixed_temp + entity_id: sensor.fixed_temperature + +binary_sensor: + - platform: homeassistant + id: door_open + +text_sensor: + - platform: homeassistant + id: house_mode + +number: + - platform: homeassistant + id: target_temp + +text: + - platform: homeassistant + id: ha_text + +select: + - platform: homeassistant + id: ha_select + +button: + - platform: homeassistant + id: ha_button + - platform: homeassistant + id: fixed_button + entity_id: button.restart + +interval: + - interval: 1h + then: + - if: + condition: + api.wizard.input_is_set: weather_input + then: + - homeassistant.action: + action: weather.get_forecasts + data: + type: daily + entity_id: !lambda 'return id(weather_input).entity_id().c_str();' diff --git a/tests/components/api/test-wizard.esp32-idf.yaml b/tests/components/api/test-wizard.esp32-idf.yaml new file mode 100644 index 0000000000..4f33f5b30d --- /dev/null +++ b/tests/components/api/test-wizard.esp32-idf.yaml @@ -0,0 +1,6 @@ +packages: + wizard: !include common-wizard.yaml + +wifi: + ssid: MySSID + password: password1 diff --git a/tests/components/api/test-wizard.esp8266-ard.yaml b/tests/components/api/test-wizard.esp8266-ard.yaml new file mode 100644 index 0000000000..4f33f5b30d --- /dev/null +++ b/tests/components/api/test-wizard.esp8266-ard.yaml @@ -0,0 +1,6 @@ +packages: + wizard: !include common-wizard.yaml + +wifi: + ssid: MySSID + password: password1 diff --git a/tests/components/api/test-wizard.host.yaml b/tests/components/api/test-wizard.host.yaml new file mode 100644 index 0000000000..ffbcfcd36b --- /dev/null +++ b/tests/components/api/test-wizard.host.yaml @@ -0,0 +1,4 @@ +packages: + wizard: !include common-wizard.yaml + +network: diff --git a/tests/components/api/test_proto_sub_message.cpp b/tests/components/api/test_proto_sub_message.cpp new file mode 100644 index 0000000000..ead9487736 --- /dev/null +++ b/tests/components/api/test_proto_sub_message.cpp @@ -0,0 +1,88 @@ +#include + +#include +#include + +#include "esphome/components/api/api_buffer.h" +#include "esphome/components/api/proto.h" + +namespace esphome::api::testing { + +// Sub-message whose body is raw bytes, so the body size is set directly by the test. +struct BlobMessage { + const uint8_t *data; + uint32_t len; + static uint8_t *encode_msg(const void *self, uint8_t *pos PROTO_ENCODE_DEBUG_PARAM) { + const auto &msg = *static_cast(self); + // An empty vector's data() may be null, and memcpy needs a valid source even for zero bytes + if (msg.len == 0) + return pos; + return ProtoEncode::encode_raw(pos PROTO_ENCODE_DEBUG_ARG, msg.data, msg.len); + } + static uint32_t calc_size_msg(const void *self) { return static_cast(self)->len; } +}; + +static void append_varint(std::vector &out, uint32_t value) { + while (value > 0x7F) { + out.push_back(static_cast(value | 0x80)); + value >>= 7; + } + out.push_back(static_cast(value)); +} + +static std::vector make_body(uint32_t len) { + std::vector body(len); + for (uint32_t i = 0; i < len; i++) + body[i] = static_cast(i * 7 + 1); + return body; +} + +static std::vector expected_field(uint32_t field_id, const std::vector &body) { + std::vector out; + append_varint(out, (field_id << 3) | 2); + append_varint(out, body.size()); + out.insert(out.end(), body.begin(), body.end()); + return out; +} + +// Encodes into a buffer of exactly the expected size, so any overrun trips ASan or the debug bounds check. +template static void verify(uint32_t field_id, uint32_t body_len) { + std::vector body = make_body(body_len); + std::vector expected = expected_field(field_id, body); + if (OPTIONAL && body_len == 0) + expected.clear(); + BlobMessage msg{body.data(), body_len}; + + APIBuffer buf; + ASSERT_TRUE(buf.resize(expected.empty() ? 1 : expected.size())); + uint8_t *pos = buf.data(); +#ifdef ESPHOME_DEBUG_API + uint8_t *proto_debug_end_ = buf.data() + buf.size(); +#endif + uint8_t *end; + if constexpr (OPTIONAL) { + end = ProtoEncode::encode_optional_sub_message(pos PROTO_ENCODE_DEBUG_ARG, field_id, msg); + } else { + end = ProtoEncode::encode_sub_message(pos PROTO_ENCODE_DEBUG_ARG, field_id, msg); + } + + ASSERT_EQ(static_cast(end - buf.data()), expected.size()) << "field " << field_id << " body " << body_len; + EXPECT_EQ(std::vector(buf.data(), end), expected) << "field " << field_id << " body " << body_len; +} + +TEST(ProtoSubMessage, OneByteTag) { verify(4, 10); } +TEST(ProtoSubMessage, TwoByteTag) { verify(20, 10); } +TEST(ProtoSubMessage, EmptyBody) { verify(20, 0); } +TEST(ProtoSubMessage, LongestOneByteLength) { verify(20, 127); } +// The length outgrows its reserved byte, so the body is moved forward +TEST(ProtoSubMessage, TwoByteLength) { + verify(20, 128); + verify(25, 200); +} +TEST(ProtoSubMessage, ThreeByteLength) { verify(4, 20000); } + +TEST(ProtoOptionalSubMessage, EmptyIsSkipped) { verify(22, 0); } +TEST(ProtoOptionalSubMessage, OneByteTag) { verify(1, 10); } +TEST(ProtoOptionalSubMessage, TwoByteTagAndLength) { verify(22, 200); } + +} // namespace esphome::api::testing diff --git a/tests/components/api/test_wizard.cpp b/tests/components/api/test_wizard.cpp new file mode 100644 index 0000000000..033c8b627e --- /dev/null +++ b/tests/components/api/test_wizard.cpp @@ -0,0 +1,148 @@ +#ifdef USE_HOST +#include + +#include +#include +#include +#include + +#include "esphome/components/api/api_buffer.h" +#include "esphome/components/api/api_pb2.h" +#include "esphome/components/api/api_wizard.h" +#include "esphome/components/api/proto.h" + +namespace esphome::api { + +// RAM buffers of the inputs, as codegen defines them +static char *wizard_input_weather() { + static char buffer[WIZARD_ENTITY_ID_BUFFER_SIZE] = "sensor.default"; + return buffer; +} +static char *wizard_input_other() { + static char buffer[WIZARD_ENTITY_ID_BUFFER_SIZE] = ""; + return buffer; +} + +static constexpr uint32_t WEATHER_KEY = 0x0a0b0c0d; +static constexpr uint32_t OTHER_KEY = 0x11223344; + +// The same shapes the generated code emits. The data is 200 bytes (API_WIZARD_DATA_SIZE), so its length takes two +// bytes as a varint. +const uint8_t API_WIZARD_DATA[API_WIZARD_DATA_SIZE] = { + 0x03, 0x0a, 0x11, 0x18, 0x1f, 0x26, 0x2d, 0x34, 0x3b, 0x42, 0x49, 0x50, 0x57, 0x5e, 0x65, 0x6c, 0x73, 0x7a, 0x81, + 0x88, 0x8f, 0x96, 0x9d, 0xa4, 0xab, 0xb2, 0xb9, 0xc0, 0xc7, 0xce, 0xd5, 0xdc, 0xe3, 0xea, 0xf1, 0xf8, 0xff, 0x06, + 0x0d, 0x14, 0x1b, 0x22, 0x29, 0x30, 0x37, 0x3e, 0x45, 0x4c, 0x53, 0x5a, 0x61, 0x68, 0x6f, 0x76, 0x7d, 0x84, 0x8b, + 0x92, 0x99, 0xa0, 0xa7, 0xae, 0xb5, 0xbc, 0xc3, 0xca, 0xd1, 0xd8, 0xdf, 0xe6, 0xed, 0xf4, 0xfb, 0x02, 0x09, 0x10, + 0x17, 0x1e, 0x25, 0x2c, 0x33, 0x3a, 0x41, 0x48, 0x4f, 0x56, 0x5d, 0x64, 0x6b, 0x72, 0x79, 0x80, 0x87, 0x8e, 0x95, + 0x9c, 0xa3, 0xaa, 0xb1, 0xb8, 0xbf, 0xc6, 0xcd, 0xd4, 0xdb, 0xe2, 0xe9, 0xf0, 0xf7, 0xfe, 0x05, 0x0c, 0x13, 0x1a, + 0x21, 0x28, 0x2f, 0x36, 0x3d, 0x44, 0x4b, 0x52, 0x59, 0x60, 0x67, 0x6e, 0x75, 0x7c, 0x83, 0x8a, 0x91, 0x98, 0x9f, + 0xa6, 0xad, 0xb4, 0xbb, 0xc2, 0xc9, 0xd0, 0xd7, 0xde, 0xe5, 0xec, 0xf3, 0xfa, 0x01, 0x08, 0x0f, 0x16, 0x1d, 0x24, + 0x2b, 0x32, 0x39, 0x40, 0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x7f, 0x86, 0x8d, 0x94, 0x9b, 0xa2, 0xa9, + 0xb0, 0xb7, 0xbe, 0xc5, 0xcc, 0xd3, 0xda, 0xe1, 0xe8, 0xef, 0xf6, 0xfd, 0x04, 0x0b, 0x12, 0x19, 0x20, 0x27, 0x2e, + 0x35, 0x3c, 0x43, 0x4a, 0x51, 0x58, 0x5f, 0x66, 0x6d, 0x74}; +const WizardInputEntry API_WIZARD_INPUTS[API_WIZARD_INPUT_COUNT] = { + {WEATHER_KEY, wizard_input_weather()}, + {OTHER_KEY, wizard_input_other()}, +}; + +using Bytes = std::vector; + +static Bytes encode(const ProtoMessage &msg, uint32_t (*calc)(const void *), ProtoEncodeFn enc) { + APIBuffer buffer; + uint32_t size = calc(&msg); + EXPECT_TRUE(buffer.resize(size)); +#ifdef ESPHOME_DEBUG_API + uint8_t *proto_debug_end_ = buffer.data() + buffer.size(); +#endif + uint8_t *end = enc(&msg, buffer.data() PROTO_ENCODE_DEBUG_ARG); + EXPECT_EQ(static_cast(end - buffer.data()), size); + return Bytes(buffer.data(), buffer.data() + size); +} + +TEST(DeviceWizard, ResponseSendsTheDataUnchanged) { + DeviceWizardResponse resp; + resp.data = API_WIZARD_DATA; + resp.data_len = API_WIZARD_DATA_SIZE; + + // Field 1, length delimited, then the 200 byte length as a two byte varint, then the data + Bytes expected{0x0a, 0xc8, 0x01}; + expected.insert(expected.end(), API_WIZARD_DATA, API_WIZARD_DATA + API_WIZARD_DATA_SIZE); + + EXPECT_EQ(encode(resp, &DeviceWizardResponse::calc_size_msg, &DeviceWizardResponse::encode_msg), expected); +} + +TEST(DeviceWizard, ShortDataHasAOneByteLength) { + DeviceWizardResponse resp; + resp.data = API_WIZARD_DATA; + resp.data_len = 3; + + Bytes expected{0x0a, 0x03, API_WIZARD_DATA[0], API_WIZARD_DATA[1], API_WIZARD_DATA[2]}; + EXPECT_EQ(encode(resp, &DeviceWizardResponse::calc_size_msg, &DeviceWizardResponse::encode_msg), expected); +} + +TEST(DeviceWizard, NoDataEncodesNothing) { + DeviceWizardResponse resp; + EXPECT_EQ(resp.calculate_size(), 0u); + EXPECT_TRUE(encode(resp, &DeviceWizardResponse::calc_size_msg, &DeviceWizardResponse::encode_msg).empty()); +} + +TEST(DeviceWizard, CapabilitiesAnnounceTheWizard) { + DeviceCapabilitiesResponse resp; + EXPECT_TRUE( + encode(resp, &DeviceCapabilitiesResponse::calc_size_msg, &DeviceCapabilitiesResponse::encode_msg).empty()); + resp.wizard.configured = true; + // Field 5 (the wizard), length delimited, holding field 1 (configured) set to 1 + EXPECT_EQ(encode(resp, &DeviceCapabilitiesResponse::calc_size_msg, &DeviceCapabilitiesResponse::encode_msg), + (Bytes{0x2a, 0x02, 0x08, 0x01})); +} + +// A request for the input with the given key and entity id +static const char *set_input(uint32_t key, const char *entity_id) { + WizardInputSetRequest request; + request.key = key; + request.entity_id = StringRef(entity_id); + return wizard_set_input(request); +} + +TEST(DeviceWizard, SetInputStoresTheEntityIdInTheBuffer) { + EXPECT_EQ(set_input(OTHER_KEY, "sensor.outdoor"), wizard_input_other()); + EXPECT_STREQ(wizard_input_other(), "sensor.outdoor"); + // A shorter id replaces a longer one completely + EXPECT_EQ(set_input(OTHER_KEY, "light.a"), wizard_input_other()); + EXPECT_STREQ(wizard_input_other(), "light.a"); + // The longest valid id fills the buffer + std::string longest = "sensor." + std::string(WIZARD_ENTITY_ID_BUFFER_SIZE - 1 - 7, 'x'); + EXPECT_EQ(set_input(OTHER_KEY, longest.c_str()), wizard_input_other()); + EXPECT_EQ(std::string(wizard_input_other()), longest); +} + +TEST(DeviceWizard, SetInputFindsTheInputByItsKey) { + EXPECT_EQ(set_input(WEATHER_KEY, "weather.home"), wizard_input_weather()); + EXPECT_STREQ(wizard_input_weather(), "weather.home"); + EXPECT_EQ(set_input(OTHER_KEY, "weather.away"), wizard_input_other()); + EXPECT_STREQ(wizard_input_weather(), "weather.home"); +} + +TEST(DeviceWizard, SetInputIgnoresWhatIsNotAnEntityId) { + std::string before = wizard_input_weather(); + EXPECT_EQ(set_input(WEATHER_KEY, ""), nullptr); + EXPECT_EQ(set_input(WEATHER_KEY, "nodot"), nullptr); + std::string too_long = "sensor." + std::string(WIZARD_ENTITY_ID_BUFFER_SIZE, 'x'); + EXPECT_EQ(set_input(WEATHER_KEY, too_long.c_str()), nullptr); + EXPECT_EQ(set_input(0xdeadbeef, "sensor.a"), nullptr); + EXPECT_EQ(std::string(wizard_input_weather()), before); +} + +TEST(DeviceWizard, StandaloneInputReadsTheBufferTheWizardWrites) { + strcpy(wizard_input_other(), ""); + WizardInput input(wizard_input_other()); + EXPECT_FALSE(input.has_entity_id()); + EXPECT_TRUE(input.entity_id().empty()); + + ASSERT_EQ(set_input(OTHER_KEY, "weather.home"), wizard_input_other()); + EXPECT_TRUE(input.has_entity_id()); + EXPECT_EQ(input.entity_id(), "weather.home"); +} + +} // namespace esphome::api +#endif // USE_HOST diff --git a/tests/components/audio_http/test-custom-ca.esp32-idf.yaml b/tests/components/audio_http/test-custom-ca.esp32-idf.yaml new file mode 100644 index 0000000000..86eececf66 --- /dev/null +++ b/tests/components/audio_http/test-custom-ca.esp32-idf.yaml @@ -0,0 +1,6 @@ +media_source: + - platform: audio_http + id: audio_http_ca_source + ca_certificate_path: $component_dir/test_ca.pem + +<<: !include common.yaml diff --git a/tests/components/audio_http/test-inherit-ca.esp32-idf.yaml b/tests/components/audio_http/test-inherit-ca.esp32-idf.yaml new file mode 100644 index 0000000000..0bf22bc46d --- /dev/null +++ b/tests/components/audio_http/test-inherit-ca.esp32-idf.yaml @@ -0,0 +1,13 @@ +# http_request requires a network component; audio_http's common.yaml declares none. +wifi: + ssid: MySSID + password: password1 + +http_request: + ca_certificate_path: $component_dir/test_ca.pem + +media_source: + - platform: audio_http + id: audio_http_inherit_ca_source + +<<: !include common.yaml diff --git a/tests/components/audio_http/test_ca.pem b/tests/components/audio_http/test_ca.pem new file mode 100644 index 0000000000..30cbc1a3c4 --- /dev/null +++ b/tests/components/audio_http/test_ca.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBkTCB+wIJAKHBfpegPjMCMA0GCSqGSIb3DQEBCwUAMBExDzANBgNVBAMMBnVu +dXNlZDAeFw0yNDAxMDEwMDAwMDBaFw0yNTAxMDEwMDAwMDBaMBExDzANBgNVBAMM +BnVudXNlZDBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC5mMUB1hOgLmlnXtsvcGMP +XkhAqZaR0dDPW5OS8VEopWLJCX9Y0cvNCqiDI8cnP8pP8XJGU1hGLvA5PJzWnWZz +AgMBAAGjUzBRMB0GA1UdDgQWBBR5oQ9KqFeZOdBuAJrXxEP0dqzPtTAfBgNVHSME +GDAWgBR5oQ9KqFeZOdBuAJrXxEP0dqzPtTAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBCwUAA0EAKqZFf6+f8FPDbKyPCpssquojgn7fEXqr/I/yz0R5CowGdMms +H3WH3aKP4lLSHdPTBtfIoJi3gEIZjFxp3S1TWw== +-----END CERTIFICATE----- diff --git a/tests/components/bmi270/common.yaml b/tests/components/bmi270/common.yaml index 0f9b70631c..41f30fd5c6 100644 --- a/tests/components/bmi270/common.yaml +++ b/tests/components/bmi270/common.yaml @@ -61,6 +61,7 @@ sensor: motion: - platform: bmi270 id: bmi270_motion + i2c_id: i2c_bus # Accelerometer full-scale range: 2G | 4G | 8G | 16G accelerometer_range: 4G diff --git a/tests/components/bytebuffer/test_wrap_values.cpp b/tests/components/bytebuffer/test_wrap_values.cpp new file mode 100644 index 0000000000..9a470d8a08 --- /dev/null +++ b/tests/components/bytebuffer/test_wrap_values.cpp @@ -0,0 +1,34 @@ +#ifdef USE_HOST +#include + +#include +#include + +#include "esphome/components/bytebuffer/bytebuffer.h" + +namespace esphome::bytebuffer { + +// The expressions esp32_ble_server emits for typed constant values, and the bytes they produce. +// Doubles are written as float literals, so they carry float precision. +TEST(ByteBufferWrapValues, MatchesGeneratedConstants) { + EXPECT_EQ(ByteBuffer::wrap(uint8_t(18), LITTLE).get_data(), (std::vector{0x12})); + EXPECT_EQ(ByteBuffer::wrap(uint16_t(18), LITTLE).get_data(), (std::vector{0x12, 0x00})); + EXPECT_EQ(ByteBuffer::wrap(uint16_t(4660), BIG).get_data(), (std::vector{0x12, 0x34})); + EXPECT_EQ(ByteBuffer::wrap(uint32_t(305419896), LITTLE).get_data(), (std::vector{0x78, 0x56, 0x34, 0x12})); + EXPECT_EQ(ByteBuffer::wrap(uint64_t(1311768467294899695ULL), BIG).get_data(), + (std::vector{0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef})); + EXPECT_EQ(ByteBuffer::wrap(int8_t(-5), LITTLE).get_data(), (std::vector{0xfb})); + EXPECT_EQ(ByteBuffer::wrap(int16_t(-2), BIG).get_data(), (std::vector{0xff, 0xfe})); + EXPECT_EQ(ByteBuffer::wrap(int32_t(-2), LITTLE).get_data(), (std::vector{0xfe, 0xff, 0xff, 0xff})); + EXPECT_EQ(ByteBuffer::wrap(int64_t(-2), LITTLE).get_data(), + (std::vector{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})); + EXPECT_EQ(ByteBuffer::wrap(float(123.1f), BIG).get_data(), (std::vector{0x42, 0xf6, 0x33, 0x33})); + EXPECT_EQ(ByteBuffer::wrap(float(0.1f), LITTLE).get_data(), (std::vector{0xcd, 0xcc, 0xcc, 0x3d})); + EXPECT_EQ(ByteBuffer::wrap(double(0.1f), LITTLE).get_data(), + (std::vector{0x00, 0x00, 0x00, 0xa0, 0x99, 0x99, 0xb9, 0x3f})); + EXPECT_EQ(ByteBuffer::wrap(double(2.5f), BIG).get_data(), + (std::vector{0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); +} + +} // namespace esphome::bytebuffer +#endif diff --git a/tests/components/canbus/common.yaml b/tests/components/canbus/common.yaml index 3ba3564608..57ee25bfc1 100644 --- a/tests/components/canbus/common.yaml +++ b/tests/components/canbus/common.yaml @@ -18,6 +18,7 @@ canbus: lambda: "return x[0] == 0x11;" then: logger.log: Truth + - canbus.send: !lambda return x; - can_id: 0b00000000000000000000001000000 can_id_mask: 0b11111000000000011111111000000 use_extended_id: true @@ -59,6 +60,9 @@ button: data: [0, 1, 2] - canbus.send: "abc" - canbus.send: [0, 1, 2] + # Identical on purpose: both sends share one constant payload + - canbus.send: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88] + - canbus.send: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88] - canbus.send: !lambda return {0, 1, 2}; # Test canbus.send with lambda that references a component (function pointer) - canbus.send: !lambda |- diff --git a/tests/components/cc1101/common.yaml b/tests/components/cc1101/common.yaml index 4d2411e021..7f511a8d96 100644 --- a/tests/components/cc1101/common.yaml +++ b/tests/components/cc1101/common.yaml @@ -30,6 +30,8 @@ cc1101: then: - lambda: |- ESP_LOGD("cc1101", "packet %s freq_offset %.0f Hz rssi %.1f dBm lqi %u", format_hex(x).c_str(), freq_offset, rssi, lqi); + - cc1101.send_packet: !lambda |- + return x; button: - platform: template @@ -42,6 +44,8 @@ button: - cc1101.reset: transceiver - cc1101.send_packet: data: [0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef] + - cc1101.send_packet: [0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef] + - cc1101.send_packet: "ABCDEFGH" - cc1101.send_packet: !lambda |- return {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; diff --git a/tests/components/cc1101/test.esp8266.yaml b/tests/components/cc1101/test.esp8266-ard.yaml similarity index 100% rename from tests/components/cc1101/test.esp8266.yaml rename to tests/components/cc1101/test.esp8266-ard.yaml diff --git a/tests/components/combination/validate-deprecated.esp32-idf.yaml b/tests/components/combination/validate-deprecated.esp32-idf.yaml new file mode 100644 index 0000000000..829d76445d --- /dev/null +++ b/tests/components/combination/validate-deprecated.esp32-idf.yaml @@ -0,0 +1,15 @@ +sensor: + - platform: template + id: template_temperature1 + lambda: return 0.6; + - platform: template + id: template_temperature2 + lambda: return 0.8; + - platform: combination + type: linear + name: Linearly combined temperatures + sources: + - source: template_temperature1 + coeffecient: 0.5 + - source: template_temperature2 + coefficient: 1.5 diff --git a/tests/components/core/test_templatable_bytes.cpp b/tests/components/core/test_templatable_bytes.cpp new file mode 100644 index 0000000000..b75b8c1b55 --- /dev/null +++ b/tests/components/core/test_templatable_bytes.cpp @@ -0,0 +1,43 @@ +#include +#include +#include "esphome/core/automation.h" + +namespace esphome::testing { + +static const uint8_t PAYLOAD[] = {1, 2, 3}; + +static std::vector repeat(int count) { return std::vector(count, 7); } + +TEST(TemplatableBytesTest, VisitStaticTable) { + TemplatableBytes<> bytes; + bytes.set_static(PAYLOAD, sizeof(PAYLOAD)); + std::vector seen; + bytes.visit([&](const uint8_t *data, size_t len) { seen.assign(data, data + len); }); + EXPECT_EQ(seen, (std::vector{1, 2, 3})); +} + +TEST(TemplatableBytesTest, VisitWithLargerStackBuffer) { + TemplatableBytes<> bytes; + bytes.set_static(PAYLOAD, sizeof(PAYLOAD)); + size_t seen = 0; + bytes.visit<256>([&](const uint8_t *, size_t len) { seen = len; }); + EXPECT_EQ(seen, sizeof(PAYLOAD)); +} + +TEST(TemplatableBytesTest, VisitEmptyStaticTable) { + TemplatableBytes<> bytes; + bytes.set_static(nullptr, 0); + size_t seen = 1; + bytes.visit([&](const uint8_t *, size_t len) { seen = len; }); + EXPECT_EQ(seen, 0u); +} + +TEST(TemplatableBytesTest, VisitLambdaWithArgument) { + TemplatableBytes bytes; + bytes.set_template(repeat); + std::vector seen; + bytes.visit([&](const uint8_t *data, size_t len) { seen.assign(data, data + len); }, 4); + EXPECT_EQ(seen, (std::vector(4, 7))); +} + +} // namespace esphome::testing diff --git a/tests/components/debug/test.bk72xx-ard.yaml b/tests/components/debug/test.bk72xx-ard.yaml index fdae374788..65320a47d6 100644 --- a/tests/components/debug/test.bk72xx-ard.yaml +++ b/tests/components/debug/test.bk72xx-ard.yaml @@ -1,4 +1,5 @@ -<<: !include common.yaml +packages: + debug: !include common.yaml sensor: - platform: debug diff --git a/tests/components/debug/test.esp32-ard.yaml b/tests/components/debug/test.esp32-ard.yaml index 8f93b0925e..cdb1615be1 100644 --- a/tests/components/debug/test.esp32-ard.yaml +++ b/tests/components/debug/test.esp32-ard.yaml @@ -1,4 +1,5 @@ -<<: !include common.yaml +packages: + debug: !include common.yaml esp32: cpu_frequency: 240MHz diff --git a/tests/components/debug/test.esp32-idf.yaml b/tests/components/debug/test.esp32-idf.yaml index 6a9996ad06..73bc97b2a9 100644 --- a/tests/components/debug/test.esp32-idf.yaml +++ b/tests/components/debug/test.esp32-idf.yaml @@ -1,12 +1,11 @@ -<<: !include common.yaml +packages: + debug: !include common.yaml esp32: cpu_frequency: 240MHz sensor: - platform: debug - free: - name: "Heap Free" psram: name: "Free PSRAM" fragmentation: diff --git a/tests/components/debug/test.esp32-s2-idf.yaml b/tests/components/debug/test.esp32-s2-idf.yaml deleted file mode 100644 index 80919b0bab..0000000000 --- a/tests/components/debug/test.esp32-s2-idf.yaml +++ /dev/null @@ -1,8 +0,0 @@ -<<: !include common.yaml - -sensor: - - platform: debug - fragmentation: - name: "Heap Fragmentation" - min_free: - name: "Heap Min Free" diff --git a/tests/components/debug/test.esp8266-ard.yaml b/tests/components/debug/test.esp8266-ard.yaml index 1398087bf0..da54acfc0d 100644 --- a/tests/components/debug/test.esp8266-ard.yaml +++ b/tests/components/debug/test.esp8266-ard.yaml @@ -1,4 +1,5 @@ -<<: !include common.yaml +packages: + debug: !include common.yaml sensor: - platform: debug diff --git a/tests/components/debug/test.host.yaml b/tests/components/debug/test.host.yaml index dade44d145..25c05c6f81 100644 --- a/tests/components/debug/test.host.yaml +++ b/tests/components/debug/test.host.yaml @@ -1 +1,2 @@ -<<: !include common.yaml +packages: + debug: !include common.yaml diff --git a/tests/components/debug/test.ln882x-ard.yaml b/tests/components/debug/test.ln882x-ard.yaml deleted file mode 100644 index fdae374788..0000000000 --- a/tests/components/debug/test.ln882x-ard.yaml +++ /dev/null @@ -1,6 +0,0 @@ -<<: !include common.yaml - -sensor: - - platform: debug - min_free: - name: "Heap Min Free" diff --git a/tests/components/debug/test.nrf52-adafruit.yaml b/tests/components/debug/test.nrf52-adafruit.yaml index 6a446634af..53eea184f5 100644 --- a/tests/components/debug/test.nrf52-adafruit.yaml +++ b/tests/components/debug/test.nrf52-adafruit.yaml @@ -1,4 +1,5 @@ -<<: !include common.yaml +packages: + debug: !include common.yaml nrf52: reg0: diff --git a/tests/components/debug/test.nrf52-mcumgr.yaml b/tests/components/debug/test.nrf52-mcumgr.yaml index dade44d145..25c05c6f81 100644 --- a/tests/components/debug/test.nrf52-mcumgr.yaml +++ b/tests/components/debug/test.nrf52-mcumgr.yaml @@ -1 +1,2 @@ -<<: !include common.yaml +packages: + debug: !include common.yaml diff --git a/tests/components/debug/test.nrf52-xiao-ble.yaml b/tests/components/debug/test.nrf52-xiao-ble.yaml deleted file mode 100644 index dade44d145..0000000000 --- a/tests/components/debug/test.nrf52-xiao-ble.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common.yaml diff --git a/tests/components/debug/test.rp2040-ard.yaml b/tests/components/debug/test.rp2040-ard.yaml index dade44d145..25c05c6f81 100644 --- a/tests/components/debug/test.rp2040-ard.yaml +++ b/tests/components/debug/test.rp2040-ard.yaml @@ -1 +1,2 @@ -<<: !include common.yaml +packages: + debug: !include common.yaml diff --git a/tests/components/debug/test.rtl87xx-ard.yaml b/tests/components/debug/test.rtl87xx-ard.yaml deleted file mode 100644 index fdae374788..0000000000 --- a/tests/components/debug/test.rtl87xx-ard.yaml +++ /dev/null @@ -1,6 +0,0 @@ -<<: !include common.yaml - -sensor: - - platform: debug - min_free: - name: "Heap Min Free" diff --git a/tests/components/dlms_meter/common.yaml b/tests/components/dlms_meter/common.yaml index 59d854a3ae..fa6deff6be 100644 --- a/tests/components/dlms_meter/common.yaml +++ b/tests/components/dlms_meter/common.yaml @@ -4,63 +4,47 @@ dlms_meter: decryption_key: "36C66639E48A8CA4D6BC8B282A793BBB" auth_key: "11223344556677889900AABBCCDDEEFF" skip_crc: true - provider: "netznoe" custom_patterns: - "custom_pattern_1" - "custom_pattern_2" + - pattern: "L, TSTR" + name: "MeterID" + default_obis: "0.0.96.1.0.255" + - pattern: "F, TDTM" + name: "DateTime" + default_obis: "0.0.1.0.0.255" sensor: - # Old Schema tests - platform: dlms_meter - voltage_l1: - name: "Voltage L1" - voltage_l2: - name: "Voltage L2" - voltage_l3: - name: "Voltage L3" - current_l1: - name: "Current L1" - current_l2: - name: "Current L2" - current_l3: - name: "Current L3" - active_power_plus: - name: "Active power taken from grid" - active_power_minus: - name: "Active power put into grid" - active_energy_plus: - name: "Active energy taken from grid" - active_energy_minus: - name: "Active energy put into grid" - reactive_energy_plus: - name: "Reactive energy taken from grid" - reactive_energy_minus: - name: "Reactive energy put into grid" - power_factor: - name: "Power factor" + obis_code: "1.0.32.7.0.255" + name: "Voltage L1" + unit_of_measurement: V + accuracy_decimals: 1 + device_class: voltage + state_class: measurement + - platform: dlms_meter + obis_code: "1.0.1.8.0.255" + name: "Active energy taken from grid" + unit_of_measurement: Wh + device_class: energy + state_class: total_increasing - # Dynamic Schema tests - platform: dlms_meter dlms_meter_id: dlms_meter_hub obis_code: "1-0:99.99.9" name: "Custom Dynamic Sensor" text_sensor: - # Old Schema tests - platform: dlms_meter - timestamp: - name: "timestamp" - meternumber: - name: "Meter Number" + obis_code: "0.0.1.0.0.255" + name: "timestamp" - # Dynamic Schema tests - platform: dlms_meter dlms_meter_id: dlms_meter_hub obis_code: "0-0:99.99.9" name: "Custom Dynamic Text Sensor" binary_sensor: - # Dynamic Schema tests (Binary sensors only use the dynamic schema) - platform: dlms_meter dlms_meter_id: dlms_meter_hub obis_code: "0-1:2.3.4" diff --git a/tests/components/ds248x/common.yaml b/tests/components/ds248x/common.yaml index 53ae56f20a..4429f5b772 100644 --- a/tests/components/ds248x/common.yaml +++ b/tests/components/ds248x/common.yaml @@ -5,10 +5,12 @@ # - DS2484: adjustable 1-Wire timing + RWPU pullup resistor selection ds248x: - id: ds2482_100 + i2c_id: i2c_bus address: 0x18 type: ds2482-100 active_pullup: true - id: ds2482_101 + i2c_id: i2c_bus address: 0x19 type: ds2482-101 active_pullup: true @@ -18,10 +20,12 @@ ds248x: bus_sleep: true hub_sleep: true - id: ds2482_800 + i2c_id: i2c_bus address: 0x1a type: ds2482-800 active_pullup: true - id: ds2484_hub + i2c_id: i2c_bus address: 0x1b type: ds2484 active_pullup: true diff --git a/tests/components/epaper_spi/display/test_ssd1677_transfer.cpp b/tests/components/epaper_spi/display/test_ssd1677_transfer.cpp index cad853b197..01eea3c83a 100644 --- a/tests/components/epaper_spi/display/test_ssd1677_transfer.cpp +++ b/tests/components/epaper_spi/display/test_ssd1677_transfer.cpp @@ -196,6 +196,21 @@ TEST(EPaperSSD1677, RequestedFullUpdateAppliesWhenTheNextUpdateStarts) { EXPECT_FALSE(display.run_update_state()) << "request was applied more than once"; } +/// The display reports updating from the start of an update until it is back to idle. +TEST(EPaperSSD1677, IsUpdatingUntilBackToIdle) { + TestableSSD1677 display(16, 2); + RecordingDelegate bus(&display.dc); + display.install(&bus, 5); + + EXPECT_FALSE(display.is_updating()); + display.request_full_update(); + EXPECT_TRUE(display.run_update_state()); + EXPECT_TRUE(display.is_updating()); + + EXPECT_FALSE(display.run_update_state()) << "an update with nothing drawn should not push"; + EXPECT_FALSE(display.is_updating()); +} + /// Nothing a partial needs lives in controller RAM any more, so a partial push skips the reset /// altogether; a full one still gets the hardware pulse and the software reset. TEST(EPaperSSD1677, PartialPushSkipsTheResetAndAFullPushKeepsIt) { diff --git a/tests/components/epaper_spi/test.esp32-s3-idf.yaml b/tests/components/epaper_spi/test.esp32-s3-idf.yaml index 7a4ce3a5ff..8cb2a99521 100644 --- a/tests/components/epaper_spi/test.esp32-s3-idf.yaml +++ b/tests/components/epaper_spi/test.esp32-s3-idf.yaml @@ -8,6 +8,11 @@ esphome: on_boot: then: - epaper_spi.full_update_next: epaper_partial + - if: + condition: + epaper_spi.is_updating: epaper_partial + then: + - component.update: epaper_partial display: - platform: epaper_spi diff --git a/tests/components/esp32_ble_server/common.yaml b/tests/components/esp32_ble_server/common.yaml index c617a73f87..b513c43234 100644 --- a/tests/components/esp32_ble_server/common.yaml +++ b/tests/components/esp32_ble_server/common.yaml @@ -77,6 +77,46 @@ esp32_ble_server: id: test_change_descriptor value: data: [0x01, 0x02, 0x03] + # Identical constants, strings and typed numbers + - ble_server.characteristic.set_value: + id: test_change_characteristic + value: + data: [0xfc, 0xef, 0xfe, 0x86] + - ble_server.characteristic.set_value: + id: test_change_characteristic + value: "Changed" + - ble_server.characteristic.set_value: + id: test_change_characteristic + value: + data: 4660 + type: uint16_t + endianness: BIG + - ble_server.characteristic.set_value: + id: test_change_characteristic + value: + data: -2 + type: int32_t + - ble_server.characteristic.set_value: + id: test_change_characteristic + value: + data: 0.1 + type: double + - ble_server.descriptor.set_value: + id: test_change_descriptor + value: + data: 123.1 + type: float + endianness: BIG + - ble_server.descriptor.set_value: + id: test_change_descriptor + value: "Changed" + # Lambdas that read the trigger argument + - ble_server.characteristic.set_value: + id: test_change_characteristic + value: !lambda return x; + - ble_server.descriptor.set_value: + id: test_change_descriptor + value: !lambda return x; # Regression test for #17142: the set_value action used from a trigger that passes # its argument by reference (climate on_control supplies ClimateCall&) previously diff --git a/tests/components/espnow/common.yaml b/tests/components/espnow/common.yaml index fec0dd9f3e..973781fb68 100644 --- a/tests/components/espnow/common.yaml +++ b/tests/components/espnow/common.yaml @@ -36,6 +36,9 @@ espnow: data: [0x01, 0x02, 0x03, 0x04, 0x05] - espnow.broadcast: data: !lambda 'return {0x01, 0x02, 0x03, 0x04, 0x05};' + - espnow.send: + address: 11:22:33:44:55:66 + data: !lambda 'return std::vector(data, data + size);' - espnow.peer.add: address: 11:22:33:44:55:66 - espnow.peer.add: diff --git a/tests/components/ethernet/common-w5500-spi-id.yaml b/tests/components/ethernet/common-w5500-spi-id.yaml deleted file mode 100644 index 14c01842f0..0000000000 --- a/tests/components/ethernet/common-w5500-spi-id.yaml +++ /dev/null @@ -1,17 +0,0 @@ -ethernet: - type: W5500 - spi_id: spi_bus - cs_pin: 5 - interrupt_pin: 36 - reset_pin: 22 - clock_speed: 10Mhz - manual_ip: - static_ip: 192.168.178.56 - gateway: 192.168.178.1 - subnet: 255.255.255.0 - domain: .local - mac_address: "02:AA:BB:CC:DD:01" - on_connect: - - logger.log: "Ethernet connected!" - on_disconnect: - - logger.log: "Ethernet disconnected!" diff --git a/tests/components/ethernet/test-ip101.esp32-idf.yaml b/tests/components/ethernet/test-ip101.esp32-idf.yaml index e52329d7ea..628b63d4e9 100644 --- a/tests/components/ethernet/test-ip101.esp32-idf.yaml +++ b/tests/components/ethernet/test-ip101.esp32-idf.yaml @@ -1 +1,6 @@ -<<: !include common-ip101.yaml +packages: + ethernet: !include common-ip101.yaml + +# IPv6 on the internal EMAC: compiles the IPv6 event handler and multicast filter +network: + enable_ipv6: true diff --git a/tests/components/ethernet/test-ipv6.esp32-idf.yaml b/tests/components/ethernet/test-ipv6.esp32-idf.yaml deleted file mode 100644 index 607e82dfa9..0000000000 --- a/tests/components/ethernet/test-ipv6.esp32-idf.yaml +++ /dev/null @@ -1,12 +0,0 @@ -network: - enable_ipv6: true - -ethernet: - type: IP101 - mdc_pin: 23 - mdio_pin: 18 - clk: - pin: 0 - mode: CLK_EXT_IN - phy_addr: 1 - power_pin: 5 diff --git a/tests/components/ethernet/test-lifecycle.esp32-idf.yaml b/tests/components/ethernet/test-lifecycle.esp32-idf.yaml deleted file mode 100644 index 904a916789..0000000000 --- a/tests/components/ethernet/test-lifecycle.esp32-idf.yaml +++ /dev/null @@ -1,39 +0,0 @@ -ethernet: - id: eth - type: W5500 - clk_pin: 19 - mosi_pin: 21 - miso_pin: 23 - cs_pin: 18 - interrupt_pin: 36 - reset_pin: 22 - enable_on_boot: false - manual_ip: - static_ip: 192.168.178.56 - gateway: 192.168.178.1 - subnet: 255.255.255.0 - mac_address: "02:AA:BB:CC:DD:01" - interface: spi2 - -esphome: - on_boot: - priority: 200 - then: - - if: - condition: - not: - ethernet.enabled: - then: - - ethernet.enable: - -button: - - platform: template - name: "Disable Ethernet" - on_press: - - ethernet.disable: - -binary_sensor: - - platform: template - name: "Ethernet Connected" - lambda: |- - return id(eth).is_connected(); diff --git a/tests/components/ethernet/test-w5500-spi-id.esp32-idf.yaml b/tests/components/ethernet/test-w5500-spi-id.esp32-idf.yaml deleted file mode 100644 index 16a25ced2a..0000000000 --- a/tests/components/ethernet/test-w5500-spi-id.esp32-idf.yaml +++ /dev/null @@ -1,3 +0,0 @@ -packages: - spi: !include ../../test_build_components/common/spi/esp32-idf.yaml - ethernet: !include common-w5500-spi-id.yaml diff --git a/tests/components/ethernet/test-w5500-wifi-psram.esp32-s3-idf.yaml b/tests/components/ethernet/test-w5500-wifi-psram.esp32-s3-idf.yaml deleted file mode 100644 index d454dbadc3..0000000000 --- a/tests/components/ethernet/test-w5500-wifi-psram.esp32-s3-idf.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# W5500 next to wifi with PSRAM: compiles the input path that moves received frames to PSRAM -packages: - ethernet: !include common-w5500.yaml - -psram: - mode: quad - -wifi: - ssid: MySSID - password: password1 - -network: - priority: - - ethernet - - wifi diff --git a/tests/components/ethernet/test-w5500.esp32-idf.yaml b/tests/components/ethernet/test-w5500.esp32-idf.yaml index f1551fef60..c99dfaf528 100644 --- a/tests/components/ethernet/test-w5500.esp32-idf.yaml +++ b/tests/components/ethernet/test-w5500.esp32-idf.yaml @@ -1,20 +1,47 @@ +# W5500 on a shared SPI bus, started from an automation instead of on boot. +# Covers the spi_id attach path and the enable/disable lifecycle in one build. +packages: + spi: !include ../../test_build_components/common/spi/esp32-idf.yaml + ethernet: + id: eth type: W5500 - clk_pin: 19 - mosi_pin: 21 - miso_pin: 23 - cs_pin: 18 + spi_id: spi_bus + cs_pin: 5 interrupt_pin: 36 reset_pin: 22 clock_speed: 10Mhz + enable_on_boot: false manual_ip: static_ip: 192.168.178.56 gateway: 192.168.178.1 subnet: 255.255.255.0 domain: .local mac_address: "02:AA:BB:CC:DD:01" - interface: spi2 on_connect: - logger.log: "Ethernet connected!" on_disconnect: - logger.log: "Ethernet disconnected!" + +esphome: + on_boot: + priority: 200 + then: + - if: + condition: + not: + ethernet.enabled: + then: + - ethernet.enable: + +button: + - platform: template + name: "Disable Ethernet" + on_press: + - ethernet.disable: + +binary_sensor: + - platform: template + name: "Ethernet Connected" + lambda: |- + return id(eth).is_connected(); diff --git a/tests/components/ethernet/test.esp32-s3-idf.yaml b/tests/components/ethernet/test.esp32-s3-idf.yaml index 36f1b5365f..d454dbadc3 100644 --- a/tests/components/ethernet/test.esp32-s3-idf.yaml +++ b/tests/components/ethernet/test.esp32-s3-idf.yaml @@ -1 +1,15 @@ -<<: !include common-w5500.yaml +# W5500 next to wifi with PSRAM: compiles the input path that moves received frames to PSRAM +packages: + ethernet: !include common-w5500.yaml + +psram: + mode: quad + +wifi: + ssid: MySSID + password: password1 + +network: + priority: + - ethernet + - wifi diff --git a/tests/components/ethernet/test-ksz8081rna.esp32-idf.yaml b/tests/components/ethernet/validate-ksz8081rna.esp32-idf.yaml similarity index 100% rename from tests/components/ethernet/test-ksz8081rna.esp32-idf.yaml rename to tests/components/ethernet/validate-ksz8081rna.esp32-idf.yaml diff --git a/tests/components/ethernet/test-lan8720-with-expander.esp32-idf.yaml b/tests/components/ethernet/validate-lan8720-with-expander.esp32-idf.yaml similarity index 100% rename from tests/components/ethernet/test-lan8720-with-expander.esp32-idf.yaml rename to tests/components/ethernet/validate-lan8720-with-expander.esp32-idf.yaml diff --git a/tests/components/ethernet/validate-w5500-interface.esp32-idf.yaml b/tests/components/ethernet/validate-w5500-interface.esp32-idf.yaml new file mode 100644 index 0000000000..a2c62e4a52 --- /dev/null +++ b/tests/components/ethernet/validate-w5500-interface.esp32-idf.yaml @@ -0,0 +1,12 @@ +# Config only: a user supplied SPI interface on dedicated pins exercises the +# interface option in validation. +ethernet: + type: W5500 + interface: spi3 + clk_pin: 19 + mosi_pin: 21 + miso_pin: 17 + cs_pin: 18 + interrupt_pin: 36 + reset_pin: 12 + clock_speed: 10Mhz diff --git a/tests/components/http_request/http_request.yaml b/tests/components/http_request/http_request.yaml index eb8a6d33e7..b82c0581d1 100644 --- a/tests/components/http_request/http_request.yaml +++ b/tests/components/http_request/http_request.yaml @@ -71,6 +71,8 @@ esphome: # encode_json_ receives const copies of non-const reference args. - http_request.post: url: https://esphome.io + request_headers: + X-Body-Length: !lambda 'return body.empty() ? "0" : "1";' json: status: "ok" # Same with json: lambda variant, exercises json_func_ path @@ -102,3 +104,23 @@ script: then: - http_request.get: url: "http://google.com" + + - id: send_with_headers + parameters: + token: string + then: + - http_request.get: + url: https://esphome.io + request_headers: + Accept: application/json + X-Device: esphome + - http_request.get: + url: https://esphome.io/other + request_headers: + Accept: application/json + X-Device: esphome + - http_request.get: + url: https://esphome.io + request_headers: + Accept: application/json + X-Token-Set: !lambda 'return token.empty() ? "no" : "yes";' diff --git a/tests/components/improv_serial/common-usb_serial_jtag.yaml b/tests/components/improv_serial/common-usb_serial_jtag.yaml deleted file mode 100644 index 0abcc07168..0000000000 --- a/tests/components/improv_serial/common-usb_serial_jtag.yaml +++ /dev/null @@ -1,8 +0,0 @@ -wifi: - ssid: MySSID - password: password1 - -logger: - hardware_uart: USB_SERIAL_JTAG - -improv_serial: diff --git a/tests/components/improv_serial/common-uart0.yaml b/tests/components/improv_serial/common.yaml similarity index 100% rename from tests/components/improv_serial/common-uart0.yaml rename to tests/components/improv_serial/common.yaml diff --git a/tests/components/improv_serial/test-uart-bus.esp8266-ard.yaml b/tests/components/improv_serial/test-uart-bus.esp8266-ard.yaml deleted file mode 100644 index 40a6b7f4fe..0000000000 --- a/tests/components/improv_serial/test-uart-bus.esp8266-ard.yaml +++ /dev/null @@ -1,3 +0,0 @@ -packages: - uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml - improv_serial: !include common-uart-bus.yaml diff --git a/tests/components/improv_serial/test-uart0.esp32-c3-idf.yaml b/tests/components/improv_serial/test-uart0.esp32-c3-idf.yaml deleted file mode 100644 index ef8c799241..0000000000 --- a/tests/components/improv_serial/test-uart0.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-uart0.yaml diff --git a/tests/components/improv_serial/test-uart0.esp32-idf.yaml b/tests/components/improv_serial/test-uart0.esp32-idf.yaml deleted file mode 100644 index ef8c799241..0000000000 --- a/tests/components/improv_serial/test-uart0.esp32-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-uart0.yaml diff --git a/tests/components/improv_serial/test-uart0.esp32-s2-idf.yaml b/tests/components/improv_serial/test-uart0.esp32-s2-idf.yaml deleted file mode 100644 index ef8c799241..0000000000 --- a/tests/components/improv_serial/test-uart0.esp32-s2-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-uart0.yaml diff --git a/tests/components/improv_serial/test-uart0.esp32-s3-idf.yaml b/tests/components/improv_serial/test-uart0.esp32-s3-idf.yaml deleted file mode 100644 index ef8c799241..0000000000 --- a/tests/components/improv_serial/test-uart0.esp32-s3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-uart0.yaml diff --git a/tests/components/improv_serial/test-uart0.esp8266-ard.yaml b/tests/components/improv_serial/test-uart0.esp8266-ard.yaml deleted file mode 100644 index ef8c799241..0000000000 --- a/tests/components/improv_serial/test-uart0.esp8266-ard.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-uart0.yaml diff --git a/tests/components/improv_serial/test-uart0.rp2040-ard.yaml b/tests/components/improv_serial/test-uart0.rp2040-ard.yaml deleted file mode 100644 index ef8c799241..0000000000 --- a/tests/components/improv_serial/test-uart0.rp2040-ard.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-uart0.yaml diff --git a/tests/components/improv_serial/test-usb_cdc.esp32-s2-idf.yaml b/tests/components/improv_serial/test-usb_cdc.esp32-s2-idf.yaml index cfdaec9771..f4217b994d 100644 --- a/tests/components/improv_serial/test-usb_cdc.esp32-s2-idf.yaml +++ b/tests/components/improv_serial/test-usb_cdc.esp32-s2-idf.yaml @@ -1 +1,2 @@ -<<: !include common-usb_cdc.yaml +packages: + improv_serial: !include common-usb_cdc.yaml diff --git a/tests/components/improv_serial/test-usb_cdc.rp2040-ard.yaml b/tests/components/improv_serial/test-usb_cdc.rp2040-ard.yaml deleted file mode 100644 index cfdaec9771..0000000000 --- a/tests/components/improv_serial/test-usb_cdc.rp2040-ard.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-usb_cdc.yaml diff --git a/tests/components/improv_serial/test-usb_serial_jtag.esp32-c3-idf.yaml b/tests/components/improv_serial/test-usb_serial_jtag.esp32-c3-idf.yaml deleted file mode 100644 index 46a927ff10..0000000000 --- a/tests/components/improv_serial/test-usb_serial_jtag.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-usb_serial_jtag.yaml diff --git a/tests/components/improv_serial/test-usb_serial_jtag.esp32-s3-idf.yaml b/tests/components/improv_serial/test-usb_serial_jtag.esp32-s3-idf.yaml deleted file mode 100644 index 46a927ff10..0000000000 --- a/tests/components/improv_serial/test-usb_serial_jtag.esp32-s3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-usb_serial_jtag.yaml diff --git a/tests/components/improv_serial/test.esp32-c3-idf.yaml b/tests/components/improv_serial/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..2a833811fc --- /dev/null +++ b/tests/components/improv_serial/test.esp32-c3-idf.yaml @@ -0,0 +1,2 @@ +packages: + improv_serial: !include common.yaml diff --git a/tests/components/improv_serial/test.esp32-idf.yaml b/tests/components/improv_serial/test.esp32-idf.yaml new file mode 100644 index 0000000000..2a833811fc --- /dev/null +++ b/tests/components/improv_serial/test.esp32-idf.yaml @@ -0,0 +1,2 @@ +packages: + improv_serial: !include common.yaml diff --git a/tests/components/improv_serial/test.esp8266-ard.yaml b/tests/components/improv_serial/test.esp8266-ard.yaml new file mode 100644 index 0000000000..2a833811fc --- /dev/null +++ b/tests/components/improv_serial/test.esp8266-ard.yaml @@ -0,0 +1,2 @@ +packages: + improv_serial: !include common.yaml diff --git a/tests/components/improv_serial/test.rp2040-ard.yaml b/tests/components/improv_serial/test.rp2040-ard.yaml new file mode 100644 index 0000000000..2a833811fc --- /dev/null +++ b/tests/components/improv_serial/test.rp2040-ard.yaml @@ -0,0 +1,2 @@ +packages: + improv_serial: !include common.yaml diff --git a/tests/components/ir_rf_proxy/test-cc1101.esp8266-ard.yaml b/tests/components/ir_rf_proxy/test-cc1101.esp8266-ard.yaml deleted file mode 100644 index e25c47ab23..0000000000 --- a/tests/components/ir_rf_proxy/test-cc1101.esp8266-ard.yaml +++ /dev/null @@ -1,9 +0,0 @@ -substitutions: - cs_pin: GPIO5 - gdo0_pin: GPIO4 - gdo2_pin: GPIO16 - -packages: - common: !include common.yaml - spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml - cc1101: !include common-cc1101.yaml diff --git a/tests/components/ir_rf_proxy/test-rx.esp8266-ard.yaml b/tests/components/ir_rf_proxy/test-rx.esp8266-ard.yaml deleted file mode 100644 index 8172885b31..0000000000 --- a/tests/components/ir_rf_proxy/test-rx.esp8266-ard.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - tx_pin: GPIO4 - rx_pin: GPIO5 - -packages: - common: !include common.yaml - rx: !include common-rx.yaml diff --git a/tests/components/ir_rf_proxy/test-rx.rp2040-ard.yaml b/tests/components/ir_rf_proxy/test-rx.rp2040-ard.yaml deleted file mode 100644 index 8172885b31..0000000000 --- a/tests/components/ir_rf_proxy/test-rx.rp2040-ard.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - tx_pin: GPIO4 - rx_pin: GPIO5 - -packages: - common: !include common.yaml - rx: !include common-rx.yaml diff --git a/tests/components/ir_rf_proxy/test-tx.esp32-idf.yaml b/tests/components/ir_rf_proxy/test-tx.esp32-idf.yaml deleted file mode 100644 index 7162f15b2d..0000000000 --- a/tests/components/ir_rf_proxy/test-tx.esp32-idf.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - tx_pin: GPIO4 - rx_pin: GPIO5 - -packages: - common: !include common.yaml - tx: !include common-tx.yaml diff --git a/tests/components/ir_rf_proxy/test-tx.esp8266-ard.yaml b/tests/components/ir_rf_proxy/test-tx.esp8266-ard.yaml deleted file mode 100644 index 7162f15b2d..0000000000 --- a/tests/components/ir_rf_proxy/test-tx.esp8266-ard.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - tx_pin: GPIO4 - rx_pin: GPIO5 - -packages: - common: !include common.yaml - tx: !include common-tx.yaml diff --git a/tests/components/ir_rf_proxy/test-tx.rp2040-ard.yaml b/tests/components/ir_rf_proxy/test-tx.rp2040-ard.yaml deleted file mode 100644 index 7162f15b2d..0000000000 --- a/tests/components/ir_rf_proxy/test-tx.rp2040-ard.yaml +++ /dev/null @@ -1,7 +0,0 @@ -substitutions: - tx_pin: GPIO4 - rx_pin: GPIO5 - -packages: - common: !include common.yaml - tx: !include common-tx.yaml diff --git a/tests/components/logger/log_tag_test.cpp b/tests/components/logger/log_tag_test.cpp new file mode 100644 index 0000000000..c702790da1 --- /dev/null +++ b/tests/components/logger/log_tag_test.cpp @@ -0,0 +1,31 @@ +#include +#include +#include + +#include "esphome/components/logger/log_buffer.h" +#include "esphome/components/logger/logger.h" +#include "esphome/core/log.h" + +namespace esphome::logger::testing { + +ESPHOME_LOG_TAG(TEST_TAG, "test.tag"); + +TEST(LogTagTest, HeaderContainsTagAndLine) { + char data[256]; + LogBuffer buf{data, sizeof(data)}; + buf.write_header(5, TEST_TAG, 42, nullptr); + const std::string header(data, buf.pos); + EXPECT_NE(header.find("[D][test.tag:042]"), std::string::npos) << header; +} + +TEST(LogTagTest, PerTagLevelLookupMatchesByContent) { + std::map levels{{"api", 1}, {"sensor", 2}, {"wifi", 3}}; + // Copies, so a match must compare content rather than pointers + char api[] = "api", sensor[] = "sensor", wifi[] = "wifi", missing[] = "switch"; + EXPECT_EQ(levels.find(FlashTag{api})->second, 1); + EXPECT_EQ(levels.find(FlashTag{sensor})->second, 2); + EXPECT_EQ(levels.find(FlashTag{wifi})->second, 3); + EXPECT_EQ(levels.find(FlashTag{missing}), levels.end()); +} + +} // namespace esphome::logger::testing diff --git a/tests/components/logger/test-custom_buffer_size.esp32-idf.yaml b/tests/components/logger/test-custom_buffer_size.esp32-idf.yaml deleted file mode 100644 index 9a396ca023..0000000000 --- a/tests/components/logger/test-custom_buffer_size.esp32-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -<<: !include common-default_uart.yaml - -logger: - id: logger_id - task_log_buffer_size: 1024B # Set a custom buffer size diff --git a/tests/components/logger/test-disable_log_buffer.esp32-idf.yaml b/tests/components/logger/test-disable_log_buffer.esp32-idf.yaml deleted file mode 100644 index 4260f178f9..0000000000 --- a/tests/components/logger/test-disable_log_buffer.esp32-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -<<: !include common-default_uart.yaml - -logger: - id: logger_id - task_log_buffer_size: 0 diff --git a/tests/components/logger/test-uart0.ln882x-ard.yaml b/tests/components/logger/test-uart0.ln882x-ard.yaml deleted file mode 100644 index 864d2b96de..0000000000 --- a/tests/components/logger/test-uart0.ln882x-ard.yaml +++ /dev/null @@ -1,2 +0,0 @@ -logger: - hardware_uart: UART0 diff --git a/tests/components/logger/test-usb_cdc.esp32-c3-idf.yaml b/tests/components/logger/test-usb_cdc.esp32-c3-idf.yaml deleted file mode 100644 index cfdaec9771..0000000000 --- a/tests/components/logger/test-usb_cdc.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-usb_cdc.yaml diff --git a/tests/components/logger/test-usb_cdc.esp32-s2-idf.yaml b/tests/components/logger/test-usb_cdc.esp32-s2-idf.yaml deleted file mode 100644 index cfdaec9771..0000000000 --- a/tests/components/logger/test-usb_cdc.esp32-s2-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-usb_cdc.yaml diff --git a/tests/components/logger/test-usb_serial_jtag.esp32-c3-idf.yaml b/tests/components/logger/test-usb_serial_jtag.esp32-c3-idf.yaml deleted file mode 100644 index 46a927ff10..0000000000 --- a/tests/components/logger/test-usb_serial_jtag.esp32-c3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-usb_serial_jtag.yaml diff --git a/tests/components/logger/test-usb_serial_jtag.esp32-s3-idf.yaml b/tests/components/logger/test-usb_serial_jtag.esp32-s3-idf.yaml deleted file mode 100644 index 46a927ff10..0000000000 --- a/tests/components/logger/test-usb_serial_jtag.esp32-s3-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-usb_serial_jtag.yaml diff --git a/tests/components/logger/test.esp32-c3-idf.yaml b/tests/components/logger/test.esp32-c3-idf.yaml index 3fe04e18a3..4260f178f9 100644 --- a/tests/components/logger/test.esp32-c3-idf.yaml +++ b/tests/components/logger/test.esp32-c3-idf.yaml @@ -1 +1,5 @@ <<: !include common-default_uart.yaml + +logger: + id: logger_id + task_log_buffer_size: 0 diff --git a/tests/components/logger/test-on_message.host.yaml b/tests/components/logger/test.host.yaml similarity index 100% rename from tests/components/logger/test-on_message.host.yaml rename to tests/components/logger/test.host.yaml diff --git a/tests/components/logger/test.ln882x-ard.yaml b/tests/components/logger/test.ln882x-ard.yaml index c5bc9de6e8..d5c62d298a 100644 --- a/tests/components/logger/test.ln882x-ard.yaml +++ b/tests/components/logger/test.ln882x-ard.yaml @@ -2,4 +2,5 @@ logger: id: logger_id + hardware_uart: UART0 task_log_buffer_size: 1024B diff --git a/tests/components/logger/test-max_buffer_size.esp32-idf.yaml b/tests/components/logger/validate-max_buffer_size.esp32-idf.yaml similarity index 100% rename from tests/components/logger/test-max_buffer_size.esp32-idf.yaml rename to tests/components/logger/validate-max_buffer_size.esp32-idf.yaml diff --git a/tests/components/logger/test-min_buffer_size.esp32-idf.yaml b/tests/components/logger/validate-min_buffer_size.esp32-idf.yaml similarity index 100% rename from tests/components/logger/test-min_buffer_size.esp32-idf.yaml rename to tests/components/logger/validate-min_buffer_size.esp32-idf.yaml diff --git a/tests/components/lsm6ds/common.yaml b/tests/components/lsm6ds/common.yaml index aeacd31448..40bfbfc2a1 100644 --- a/tests/components/lsm6ds/common.yaml +++ b/tests/components/lsm6ds/common.yaml @@ -61,6 +61,7 @@ sensor: motion: - platform: lsm6ds id: lsm6ds_motion + i2c_id: i2c_bus # Accelerometer full-scale range: 2G | 4G | 8G | 16G accelerometer_range: 4G diff --git a/tests/components/ltr501/common.yaml b/tests/components/ltr501/common.yaml index 77c6f13739..406450f8c5 100644 --- a/tests/components/ltr501/common.yaml +++ b/tests/components/ltr501/common.yaml @@ -1,5 +1,6 @@ sensor: - platform: ltr501 + i2c_id: i2c_bus address: 0x23 type: ALS_PS gain: 1X diff --git a/tests/components/ltr_als_ps/common.yaml b/tests/components/ltr_als_ps/common.yaml index edad4b2e4f..6c555eeb74 100644 --- a/tests/components/ltr_als_ps/common.yaml +++ b/tests/components/ltr_als_ps/common.yaml @@ -1,5 +1,6 @@ sensor: - platform: ltr_als_ps + i2c_id: i2c_bus address: 0x23 gain: 1x integration_time: 100ms diff --git a/tests/components/mcp2515/common.yaml b/tests/components/mcp2515/common.yaml index 15639df5fe..2e88f3e5c6 100644 --- a/tests/components/mcp2515/common.yaml +++ b/tests/components/mcp2515/common.yaml @@ -10,6 +10,11 @@ canbus: - lambda: |- std::string b(x.begin(), x.end()); ESP_LOGD("can_id 500", "%s", b.c_str()); + - canbus.send: + canbus_id: mcp2515_can + can_id: 501 + data: !lambda |- + return {static_cast(can_id), x.empty() ? uint8_t(0) : x[0]}; - can_id: 23 then: - if: @@ -36,3 +41,22 @@ canbus: break; // to be continued... } + +button: + - platform: template + name: MCP2515 Send + on_press: + # Identical on purpose: both sends share one constant payload + - canbus.send: + canbus_id: mcp2515_can + data: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88] + - canbus.send: + canbus_id: mcp2515_can + data: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88] + - canbus.send: + canbus_id: mcp2515_can + can_id: 0x123 + data: "abc" + - canbus.send: + canbus_id: mcp2515_can + data: !lambda return {0x01, 0x02}; diff --git a/tests/components/mdns/common-enabled-ethernet.yaml b/tests/components/mdns/common-ethernet.yaml similarity index 95% rename from tests/components/mdns/common-enabled-ethernet.yaml rename to tests/components/mdns/common-ethernet.yaml index bfa9321d43..95a5d0edc8 100644 --- a/tests/components/mdns/common-enabled-ethernet.yaml +++ b/tests/components/mdns/common-ethernet.yaml @@ -14,7 +14,6 @@ ethernet: mac_address: "02:AA:BB:CC:DD:01" mdns: - disabled: false services: - service: _test_service protocol: _tcp diff --git a/tests/components/mdns/common-enabled.yaml b/tests/components/mdns/common.yaml similarity index 92% rename from tests/components/mdns/common-enabled.yaml rename to tests/components/mdns/common.yaml index 8b3d81cf69..8d0154fe9d 100644 --- a/tests/components/mdns/common-enabled.yaml +++ b/tests/components/mdns/common.yaml @@ -3,7 +3,6 @@ wifi: password: password1 mdns: - disabled: false services: - service: _test_service protocol: _tcp diff --git a/tests/components/mdns/test-enabled-ethernet.rp2040-ard.yaml b/tests/components/mdns/test-enabled-ethernet.rp2040-ard.yaml deleted file mode 100644 index f84a0bc276..0000000000 --- a/tests/components/mdns/test-enabled-ethernet.rp2040-ard.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-enabled-ethernet.yaml diff --git a/tests/components/mdns/test-enabled.esp32-idf.yaml b/tests/components/mdns/test-enabled.esp32-idf.yaml deleted file mode 100644 index 97fd63d70e..0000000000 --- a/tests/components/mdns/test-enabled.esp32-idf.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-enabled.yaml diff --git a/tests/components/mdns/test-enabled.esp8266-ard.yaml b/tests/components/mdns/test-enabled.esp8266-ard.yaml deleted file mode 100644 index 97fd63d70e..0000000000 --- a/tests/components/mdns/test-enabled.esp8266-ard.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-enabled.yaml diff --git a/tests/components/mdns/test-enabled.rp2040-ard.yaml b/tests/components/mdns/test-enabled.rp2040-ard.yaml deleted file mode 100644 index 97fd63d70e..0000000000 --- a/tests/components/mdns/test-enabled.rp2040-ard.yaml +++ /dev/null @@ -1 +0,0 @@ -<<: !include common-enabled.yaml diff --git a/tests/components/mdns/test-ethernet.rp2040-ard.yaml b/tests/components/mdns/test-ethernet.rp2040-ard.yaml new file mode 100644 index 0000000000..4c2dfce1e3 --- /dev/null +++ b/tests/components/mdns/test-ethernet.rp2040-ard.yaml @@ -0,0 +1,2 @@ +packages: + mdns: !include common-ethernet.yaml diff --git a/tests/components/mdns/test.esp32-idf.yaml b/tests/components/mdns/test.esp32-idf.yaml new file mode 100644 index 0000000000..c5e8cf7aea --- /dev/null +++ b/tests/components/mdns/test.esp32-idf.yaml @@ -0,0 +1,2 @@ +packages: + mdns: !include common.yaml diff --git a/tests/components/mdns/test.esp8266-ard.yaml b/tests/components/mdns/test.esp8266-ard.yaml new file mode 100644 index 0000000000..c5e8cf7aea --- /dev/null +++ b/tests/components/mdns/test.esp8266-ard.yaml @@ -0,0 +1,2 @@ +packages: + mdns: !include common.yaml diff --git a/tests/components/mdns/test.rp2040-ard.yaml b/tests/components/mdns/test.rp2040-ard.yaml new file mode 100644 index 0000000000..c5e8cf7aea --- /dev/null +++ b/tests/components/mdns/test.rp2040-ard.yaml @@ -0,0 +1,2 @@ +packages: + mdns: !include common.yaml diff --git a/tests/components/mdns/validate-disabled.esp32-idf.yaml b/tests/components/mdns/validate-disabled.esp32-idf.yaml new file mode 100644 index 0000000000..7d76bf4de9 --- /dev/null +++ b/tests/components/mdns/validate-disabled.esp32-idf.yaml @@ -0,0 +1,7 @@ +# Disabling mdns compiles no mdns code, so config validation is enough. +wifi: + ssid: MySSID + password: password1 + +mdns: + disabled: true diff --git a/tests/components/mipi_dsi/test-tab5.esp32-p4-idf.yaml b/tests/components/mipi_dsi/test-tab5.esp32-p4-idf.yaml new file mode 100644 index 0000000000..2f4b02eea8 --- /dev/null +++ b/tests/components/mipi_dsi/test-tab5.esp32-p4-idf.yaml @@ -0,0 +1,23 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp32-p4-idf.yaml + +esp_ldo: + - id: ldo_id + channel: 3 + voltage: 2.5V + +psram: + +pi4ioe5v6408: + # A second, decoy hub at a different address, so this test only passes if + # the model's reset_pin default actually resolves pi4ioe1 by its address + # (0x43) -- with only one hub declared, the single-instance auto-pick + # fallback would resolve it too, even if address matching regressed. + - id: pi4ioe1 + address: 0x43 + - id: pi4ioe2 + address: 0x44 + +display: + - platform: mipi_dsi + model: M5STACK-TAB5 diff --git a/tests/components/modbus_client/common.yaml b/tests/components/modbus_client/common.yaml index ce2965e449..25ff8f5d36 100644 --- a/tests/components/modbus_client/common.yaml +++ b/tests/components/modbus_client/common.yaml @@ -153,3 +153,58 @@ button: then: # `values` here is the READ-BACK block, not the written block above - lambda: 'ESP_LOGI("modbus_client.test", "rw read0=%u n=%u", values[0], (unsigned) values.size());' + # Identical on purpose: each pair shares one constant value list + - platform: template + name: "Modbus Client Constant Writes" + on_press: + - modbus_client.write_multiple_registers: + address: 0x01 + start_address: 0x0210 + values: [0x1234, 0xABCD, 1] + - modbus_client.write_multiple_registers: + address: 0x01 + start_address: 0x0220 + values: [0x1234, 0xABCD, 1] + - modbus_client.write_multiple_coils: + address: 0x01 + start_address: 0x0020 + values: [true, false, true, true, false, false, true, false, true] + - modbus_client.write_multiple_coils: + address: 0x01 + start_address: 0x0030 + values: [true, false, true, true, false, false, true, false, true] + - modbus_client.read_write_multiple_registers: + address: 0x01 + write_address: 0x0310 + values: [0x0102, 0xFFFF] + read_address: 0x0410 + read_count: 1 + - modbus_client.read_write_multiple_registers: + address: 0x01 + write_address: 0x0320 + values: [0x0102, 0xFFFF] + read_address: 0x0420 + read_count: 1 + +number: + - platform: template + name: "Modbus Client Write Value" + optimistic: true + min_value: 0 + max_value: 100 + step: 1 + on_value: + - modbus_client.write_multiple_registers: + address: 0x01 + start_address: 0x0230 + values: !lambda "return {static_cast(x)};" + - modbus_client.write_multiple_coils: + address: 0x01 + start_address: 0x0040 + values: !lambda "return {x > 50};" + - modbus_client.read_write_multiple_registers: + address: 0x01 + write_address: 0x0330 + values: !lambda "return {static_cast(x)};" + read_address: 0x0430 + read_count: 1 diff --git a/tests/components/ota/common.yaml b/tests/components/ota/common.yaml index 1433dada1f..af8cf66708 100644 --- a/tests/components/ota/common.yaml +++ b/tests/components/ota/common.yaml @@ -1,11 +1,6 @@ -wifi: - ssid: MySSID - password: password1 - ota: - platform: esphome password: "superlongpasswordthatnoonewillknow" - port: 3286 on_begin: then: - logger.log: "OTA start" diff --git a/tests/components/ota/test-api_key_offer.esp8266-ard.yaml b/tests/components/ota/test-api_key_offer.esp8266-ard.yaml deleted file mode 100644 index ecda625521..0000000000 --- a/tests/components/ota/test-api_key_offer.esp8266-ard.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - ota: !include api_key_offer.yaml diff --git a/tests/components/ota/test-api_runtime_key.esp8266-ard.yaml b/tests/components/ota/test-api_runtime_key.esp8266-ard.yaml deleted file mode 100644 index 4709a9e45c..0000000000 --- a/tests/components/ota/test-api_runtime_key.esp8266-ard.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - ota: !include api_runtime_key.yaml diff --git a/tests/components/ota/test-partition_access.esp32-idf.yaml b/tests/components/ota/test-partition_access.esp32-idf.yaml deleted file mode 100644 index 0cbf854952..0000000000 --- a/tests/components/ota/test-partition_access.esp32-idf.yaml +++ /dev/null @@ -1,5 +0,0 @@ -ota: - - platform: esphome - allow_partition_access: true - -<<: !include common.yaml diff --git a/tests/components/ota/test.bk72xx-ard.yaml b/tests/components/ota/test.bk72xx-ard.yaml index dade44d145..3b32457cf4 100644 --- a/tests/components/ota/test.bk72xx-ard.yaml +++ b/tests/components/ota/test.bk72xx-ard.yaml @@ -1 +1,3 @@ -<<: !include common.yaml +packages: + wifi: !include wifi.yaml + ota: !include common.yaml diff --git a/tests/components/ota/test.esp32-ard.yaml b/tests/components/ota/test.esp32-ard.yaml index dade44d145..3b32457cf4 100644 --- a/tests/components/ota/test.esp32-ard.yaml +++ b/tests/components/ota/test.esp32-ard.yaml @@ -1 +1,3 @@ -<<: !include common.yaml +packages: + wifi: !include wifi.yaml + ota: !include common.yaml diff --git a/tests/components/ota/test.esp32-idf.yaml b/tests/components/ota/test.esp32-idf.yaml index dade44d145..7f25fafadb 100644 --- a/tests/components/ota/test.esp32-idf.yaml +++ b/tests/components/ota/test.esp32-idf.yaml @@ -1 +1,8 @@ -<<: !include common.yaml +packages: + wifi: !include wifi.yaml + ota: !include common.yaml + +# Final validation merges this into the common block +ota: + - platform: esphome + allow_partition_access: true diff --git a/tests/components/ota/test.esp8266-ard.yaml b/tests/components/ota/test.esp8266-ard.yaml index dade44d145..3b32457cf4 100644 --- a/tests/components/ota/test.esp8266-ard.yaml +++ b/tests/components/ota/test.esp8266-ard.yaml @@ -1 +1,3 @@ -<<: !include common.yaml +packages: + wifi: !include wifi.yaml + ota: !include common.yaml diff --git a/tests/components/ota/test.host.yaml b/tests/components/ota/test.host.yaml index ae7c4d0add..fdfbf47139 100644 --- a/tests/components/ota/test.host.yaml +++ b/tests/components/ota/test.host.yaml @@ -1,4 +1,2 @@ -<<: !include common.yaml - -#host platform does not support wifi / network is automatically included -wifi: !remove +packages: + ota: !include common.yaml diff --git a/tests/components/ota/test.rp2040-ard.yaml b/tests/components/ota/test.rp2040-ard.yaml index dade44d145..3b32457cf4 100644 --- a/tests/components/ota/test.rp2040-ard.yaml +++ b/tests/components/ota/test.rp2040-ard.yaml @@ -1 +1,3 @@ -<<: !include common.yaml +packages: + wifi: !include wifi.yaml + ota: !include common.yaml diff --git a/tests/components/ota/test-empty_password.esp8266-ard.yaml b/tests/components/ota/validate-empty_password.esp8266-ard.yaml similarity index 100% rename from tests/components/ota/test-empty_password.esp8266-ard.yaml rename to tests/components/ota/validate-empty_password.esp8266-ard.yaml diff --git a/tests/components/ota/test-encryption_inherit.esp8266-ard.yaml b/tests/components/ota/validate-encryption_inherit.esp8266-ard.yaml similarity index 100% rename from tests/components/ota/test-encryption_inherit.esp8266-ard.yaml rename to tests/components/ota/validate-encryption_inherit.esp8266-ard.yaml diff --git a/tests/components/mdns/test-disabled.esp32-idf.yaml b/tests/components/ota/wifi.yaml similarity index 64% rename from tests/components/mdns/test-disabled.esp32-idf.yaml rename to tests/components/ota/wifi.yaml index 07c70bf248..ac662302fc 100644 --- a/tests/components/mdns/test-disabled.esp32-idf.yaml +++ b/tests/components/ota/wifi.yaml @@ -1,6 +1,3 @@ wifi: ssid: MySSID password: password1 - -mdns: - disabled: true diff --git a/tests/components/remote_receiver/common-actions.yaml b/tests/components/remote_receiver/common-actions.yaml index 5d12dc68d4..4dadec6a01 100644 --- a/tests/components/remote_receiver/common-actions.yaml +++ b/tests/components/remote_receiver/common-actions.yaml @@ -103,6 +103,11 @@ on_nexa: - logger.log: format: "on_nexa: %lu %u %u %u %u" args: ["long(x.device)", "x.group", "x.state", "x.channel", "x.level"] +on_onkyori: + then: + - logger.log: + format: "on_onkyori: 0x%03X" + args: ["x.data"] on_panasonic: then: - logger.log: diff --git a/tests/components/remote_transmitter/common-buttons.yaml b/tests/components/remote_transmitter/common-buttons.yaml index a41ba346aa..776a03e680 100644 --- a/tests/components/remote_transmitter/common-buttons.yaml +++ b/tests/components/remote_transmitter/common-buttons.yaml @@ -5,6 +5,13 @@ number: min_value: 0 max_value: 255 step: 1 + on_value: + - remote_transmitter.transmit_abbwelcome: + source_address: 0x1234 + destination_address: 0x5678 + message_type: 0x01 + data: !lambda |- + return {(uint8_t) x, 0x20}; button: - platform: template @@ -245,6 +252,16 @@ button: channel: 1 button: 1 check: 1 + - platform: template + name: OnkyoRI + on_press: + remote_transmitter.transmit_onkyori: + data: 0xD9 + - platform: template + name: OnkyoRI 12-bit + on_press: + remote_transmitter.transmit_onkyori: + data: 0x170 - platform: template name: Toto on_press: @@ -260,6 +277,22 @@ button: destination_address: 0x5678 message_type: 0x01 data: [0x10, 0x20, 0x30] + - platform: template + name: ABBWelcome static same data + on_press: + remote_transmitter.transmit_abbwelcome: + source_address: 0x1234 + destination_address: 0x5678 + message_type: 0x02 + data: [0x10, 0x20, 0x30] + - platform: template + name: ABBWelcome static empty data + on_press: + remote_transmitter.transmit_abbwelcome: + source_address: 0x1234 + destination_address: 0x5678 + message_type: 0x03 + data: [] - platform: template name: ABBWelcome lambda on_press: diff --git a/tests/components/sendspin/common-hub.yaml b/tests/components/sendspin/common-hub.yaml index bd6747ee07..d89d16856d 100644 --- a/tests/components/sendspin/common-hub.yaml +++ b/tests/components/sendspin/common-hub.yaml @@ -7,3 +7,38 @@ sendspin: manufacturer: Test Manufacturer model: Test Model firmware_version: 1.2.3 + # No static_pairing_code here: the on_display_pairing_code automation below makes + # this a dynamic-pairing-code device, and validation rejects both together. + # common-pairing.yaml covers the static-code build. + on_open_pairing_window: + then: + - logger.log: "Pairing window requested" + - sendspin.confirm_pairing_window: + # Calling cancel_pairing_window here only compiles the action; a real config cancels from a user input. + on_close_pairing_window: + then: + - logger.log: "Pairing window prompt dismissed" + - sendspin.cancel_pairing_window: + on_display_pairing_code: + then: + - logger.log: + format: "Pairing code: %s" + args: ["code.c_str()"] + on_clear_pairing_code: + then: + - logger.log: "Pairing code cleared" + on_pairing_succeeded: + then: + - logger.log: + format: "Paired with server %s" + args: ["server_id.c_str()"] + on_pairing_failed: + then: + - logger.log: + format: "Pairing with server %s failed: %s" + args: ["server_id.c_str()", "reason.c_str()"] + - if: + condition: + lambda: 'return reason == "user_cancelled";' + then: + - logger.log: "Pairing was cancelled" diff --git a/tests/components/sendspin/common-pairing.yaml b/tests/components/sendspin/common-pairing.yaml new file mode 100644 index 0000000000..500f888ac7 --- /dev/null +++ b/tests/components/sendspin/common-pairing.yaml @@ -0,0 +1,19 @@ +# The hub on its own, with no other sendspin platforms, to build the pairing options. +# +# Deliberately does not include common.yaml: that file pulls in the shared hub package, and +# the point here is a separate hub configuration. sendspin depends on network, so the interface +# has to be declared locally instead. +wifi: + ap: + +# Also the static-pairing-code build. +sendspin: + id: sendspin_hub_id + static_pairing_code: "01234567" + # Covers the hub's unpaired access option, which is only valid without an unpaired access switch. + unpaired_access: false + +# Builds the API request for the Sendspin pairing token. +api: + encryption: + key: bOFFzzvfpg5DB94DuBGLXD/hMnhpDKgP9UQyBulwWVU= diff --git a/tests/components/sendspin/common-switch.yaml b/tests/components/sendspin/common-switch.yaml index 01e2f0b02b..dbf260286b 100644 --- a/tests/components/sendspin/common-switch.yaml +++ b/tests/components/sendspin/common-switch.yaml @@ -5,3 +5,6 @@ switch: - platform: sendspin name: "Sendspin Enabled" type: enabled + - platform: sendspin + name: "Sendspin Unpaired Access" + type: unpaired_access diff --git a/tests/components/sendspin/common-text_sensor-pairing.yaml b/tests/components/sendspin/common-text_sensor-pairing.yaml new file mode 100644 index 0000000000..c2d92eb453 --- /dev/null +++ b/tests/components/sendspin/common-text_sensor-pairing.yaml @@ -0,0 +1,17 @@ +# The pairing code text sensor on its own, with no metadata text sensor to pull in the +# metadata role. This is the build where USE_SENDSPIN_METADATA is undefined while the text +# sensor platform is still compiled, which is guarded separately in sendspin_text_sensor.{h,cpp}. +# +# Deliberately does not include common.yaml: that file pulls in the shared hub package, and +# the point here is the narrower configuration. sendspin depends on network, so the interface +# has to be declared locally instead. +wifi: + ap: + +sendspin: + id: sendspin_hub_id + +text_sensor: + - platform: sendspin + name: "Pairing Code" + type: pairing_code diff --git a/tests/components/sendspin/common-text_sensor.yaml b/tests/components/sendspin/common-text_sensor.yaml index 23111e8d37..087bbdb6b0 100644 --- a/tests/components/sendspin/common-text_sensor.yaml +++ b/tests/components/sendspin/common-text_sensor.yaml @@ -14,3 +14,7 @@ text_sensor: - platform: sendspin name: "Album Artist" type: album_artist + - platform: sendspin + name: "Pairing Code" + type: pairing_code + entity_category: diagnostic diff --git a/tests/components/sendspin/test-pairing.esp32-idf.yaml b/tests/components/sendspin/test-pairing.esp32-idf.yaml new file mode 100644 index 0000000000..5862e841df --- /dev/null +++ b/tests/components/sendspin/test-pairing.esp32-idf.yaml @@ -0,0 +1,2 @@ +packages: + sendspin: !include common-pairing.yaml diff --git a/tests/components/sendspin/test-text_sensor-pairing.esp32-idf.yaml b/tests/components/sendspin/test-text_sensor-pairing.esp32-idf.yaml new file mode 100644 index 0000000000..2336f87740 --- /dev/null +++ b/tests/components/sendspin/test-text_sensor-pairing.esp32-idf.yaml @@ -0,0 +1,2 @@ +packages: + sendspin: !include common-text_sensor-pairing.yaml diff --git a/tests/components/serial_proxy/test-usb.esp32-s3-idf.yaml b/tests/components/serial_proxy/test-usb.esp32-s3-idf.yaml new file mode 100644 index 0000000000..4bcd3a771f --- /dev/null +++ b/tests/components/serial_proxy/test-usb.esp32-s3-idf.yaml @@ -0,0 +1,30 @@ +# A hardware UART port alongside the USB one, so the USB identity define is exercised on a +# port that has no USB channel +packages: + uart: !include ../../test_build_components/common/uart/esp32-idf.yaml + +wifi: + ssid: MySSID + password: password1 + +api: + +usb_host: + +usb_uart: + - type: CDC_ACM + vid: 0x303A + pid: 0x831A + channels: + - id: usb_serial_channel + baud_rate: 460800 + +serial_proxy: + - id: serial_proxy_usb + uart_id: usb_serial_channel + name: USB Serial Port + port_type: USB_SERIAL + - id: serial_proxy_hw + uart_id: uart_bus + name: Hardware Serial Port + port_type: TTL diff --git a/tests/components/spd2010/common.yaml b/tests/components/spd2010/common.yaml new file mode 100644 index 0000000000..0692137638 --- /dev/null +++ b/tests/components/spd2010/common.yaml @@ -0,0 +1,25 @@ +display: + - platform: ssd1306_i2c + id: spd2010_display + model: SSD1306_128X64 + +touchscreen: + - platform: spd2010 + id: spd2010_touchscreen + display: spd2010_display + interrupt_pin: ${interrupt_pin} + reset_pin: ${reset_pin} + - platform: spd2010 + id: spd2010_polling_touchscreen + display: spd2010_display + address: 0x54 + update_interval: 100ms + calibration: + x_min: 0 + x_max: 479 + y_min: 0 + y_max: 479 + transform: + swap_xy: true + mirror_x: true + mirror_y: false diff --git a/tests/components/spd2010/test.esp32-idf.yaml b/tests/components/spd2010/test.esp32-idf.yaml new file mode 100644 index 0000000000..ca2be87d29 --- /dev/null +++ b/tests/components/spd2010/test.esp32-idf.yaml @@ -0,0 +1,7 @@ +substitutions: + interrupt_pin: "20" + reset_pin: "21" + +packages: + i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml + spd2010: !include common.yaml diff --git a/tests/components/sx126x/common.yaml b/tests/components/sx126x/common.yaml index 05794ad1a8..eff5785bae 100644 --- a/tests/components/sx126x/common.yaml +++ b/tests/components/sx126x/common.yaml @@ -36,6 +36,10 @@ number: min_value: 0 max_value: 100 step: 1 + on_value: + then: + - sx126x.send_packet: !lambda |- + return {0x03, (uint8_t) x}; button: - platform: template @@ -50,3 +54,6 @@ button: data: [0xC5, 0x51, 0x78, 0x82, 0xB7, 0xF9, 0x9C, 0x5C] - sx126x.send_packet: !lambda |- return {0x01, 0x02, (uint8_t)id(sx126x_my_number).state}; + # Same bytes as above on purpose: equal payloads share one flash table + - sx126x.send_packet: [0xC5, 0x51, 0x78, 0x82, 0xB7, 0xF9, 0x9C, 0x5C] + - sx126x.send_packet: "hello" diff --git a/tests/components/sx127x/common.yaml b/tests/components/sx127x/common.yaml index b7eadc084f..aa74b52cda 100644 --- a/tests/components/sx127x/common.yaml +++ b/tests/components/sx127x/common.yaml @@ -25,6 +25,8 @@ sx127x: then: - sx127x.send_packet: data: [0xC5, 0x51, 0x78, 0x82, 0xB7, 0xF9, 0x9C, 0x5C] + - sx127x.send_packet: !lambda |- + return x; number: - platform: template @@ -34,6 +36,10 @@ number: min_value: 0 max_value: 100 step: 1 + on_value: + then: + - sx127x.send_packet: !lambda |- + return {0x03, (uint8_t) x}; button: - platform: template @@ -49,3 +55,5 @@ button: data: [0xC5, 0x51, 0x78, 0x82, 0xB7, 0xF9, 0x9C, 0x5C] - sx127x.send_packet: !lambda |- return {0x01, 0x02, (uint8_t)id(sx127x_my_number).state}; + - sx127x.send_packet: [0xC5, 0x51, 0x78, 0x82, 0xB7, 0xF9, 0x9C, 0x5C] + - sx127x.send_packet: "hello" diff --git a/tests/components/udp/common.yaml b/tests/components/udp/common.yaml index 6824c5cca8..f3a082fdd2 100644 --- a/tests/components/udp/common.yaml +++ b/tests/components/udp/common.yaml @@ -20,6 +20,13 @@ udp: id: my_udp data: !lambda |- return std::vector{1,3,4,5,6}; + - udp.write: + id: my_udp + data: [0xAA, 0x55, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06] + - udp.write: + id: my_udp + data: !lambda |- + return std::vector(data.begin(), data.end()); number: - platform: template @@ -37,5 +44,7 @@ button: then: - udp.write: data: [0x01, 0x02, 0x03] + - udp.write: + data: [0xAA, 0x55, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06] - udp.write: !lambda |- return {0x10, 0x20, (uint8_t)id(udp_my_number).state}; diff --git a/tests/components/usb_host/test.esp32-s3-idf.yaml b/tests/components/usb_host/test.esp32-s3-idf.yaml index 5360d1f6ff..a71140feca 100644 --- a/tests/components/usb_host/test.esp32-s3-idf.yaml +++ b/tests/components/usb_host/test.esp32-s3-idf.yaml @@ -4,3 +4,8 @@ usb_host: - id: device_1 vid: 0x1234 pid: 0x1234 + - id: device_2 + vid: 0x1234 + pid: 0xABCD + manufacturer: Example Corp + product: Example Widget diff --git a/tests/components/usb_uart/common.yaml b/tests/components/usb_uart/common.yaml index 2e41fad1a1..02500035cf 100644 --- a/tests/components/usb_uart/common.yaml +++ b/tests/components/usb_uart/common.yaml @@ -36,8 +36,10 @@ usb_uart: debug: true dummy_receiver: true debug_prefix: "[ESP_JTAG] " + # A CP2105, so it does not share 10C4:EA60 with uart_1 above - id: uart_5 type: cp210x + pid: 0xEA70 channels: - id: channel_5_1 baud_rate: 9600 @@ -66,3 +68,20 @@ usb_uart: stop_bits: 2 data_bits: 7 parity: even + # A ZBT-2 and a ZWA-2 both enumerate as 303A:4001, so only iProduct separates them + - id: uart_9 + type: cdc_acm + vid: 0x303A + pid: 0x4001 + manufacturer: Nabu Casa + product: ZBT-2 + channels: + - id: channel_9_1 + - id: uart_10 + type: cdc_acm + vid: 0x303A + pid: 0x4001 + manufacturer: Nabu Casa + product: ZWA-2 + channels: + - id: channel_10_1 diff --git a/tests/components/xiaomi_mccgq02hl/binary_sensor/xiaomi_mccgq02hl_test.cpp b/tests/components/xiaomi_mccgq02hl/binary_sensor/xiaomi_mccgq02hl_test.cpp new file mode 100644 index 0000000000..c9a4a67a34 --- /dev/null +++ b/tests/components/xiaomi_mccgq02hl/binary_sensor/xiaomi_mccgq02hl_test.cpp @@ -0,0 +1,74 @@ +#include "../common.h" + +#include + +namespace esphome::xiaomi_mccgq02hl::testing { + +TEST(XiaomiMCCGQ02HL, DecodesOpenAndClosed) { + Harness h; + ASSERT_TRUE(h.sensor.parse_device(advert(ADDRESS, OPEN))); + EXPECT_TRUE(h.sensor.state); + ASSERT_TRUE(h.sensor.parse_device(advert(ADDRESS, CLOSED))); + EXPECT_FALSE(h.sensor.state); +} + +TEST(XiaomiMCCGQ02HL, LeftOpenIsReportedAsOpen) { + Harness h; + ASSERT_TRUE(h.sensor.parse_device(advert(ADDRESS, LEFT_OPEN))); + EXPECT_TRUE(h.sensor.state); +} + +TEST(XiaomiMCCGQ02HL, DeviceResetIsIgnored) { + Harness h; + ASSERT_TRUE(h.sensor.parse_device(advert(ADDRESS, OPEN))); + EXPECT_FALSE(h.sensor.parse_device(advert(ADDRESS, DEVICE_RESET))); + EXPECT_TRUE(h.sensor.state); +} + +TEST(XiaomiMCCGQ02HL, DecodesLight) { + Harness h; + ASSERT_TRUE(h.sensor.parse_device(advert(ADDRESS, BRIGHT))); + EXPECT_TRUE(h.light.state); + ASSERT_TRUE(h.sensor.parse_device(advert(ADDRESS, DARK))); + EXPECT_FALSE(h.light.state); + EXPECT_EQ(h.door_changes, 0); // light frames carry no door state +} + +TEST(XiaomiMCCGQ02HL, DecodesBatteryLevel) { + Harness h; + ASSERT_TRUE(h.sensor.parse_device(advert(ADDRESS, BATTERY))); + EXPECT_FLOAT_EQ(h.battery_level.state, 87.0f); +} + +TEST(XiaomiMCCGQ02HL, RepeatedFrameIsDropped) { + Harness h; + ASSERT_TRUE(h.sensor.parse_device(advert(ADDRESS, OPEN))); + EXPECT_FALSE(h.sensor.parse_device(advert(ADDRESS, OPEN))); +} + +TEST(XiaomiMCCGQ02HL, RejectsBadMic) { + Harness h; + EXPECT_FALSE(h.sensor.parse_device(advert(ADDRESS, BAD_MIC))); + EXPECT_EQ(h.door_changes, 0); +} + +TEST(XiaomiMCCGQ02HL, BadMicDoesNotBlockTheNextFrame) { + Harness h; + EXPECT_FALSE(h.sensor.parse_device(advert(ADDRESS, BAD_MIC))); + ASSERT_TRUE(h.sensor.parse_device(advert(ADDRESS, OPEN))); + EXPECT_TRUE(h.sensor.state); +} + +TEST(XiaomiMCCGQ02HL, RejectsPlaintextFrame) { + Harness h; + EXPECT_FALSE(h.sensor.parse_device(advert(ADDRESS, PLAINTEXT_OPEN))); + EXPECT_EQ(h.door_changes, 0); +} + +TEST(XiaomiMCCGQ02HL, IgnoresOtherAddress) { + Harness h; + EXPECT_FALSE(h.sensor.parse_device(advert(OTHER_ADDRESS, OPEN))); + EXPECT_EQ(h.door_changes, 0); +} + +} // namespace esphome::xiaomi_mccgq02hl::testing diff --git a/tests/components/xiaomi_mccgq02hl/common-ln.yaml b/tests/components/xiaomi_mccgq02hl/common-ln.yaml new file mode 100644 index 0000000000..9d7a7278a9 --- /dev/null +++ b/tests/components/xiaomi_mccgq02hl/common-ln.yaml @@ -0,0 +1,9 @@ +binary_sensor: + - platform: xiaomi_mccgq02hl + name: Mi Door Sensor 2 + mac_address: E4:AA:EC:01:02:03 + bindkey: "48403ebe2d385db8d0c187f81e62cb64" + light: + name: Mi Door Sensor 2 Light + battery_level: + name: Mi Door Sensor 2 Battery Level diff --git a/tests/components/xiaomi_mccgq02hl/common.h b/tests/components/xiaomi_mccgq02hl/common.h new file mode 100644 index 0000000000..3f6dcd39bb --- /dev/null +++ b/tests/components/xiaomi_mccgq02hl/common.h @@ -0,0 +1,76 @@ +#pragma once + +#include "esphome/components/xiaomi_mccgq02hl/xiaomi_mccgq02hl.h" + +#include +#include + +namespace esphome::xiaomi_mccgq02hl::testing { + +using Frame = std::array; +using PlainFrame = std::array; + +static constexpr uint64_t ADDRESS = 0xE4AAEC010203ULL; +static constexpr uint64_t OTHER_ADDRESS = 0xE4AAEC010204ULL; +static constexpr const char *BINDKEY = "48403ebe2d385db8d0c187f81e62cb64"; + +// MiBeacon v5 frames (MAC included, AES-CCM encrypted) built with Python's cryptography AESCCM using ADDRESS and +// BINDKEY. Each frame has a different frame count. +// Door open (object 0x0019) +static constexpr Frame OPEN = {0x58, 0x59, 0x8b, 0x09, 0x01, 0x03, 0x02, 0x01, 0xec, 0xaa, 0xe4, + 0xe4, 0xd9, 0x75, 0x9f, 0x01, 0x00, 0x00, 0x7e, 0x61, 0x36, 0x49}; +// Door closed +static constexpr Frame CLOSED = {0x58, 0x59, 0x8b, 0x09, 0x02, 0x03, 0x02, 0x01, 0xec, 0xaa, 0xe4, + 0x52, 0x9c, 0x58, 0x6e, 0x01, 0x00, 0x00, 0x69, 0x45, 0x8f, 0xec}; +// Door left open past the device timeout (object 0x1019) +static constexpr Frame LEFT_OPEN = {0x58, 0x59, 0x8b, 0x09, 0x03, 0x03, 0x02, 0x01, 0xec, 0xaa, 0xe4, + 0x38, 0xe3, 0xbd, 0x82, 0x01, 0x00, 0x00, 0x74, 0x97, 0x75, 0x9d}; +// Device reset, must be ignored +static constexpr Frame DEVICE_RESET = {0x58, 0x59, 0x8b, 0x09, 0x04, 0x03, 0x02, 0x01, 0xec, 0xaa, 0xe4, + 0x4b, 0x18, 0xdb, 0x8e, 0x01, 0x00, 0x00, 0x48, 0xa6, 0x52, 0x99}; +// Light above threshold (object 0x1018) +static constexpr Frame BRIGHT = {0x58, 0x59, 0x8b, 0x09, 0x05, 0x03, 0x02, 0x01, 0xec, 0xaa, 0xe4, + 0xe3, 0x96, 0x1c, 0xa4, 0x01, 0x00, 0x00, 0x4b, 0x43, 0xca, 0x14}; +// Light below threshold (object 0x0018) +static constexpr Frame DARK = {0x58, 0x59, 0x8b, 0x09, 0x06, 0x03, 0x02, 0x01, 0xec, 0xaa, 0xe4, + 0x78, 0x07, 0x43, 0xb5, 0x01, 0x00, 0x00, 0x69, 0x62, 0x88, 0x89}; +// Battery 87 % (object 0x100A) +static constexpr Frame BATTERY = {0x58, 0x59, 0x8b, 0x09, 0x07, 0x03, 0x02, 0x01, 0xec, 0xaa, 0xe4, + 0xdc, 0x88, 0xf2, 0xd0, 0x01, 0x00, 0x00, 0x39, 0xe8, 0x96, 0x08}; +// OPEN with a corrupted MIC (same frame count as OPEN) +static constexpr Frame BAD_MIC = {0x58, 0x59, 0x8b, 0x09, 0x01, 0x03, 0x02, 0x01, 0xec, 0xaa, 0xe4, + 0xe4, 0xd9, 0x75, 0x9f, 0x01, 0x00, 0x00, 0x7e, 0x61, 0x36, 0xb6}; + +// OPEN sent without encryption (MAC included), which must be rejected +static constexpr PlainFrame PLAINTEXT_OPEN = {0x50, 0x59, 0x8b, 0x09, 0x09, 0x03, 0x02, 0x01, + 0xec, 0xaa, 0xe4, 0x19, 0x00, 0x01, 0x00}; + +template ble_device_base::ESPBTDevice advert(uint64_t address, const std::array &frame) { + // Service data AD structure for UUID 0xFE95 + std::vector adv = {static_cast(frame.size() + 3), 0x16, 0x95, 0xFE}; + adv.insert(adv.end(), frame.begin(), frame.end()); + uint8_t mac[6]; + for (size_t i = 0; i < 6; i++) + mac[i] = static_cast(address >> (i * 8)); + ble_device_base::ESPBTDevice device; + device.from_scan_result(mac, -60, 0, adv.data(), static_cast(adv.size())); + return device; +} + +struct Harness { + Harness() { + this->sensor.set_address(ADDRESS); + this->sensor.set_bindkey(BINDKEY); + this->sensor.set_light(&this->light); + this->sensor.set_battery_level(&this->battery_level); + // The door sensor starts as "closed", so count changes to see whether anything was published + this->sensor.add_full_state_callback([this](optional, optional) { this->door_changes++; }); + } + + XiaomiMCCGQ02HL sensor; + binary_sensor::BinarySensor light; + sensor::Sensor battery_level; + int door_changes{0}; +}; + +} // namespace esphome::xiaomi_mccgq02hl::testing diff --git a/tests/components/xiaomi_mccgq02hl/common.yaml b/tests/components/xiaomi_mccgq02hl/common.yaml new file mode 100644 index 0000000000..9cf9be8e2d --- /dev/null +++ b/tests/components/xiaomi_mccgq02hl/common.yaml @@ -0,0 +1,14 @@ +esp32_ble_tracker: + id: ble_tracker_hub + +binary_sensor: + # Explicit ble_hub_id: pins the neutral binding as a declared key. + - platform: xiaomi_mccgq02hl + ble_hub_id: ble_tracker_hub + name: Mi Door Sensor 2 + mac_address: E4:AA:EC:01:02:03 + bindkey: "48403ebe2d385db8d0c187f81e62cb64" + light: + name: Mi Door Sensor 2 Light + battery_level: + name: Mi Door Sensor 2 Battery Level diff --git a/tests/components/xiaomi_mccgq02hl/test.esp32-idf.yaml b/tests/components/xiaomi_mccgq02hl/test.esp32-idf.yaml new file mode 100644 index 0000000000..8e5a674bc2 --- /dev/null +++ b/tests/components/xiaomi_mccgq02hl/test.esp32-idf.yaml @@ -0,0 +1,3 @@ +packages: + ble: !include ../../test_build_components/common/ble/esp32-idf.yaml + xiaomi_mccgq02hl: !include common.yaml diff --git a/tests/components/xiaomi_mccgq02hl/test.ln882x-ard.yaml b/tests/components/xiaomi_mccgq02hl/test.ln882x-ard.yaml new file mode 100644 index 0000000000..8bbe6097db --- /dev/null +++ b/tests/components/xiaomi_mccgq02hl/test.ln882x-ard.yaml @@ -0,0 +1,3 @@ +packages: + ln882h_ble_tracker: !include ../ln882h_ble_tracker/common.yaml + xiaomi_mccgq02hl: !include common-ln.yaml diff --git a/tests/components/xiaomi_mccgq02hl/validate.bk72xx-ard.yaml b/tests/components/xiaomi_mccgq02hl/validate.bk72xx-ard.yaml new file mode 100644 index 0000000000..921263442c --- /dev/null +++ b/tests/components/xiaomi_mccgq02hl/validate.bk72xx-ard.yaml @@ -0,0 +1,23 @@ +# Config-only: the CI base board (generic-bk7252, BLE 4.2) cannot compile the +# BLE 5.x tracker, so this fixture proves validation (schema + neutral binding) +# on a non-esp32 platform; codegen and compilation are not exercised here. +bk72xx_ble_tracker: + id: ble_tracker_hub + +binary_sensor: + # Explicit ble_hub_id: pins the neutral binding as a declared key. + - platform: xiaomi_mccgq02hl + ble_hub_id: ble_tracker_hub + name: Mi Door Sensor 2 + mac_address: E4:AA:EC:01:02:03 + bindkey: "48403ebe2d385db8d0c187f81e62cb64" + light: + name: Mi Door Sensor 2 Light + battery_level: + name: Mi Door Sensor 2 Battery Level + + # No ble_hub_id: exercises the generated binding real configs use. + - platform: xiaomi_mccgq02hl + name: Mi Door Sensor 2 Implicit + mac_address: E4:AA:EC:01:02:04 + bindkey: "48403ebe2d385db8d0c187f81e62cb64" diff --git a/tests/components/xl9535/common.yaml b/tests/components/xl9535/common.yaml index 81e96131ab..21ce8b5512 100644 --- a/tests/components/xl9535/common.yaml +++ b/tests/components/xl9535/common.yaml @@ -2,6 +2,9 @@ xl9535: - id: xl9535_hub i2c_id: i2c_bus address: 0x20 + - id: xl9535_hub_2 + i2c_id: i2c_bus + address: 0x21 binary_sensor: - platform: gpio @@ -20,3 +23,12 @@ binary_sensor: mode: input: true inverted: false + - platform: gpio + name: XL9535 Pin 1 By Address + pin: + xl9535: + address: 0x21 + number: 1 + mode: + input: true + inverted: false diff --git a/tests/components/zigbee/common.yaml b/tests/components/zigbee/common.yaml index ce59f4a6ae..8ef4957072 100644 --- a/tests/components/zigbee/common.yaml +++ b/tests/components/zigbee/common.yaml @@ -45,6 +45,7 @@ number: max_value: 100 step: 1 unit_of_measurement: "ยฐC" + device_class: temperature time: - platform: zigbee diff --git a/tests/integration/fixtures/api_string_lambda.yaml b/tests/integration/fixtures/api_string_lambda.yaml index e2da4683c0..8f2a11cf31 100644 --- a/tests/integration/fixtures/api_string_lambda.yaml +++ b/tests/integration/fixtures/api_string_lambda.yaml @@ -37,7 +37,7 @@ api: args: [input_number] # Test that int lambdas still work correctly with to_string - # The TemplatableStringValue should automatically convert int to string + # Home Assistant action lambdas convert an int result to a string - homeassistant.event: event: esphome.test_int_lambda data: @@ -54,7 +54,7 @@ api: args: [input_float] # Test that float lambdas still work correctly with to_string - # The TemplatableStringValue should automatically convert float to string + # Home Assistant action lambdas convert a float result to a string - homeassistant.event: event: esphome.test_float_lambda data: diff --git a/tests/integration/fixtures/branch_resume_async.yaml b/tests/integration/fixtures/branch_resume_async.yaml new file mode 100644 index 0000000000..fb09323809 --- /dev/null +++ b/tests/integration/fixtures/branch_resume_async.yaml @@ -0,0 +1,261 @@ +esphome: + name: test-branch-resume-async + +host: + +logger: + level: DEBUG + +script: + - id: delay_in_if + then: + - if: + condition: + lambda: 'return true;' + then: + - delay: 50ms + - logger.log: "if-branch-after-delay" + else: + - logger.log: "if-else-should-not-run" + - logger.log: "after-if" + + - id: delay_in_else + then: + - if: + condition: + lambda: 'return false;' + then: + - logger.log: "if-then-should-not-run" + else: + - delay: 50ms + - logger.log: "else-branch-after-delay" + - logger.log: "after-else" + + - id: delay_in_while + then: + - lambda: 'id(counter) = 0;' + - while: + condition: + lambda: 'return id(counter) < 3;' + then: + - delay: 20ms + - lambda: 'id(counter) += 1;' + - logger.log: + format: "while-body-%d" + args: ['id(counter)'] + - logger.log: "after-while" + + - id: stop_in_branch + then: + - if: + condition: + lambda: 'return true;' + then: + - delay: 500ms + - logger.log: "stopped-branch-should-not-run" + - logger.log: "stopped-after-if-should-not-run" + + - id: parallel_branch + mode: parallel + parameters: + run: int + then: + - if: + condition: + lambda: 'return run >= 0;' + then: + - delay: 100ms + - logger.log: + format: "parallel-branch-done-%d" + args: ['run'] + - logger.log: + format: "parallel-after-if-%d" + args: ['run'] + + - id: nested_if_last_in_while + then: + - lambda: 'id(counter) = 0;' + - while: + condition: + lambda: 'return id(counter) < 2;' + then: + - lambda: 'id(counter) += 1;' + - if: + condition: + lambda: 'return true;' + then: + - delay: 20ms + - logger.log: + format: "nested-if-in-while-%d" + args: ['id(counter)'] + - logger.log: "after-nested-while" + + - id: stop_mid_while + then: + - lambda: 'id(counter) = 0;' + - while: + condition: + lambda: 'return id(counter) < 5;' + then: + - delay: 200ms + - lambda: 'id(counter) += 1;' + - logger.log: + format: "stop-while-body-%d" + args: ['id(counter)'] + - logger.log: "stop-while-done" + + - id: queued_branch + mode: queued + parameters: + run: int + then: + - if: + condition: + lambda: 'return true;' + then: + - delay: 30ms + - logger.log: + format: "queued-branch-%d" + args: ['run'] + - logger.log: + format: "queued-after-if-%d" + args: ['run'] + + - id: restart_branch + mode: restart + parameters: + run: int + then: + - if: + condition: + lambda: 'return true;' + then: + - delay: 100ms + - logger.log: + format: "restart-branch-%d" + args: ['run'] + - logger.log: + format: "restart-after-if-%d" + args: ['run'] + + - id: delay_last_in_branch + then: + - if: + condition: + lambda: 'return true;' + then: + - logger.log: "delay-last-start" + - delay: 30ms + - logger.log: "after-delay-last" + + - id: stop_in_if_condition + then: + - if: + condition: + lambda: |- + id(stop_in_if_condition).stop(); + return true; + then: + - logger.log: "if-stopped-by-condition-should-not-run" + else: + - logger.log: "if-else-stopped-by-condition-should-not-run" + - logger.log: "after-if-stopped-by-condition-should-not-run" + - id: stop_in_if_else_condition + then: + - if: + condition: + lambda: |- + id(stop_in_if_else_condition).stop(); + return false; + else: + - logger.log: "else-stopped-by-condition-should-not-run" + - logger.log: "after-else-stopped-by-condition-should-not-run" + - id: stop_in_while_condition + then: + - while: + condition: + lambda: |- + id(stop_in_while_condition).stop(); + return true; + then: + - logger.log: "while-stopped-by-condition-should-not-run" + - logger.log: "after-while-stopped-by-condition-should-not-run" + +globals: + - id: counter + type: int + initial_value: '0' + +api: + actions: + - action: run_delay_in_if + then: + - script.execute: delay_in_if + - action: run_delay_in_else + then: + - script.execute: delay_in_else + - action: run_delay_in_while + then: + - script.execute: delay_in_while + - action: run_stop_in_branch + then: + - script.execute: stop_in_branch + - delay: 100ms + - script.stop: stop_in_branch + - lambda: |- + ESP_LOGI("test", "stop-running=%d", id(stop_in_branch).is_running() ? 1 : 0); + - delay: 600ms + - logger.log: "stop-check-done" + - action: run_nested_if_last_in_while + then: + - script.execute: nested_if_last_in_while + - action: run_stop_mid_while + then: + - script.execute: stop_mid_while + - delay: 300ms + - script.stop: stop_mid_while + - lambda: |- + ESP_LOGI("test", "stop-while-running=%d", id(stop_mid_while).is_running() ? 1 : 0); + - delay: 400ms + - script.execute: stop_mid_while + - action: run_queued_branch + then: + - script.execute: + id: queued_branch + run: 1 + - script.execute: + id: queued_branch + run: 2 + - script.execute: + id: queued_branch + run: 3 + - action: run_restart_branch + then: + - script.execute: + id: restart_branch + run: 1 + - delay: 30ms + - script.execute: + id: restart_branch + run: 2 + - action: run_delay_last_in_branch + then: + - script.execute: delay_last_in_branch + - action: run_stop_in_conditions + then: + - script.execute: stop_in_if_condition + - script.execute: stop_in_if_else_condition + - script.execute: stop_in_while_condition + - delay: 200ms + - logger.log: "stop-in-conditions-done" + - action: run_parallel_branch + then: + - script.execute: + id: parallel_branch + run: 1 + - script.execute: + id: parallel_branch + run: 2 + - script.execute: + id: parallel_branch + run: 3 diff --git a/tests/integration/fixtures/external_components/remote_transmitter_mock/__init__.py b/tests/integration/fixtures/external_components/remote_transmitter_mock/__init__.py new file mode 100644 index 0000000000..b58eb41c59 --- /dev/null +++ b/tests/integration/fixtures/external_components/remote_transmitter_mock/__init__.py @@ -0,0 +1,25 @@ +"""Host-only stand-in for remote_transmitter used by the IR/RF integration tests.""" + +import esphome.codegen as cg +from esphome.components import remote_base +import esphome.config_validation as cv +from esphome.const import CONF_ID +from esphome.types import ConfigType + +CODEOWNERS = ["@esphome/tests"] +MULTI_CONF = True +AUTO_LOAD = ["remote_base"] + +remote_transmitter_mock_ns = cg.esphome_ns.namespace("remote_transmitter_mock") +MockRemoteTransmitter = remote_transmitter_mock_ns.class_( + "MockRemoteTransmitter", remote_base.RemoteTransmitterBase, cg.Component +) + +CONFIG_SCHEMA = cv.Schema( + {cv.GenerateID(): cv.declare_id(MockRemoteTransmitter)} +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config: ConfigType) -> None: + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) diff --git a/tests/integration/fixtures/external_components/remote_transmitter_mock/remote_transmitter_mock.cpp b/tests/integration/fixtures/external_components/remote_transmitter_mock/remote_transmitter_mock.cpp new file mode 100644 index 0000000000..cfc03f38db --- /dev/null +++ b/tests/integration/fixtures/external_components/remote_transmitter_mock/remote_transmitter_mock.cpp @@ -0,0 +1,40 @@ +#include "remote_transmitter_mock.h" +#include "esphome/core/log.h" + +#include + +namespace esphome::remote_transmitter_mock { + +static const char *const TAG = "remote_transmitter_mock"; + +void MockRemoteTransmitter::dump_config() { ESP_LOGCONFIG(TAG, "Mock Remote Transmitter"); } + +void MockRemoteTransmitter::flush_pending_completion() { + if (!this->busy_) + return; + // The RMT backend blocks here until the hardware is idle; the mock only reports the overlap + ESP_LOGW(TAG, "Overlap: a frame was requested while seq=%" PRIu16 " was in flight", this->current_seq_); + this->cancel_timeout("complete"); + this->finish_(); +} + +void MockRemoteTransmitter::send_internal(uint32_t send_times, uint32_t send_wait) { + uint64_t total_us = static_cast(send_wait) * (send_times - 1); + for (int32_t value : this->temp_.get_data()) { + total_us += static_cast(value < 0 ? -value : value) * send_times; + } + const auto duration_ms = static_cast(total_us / 1000); + + this->busy_ = true; + ESP_LOGI(TAG, "TX seq=%" PRIu16 " timings=%zu repeat=%" PRIu32 " duration=%" PRIu32 "ms", this->current_seq_, + this->temp_.get_data().size(), send_times, duration_ms); + this->set_timeout("complete", duration_ms, [this]() { this->finish_(); }); +} + +void MockRemoteTransmitter::finish_() { + this->busy_ = false; + ESP_LOGI(TAG, "Complete seq=%" PRIu16, this->current_seq_); + this->notify_complete_(true); +} + +} // namespace esphome::remote_transmitter_mock diff --git a/tests/integration/fixtures/external_components/remote_transmitter_mock/remote_transmitter_mock.h b/tests/integration/fixtures/external_components/remote_transmitter_mock/remote_transmitter_mock.h new file mode 100644 index 0000000000..5b753d9638 --- /dev/null +++ b/tests/integration/fixtures/external_components/remote_transmitter_mock/remote_transmitter_mock.h @@ -0,0 +1,31 @@ +#pragma once + +// ============================================================================ +// HOST-ONLY TEST COMPONENT โ€” DO NOT COPY TO PRODUCTION CODE +// +// Emulates a non-blocking remote transmitter: a frame "occupies" the +// transmitter for its real duration and the completion callback fires from a +// scheduler timeout, the same way the ESP32 RMT backend reports completion. +// A frame submitted while another is in flight is logged as an overlap; the +// real backends block the main loop in that case. +// ============================================================================ + +#include "esphome/core/component.h" +#include "esphome/components/remote_base/remote_base.h" + +namespace esphome::remote_transmitter_mock { + +class MockRemoteTransmitter : public remote_base::RemoteTransmitterBase, public Component { + public: + MockRemoteTransmitter() : remote_base::RemoteTransmitterBase(nullptr) {} + void dump_config() override; + + protected: + void flush_pending_completion() override; + void send_internal(uint32_t send_times, uint32_t send_wait) override; + void finish_(); + + bool busy_{false}; +}; + +} // namespace esphome::remote_transmitter_mock diff --git a/tests/integration/fixtures/external_components/wake_test_component/wake_test_component.cpp b/tests/integration/fixtures/external_components/wake_test_component/wake_test_component.cpp index b58f1c9adc..71182a46ad 100644 --- a/tests/integration/fixtures/external_components/wake_test_component/wake_test_component.cpp +++ b/tests/integration/fixtures/external_components/wake_test_component/wake_test_component.cpp @@ -16,4 +16,16 @@ void WakeTestComponent::start_async_wake() { }).detach(); } +void WakeTestComponent::start_async_timeout(uint32_t delay_ms) { + const uint32_t start_time = millis(); + std::thread([this, start_time, delay_ms] { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + const int start_loop_count = this->get_loop_count(); + this->set_timeout(delay_ms, [this, start_time, start_loop_count, delay_ms] { + ESP_LOGI(TAG, "SCHEDULER_WAKE_RESULT delay=%u elapsed=%u loop_delta=%d", static_cast(delay_ms), + static_cast(millis() - start_time), this->get_loop_count() - start_loop_count); + }); + }).detach(); +} + } // namespace esphome::wake_test_component diff --git a/tests/integration/fixtures/external_components/wake_test_component/wake_test_component.h b/tests/integration/fixtures/external_components/wake_test_component/wake_test_component.h index c8e4e0a89f..a1f06f8998 100644 --- a/tests/integration/fixtures/external_components/wake_test_component/wake_test_component.h +++ b/tests/integration/fixtures/external_components/wake_test_component/wake_test_component.h @@ -18,6 +18,10 @@ class WakeTestComponent : public Component { // loop_interval_ has been raised high enough to gate it off otherwise. void start_async_wake(); + // Spawn a detached thread that inserts a timeout (or a defer when delay_ms + // is 0) while the main loop sleeps, and report how soon it ran. + void start_async_timeout(uint32_t delay_ms); + float get_setup_priority() const override { return setup_priority::DATA; } protected: diff --git a/tests/integration/fixtures/ir_rf_transmit_complete.yaml b/tests/integration/fixtures/ir_rf_transmit_complete.yaml new file mode 100644 index 0000000000..16bc09b1ad --- /dev/null +++ b/tests/integration/fixtures/ir_rf_transmit_complete.yaml @@ -0,0 +1,30 @@ +esphome: + name: ir-rf-transmit-complete-test + +host: +api: +logger: + level: DEBUG + +external_components: + - source: + type: local + path: EXTERNAL_COMPONENT_PATH + +remote_transmitter_mock: + - id: rf_tx + - id: ir_tx + +# two entities on one transmitter: each must get only its own completion +radio_frequency: + - platform: ir_rf_proxy + name: RF Transmitter A + remote_transmitter_id: rf_tx + - platform: ir_rf_proxy + name: RF Transmitter B + remote_transmitter_id: rf_tx + +infrared: + - platform: ir_rf_proxy + name: IR Transmitter + remote_transmitter_id: ir_tx diff --git a/tests/integration/fixtures/scheduler_background_wake.yaml b/tests/integration/fixtures/scheduler_background_wake.yaml new file mode 100644 index 0000000000..78e6864eb2 --- /dev/null +++ b/tests/integration/fixtures/scheduler_background_wake.yaml @@ -0,0 +1,31 @@ +esphome: + name: scheduler-background-wake + on_boot: + priority: -100 + then: + - lambda: |- + App.set_loop_interval(5000); + +host: +api: +logger: + level: INFO + +external_components: + - source: + type: local + path: EXTERNAL_COMPONENT_PATH + components: [wake_test_component] + +wake_test_component: + id: wake_test + +button: + - platform: template + name: Start Scheduler Timeout + on_press: + - lambda: id(wake_test)->start_async_timeout(100); + - platform: template + name: Start Scheduler Defer + on_press: + - lambda: id(wake_test)->start_async_timeout(0); diff --git a/tests/integration/fixtures/sensor_timeout_throttle_filter.yaml b/tests/integration/fixtures/sensor_timeout_throttle_filter.yaml new file mode 100644 index 0000000000..5f1f9556e0 --- /dev/null +++ b/tests/integration/fixtures/sensor_timeout_throttle_filter.yaml @@ -0,0 +1,67 @@ +esphome: + name: test-timeout-throttle + +host: +api: + batch_delay: 0ms # Disable batching to receive all state updates +logger: + level: DEBUG + +sensor: + - platform: template + name: "Source" + id: source_sensor + accuracy_decimals: 2 + + # The default radar pair; codegen merges it into one TimeoutThrottleFilter + - platform: copy + source_id: source_sensor + name: "Merged" + filters: + - timeout: + timeout: 200ms + value: last + - throttle_with_priority: 200ms + + # Same chain with an identity filter in between, which keeps the two filter objects + - platform: copy + source_id: source_sensor + name: "Reference" + filters: + - timeout: + timeout: 200ms + value: last + - multiply: 1.0 + - throttle_with_priority: 200ms + +script: + - id: drive + then: + # Burst faster than the period: first passes, the rest are dropped, the timeout republishes the last + - sensor.template.publish: {id: source_sensor, state: 1.0} + - delay: 30ms + - sensor.template.publish: {id: source_sensor, state: 2.0} + - delay: 30ms + - sensor.template.publish: {id: source_sensor, state: 3.0} + - delay: 450ms + # NaN inside a burst always passes + - sensor.template.publish: {id: source_sensor, state: 4.0} + - delay: 30ms + - sensor.template.publish: {id: source_sensor, state: !lambda "return NAN;"} + - delay: 30ms + - sensor.template.publish: {id: source_sensor, state: 5.0} + - delay: 450ms + # Values right around the period + - sensor.template.publish: {id: source_sensor, state: 6.0} + - delay: 250ms + - sensor.template.publish: {id: source_sensor, state: 7.0} + - delay: 190ms + - sensor.template.publish: {id: source_sensor, state: 8.0} + - delay: 600ms + - sensor.template.publish: {id: source_sensor, state: 99.0} + +button: + - platform: template + name: "Drive" + on_press: + - script.execute: drive diff --git a/tests/integration/test_api_string_lambda.py b/tests/integration/test_api_string_lambda.py index ece8b192a2..373bd3c20b 100644 --- a/tests/integration/test_api_string_lambda.py +++ b/tests/integration/test_api_string_lambda.py @@ -1,4 +1,4 @@ -"""Integration test for TemplatableStringValue with string lambdas.""" +"""Integration test for Home Assistant action lambdas returning strings and numbers.""" from __future__ import annotations @@ -16,7 +16,7 @@ async def test_api_string_lambda( run_compiled: RunCompiledFunction, api_client_connected: APIClientConnectedFactory, ) -> None: - """Test TemplatableStringValue works with lambdas that return different types.""" + """Test Home Assistant action lambdas that return different types are sent as strings.""" loop = asyncio.get_running_loop() # Track log messages for all four service calls diff --git a/tests/integration/test_branch_resume_async.py b/tests/integration/test_branch_resume_async.py new file mode 100644 index 0000000000..78064cbe30 --- /dev/null +++ b/tests/integration/test_branch_resume_async.py @@ -0,0 +1,140 @@ +"""Test that if/else and while branches resume correctly after async actions, stop and parallel runs.""" + +from __future__ import annotations + +import asyncio +import re + +import pytest + +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_branch_resume_async( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """A branch that ends after a delay must resume its owner exactly once.""" + lines: list[str] = [] + waiters: list[tuple[str, asyncio.Future[None]]] = [] + + def has_marker(text: str, line: str) -> bool: + # Whole markers only: "after-if" must not match "queued-after-if-1" + return re.search(rf"(? None: + lines.append(line) + for text, future in waiters: + if has_marker(text, line) and not future.done(): + future.set_result(None) + + def wait_for(text: str) -> asyncio.Future[None]: + future: asyncio.Future[None] = asyncio.get_running_loop().create_future() + waiters.append((text, future)) + return future + + def count(text: str) -> int: + return sum(has_marker(text, line) for line in lines) + + async with ( + run_compiled(yaml_config, line_callback=check_output), + api_client_connected() as client, + ): + _, services = await client.list_entities_services() + service = {s.name: s for s in services} + + done = wait_for("after-if") + await client.execute_service(service["run_delay_in_if"], {}) + await asyncio.wait_for(done, timeout=2.0) + assert count("if-branch-after-delay") == 1 + assert count("if-else-should-not-run") == 0 + assert count("after-if") == 1 + + done = wait_for("after-else") + await client.execute_service(service["run_delay_in_else"], {}) + await asyncio.wait_for(done, timeout=2.0) + assert count("else-branch-after-delay") == 1 + assert count("if-then-should-not-run") == 0 + assert count("after-else") == 1 + + done = wait_for("after-while") + await client.execute_service(service["run_delay_in_while"], {}) + await asyncio.wait_for(done, timeout=2.0) + assert [count(f"while-body-{i}") for i in (1, 2, 3)] == [1, 1, 1] + assert count("while-body-4") == 0 + assert count("after-while") == 1 + + done = wait_for("stop-check-done") + await client.execute_service(service["run_stop_in_branch"], {}) + await asyncio.wait_for(done, timeout=3.0) + assert count("stop-running=0") == 1 + assert count("stopped-branch-should-not-run") == 0 + assert count("stopped-after-if-should-not-run") == 0 + + done = wait_for("after-nested-while") + await client.execute_service(service["run_nested_if_last_in_while"], {}) + await asyncio.wait_for(done, timeout=2.0) + assert [count(f"nested-if-in-while-{i}") for i in (1, 2)] == [1, 1] + assert count("nested-if-in-while-3") == 0 + assert count("after-nested-while") == 1 + + # Stop during the second iteration's delay: that iteration and the line after the loop never run; + # a fresh run then completes + done = wait_for("stop-while-done") + await client.execute_service(service["run_stop_mid_while"], {}) + await asyncio.wait_for(done, timeout=3.0) + assert count("stop-while-running=0") == 1 + assert count("stop-while-body-1") == 2 + assert [count(f"stop-while-body-{i}") for i in (2, 3, 4, 5)] == [1, 1, 1, 1] + assert count("stop-while-done") == 1 + + done = wait_for("queued-after-if-3") + await client.execute_service(service["run_queued_branch"], {}) + await asyncio.wait_for(done, timeout=2.0) + queued = [line for line in lines if "queued-" in line] + order = [ + "queued-branch-1", + "queued-after-if-1", + "queued-branch-2", + "queued-after-if-2", + "queued-branch-3", + "queued-after-if-3", + ] + assert [next(o for o in order if o in line) for line in queued] == order + + done = wait_for("restart-after-if-2") + await client.execute_service(service["run_restart_branch"], {}) + await asyncio.wait_for(done, timeout=2.0) + await asyncio.sleep(0.2) + assert count("restart-branch-1") == 0 + assert count("restart-after-if-1") == 0 + assert count("restart-branch-2") == 1 + assert count("restart-after-if-2") == 1 + + done = wait_for("after-delay-last") + await client.execute_service(service["run_delay_last_in_branch"], {}) + await asyncio.wait_for(done, timeout=2.0) + assert count("delay-last-start") == 1 + assert count("after-delay-last") == 1 + + done = wait_for("parallel-after-if-3") + await client.execute_service(service["run_parallel_branch"], {}) + await asyncio.wait_for(done, timeout=2.0) + await asyncio.sleep(0.2) + for run in (1, 2, 3): + assert count(f"parallel-branch-done-{run}") == 1 + assert count(f"parallel-after-if-{run}") == 1 + + # A condition that stops its own script must not start a branch or continue the chain + done = wait_for("stop-in-conditions-done") + await client.execute_service(service["run_stop_in_conditions"], {}) + await asyncio.wait_for(done, timeout=2.0) + assert count("if-stopped-by-condition-should-not-run") == 0 + assert count("if-else-stopped-by-condition-should-not-run") == 0 + assert count("after-if-stopped-by-condition-should-not-run") == 0 + assert count("else-stopped-by-condition-should-not-run") == 0 + assert count("after-else-stopped-by-condition-should-not-run") == 0 + assert count("while-stopped-by-condition-should-not-run") == 0 + assert count("after-while-stopped-by-condition-should-not-run") == 0 diff --git a/tests/integration/test_ir_rf_transmit_complete.py b/tests/integration/test_ir_rf_transmit_complete.py new file mode 100644 index 0000000000..d74712271f --- /dev/null +++ b/tests/integration/test_ir_rf_transmit_complete.py @@ -0,0 +1,131 @@ +"""IR/RF transmit completion replies (API 1.18) and the client pacing built on them. + +The transmitter is a host-only mock that takes a frame's real duration to +"send" it and reports completion from a scheduler timeout, like the ESP32 RMT +backend. The client is expected to hold the next frame until the device +replies, so the mock never sees an overlapping frame. +""" + +from __future__ import annotations + +import asyncio +import re + +from aioesphomeapi import InfraredInfo, RadioFrequencyInfo +from aioesphomeapi.api_pb2 import InfraredRFTransmitCompleteResponse +import pytest + +from .state_utils import find_entity +from .types import APIClientConnectedFactory, RunCompiledFunction + +FRAME_COUNT = 5 +# 10 marks and 10 spaces of 500 us, sent twice: 20 ms per frame +TIMINGS = [500, -500] * 10 +REPEAT = 2 +MOCK_EVENT = re.compile( + r"remote_transmitter_mock[^\]]*\]: (TX|Complete|Overlap)\b.*?seq=(\d+)" +) + + +@pytest.mark.shared_yaml("ir_rf_transmit_complete") +@pytest.mark.asyncio +async def test_ir_rf_transmit_complete_boot( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """The host build with the mock transmitters boots and lists all entities on any client.""" + async with run_compiled(yaml_config), api_client_connected() as client: + entities, _ = await client.list_entities_services() + assert find_entity(entities, "rf_transmitter_a", RadioFrequencyInfo) is not None + assert find_entity(entities, "rf_transmitter_b", RadioFrequencyInfo) is not None + assert find_entity(entities, "ir_transmitter", InfraredInfo) is not None + + +@pytest.mark.shared_yaml("ir_rf_transmit_complete") +@pytest.mark.asyncio +async def test_ir_rf_transmit_complete( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + """Frames are answered once they leave the transmitter, never overlap, and a refused + request is answered at once. Two RF entities share a transmitter and each gets its own + reply; the infrared entity on its own transmitter is answered the same way.""" + loop = asyncio.get_running_loop() + events: list[tuple[str, int]] = [] + all_sent = loop.create_future() + all_logged = loop.create_future() + + def line_callback(line: str) -> None: + if (match := MOCK_EVENT.search(line)) is None: + return + events.append((match.group(1), int(match.group(2)))) + if ( + match.group(1) == "Complete" + and not all_sent.done() + and sum(kind == "Complete" for kind, _ in events) == FRAME_COUNT + ): + all_sent.set_result(None) + # the log reader stops with the device, so wait for the last line before asserting on it + if len(events) == 2 * (FRAME_COUNT + 1) and not all_logged.done(): + all_logged.set_result(None) + + completions: list[InfraredRFTransmitCompleteResponse] = [] + all_replied = loop.create_future() + ir_replied = loop.create_future() + refused = loop.create_future() + + def on_complete(msg: InfraredRFTransmitCompleteResponse) -> None: + if not msg.success: + if not refused.done(): + refused.set_result(msg) + return + completions.append(msg) + if len(completions) == FRAME_COUNT and not all_replied.done(): + all_replied.set_result(None) + if len(completions) == FRAME_COUNT + 1 and not ir_replied.done(): + ir_replied.set_result(None) + + async with ( + run_compiled(yaml_config, line_callback=line_callback), + api_client_connected() as client, + ): + entities, _ = await client.list_entities_services() + rf = find_entity(entities, "rf_transmitter_a", RadioFrequencyInfo) + rf_b = find_entity(entities, "rf_transmitter_b", RadioFrequencyInfo) + ir = find_entity(entities, "ir_transmitter", InfraredInfo) + assert rf is not None and rf_b is not None, "RF transmitter entities not found" + assert ir is not None, "IR transmitter entity not found" + + client._connection.add_message_callback( + on_complete, (InfraredRFTransmitCompleteResponse,) + ) + # alternate between the two entities sharing the transmitter + keys = [rf.key if i % 2 == 0 else rf_b.key for i in range(FRAME_COUNT)] + for key in keys: + client.radio_frequency_transmit_raw_timings( + key, 433920000, TIMINGS, repeat_count=REPEAT + ) + + await asyncio.wait_for(all_replied, timeout=10) + await asyncio.wait_for(all_sent, timeout=10) + + # the infrared entity goes through the same path on its own transmitter + client.infrared_rf_transmit_raw_timings(ir.key, 38000, TIMINGS) + await asyncio.wait_for(ir_replied, timeout=10) + await asyncio.wait_for(all_logged, timeout=10) + + # A request the entity refuses is answered right away with success false; + # no timings, so the proxy rejects it before it reaches the transmitter + client.radio_frequency_transmit_raw_timings(rf.key, 433920000, []) + refused_msg = await asyncio.wait_for(refused, timeout=10) + + assert [msg.key for msg in completions] == [*keys, ir.key] + assert refused_msg.key == rf.key + + # The mocks saw one frame at a time: every transmit follows the previous completion + kinds = [kind for kind, _ in events] + assert kinds == ["TX", "Complete"] * (FRAME_COUNT + 1), events + seqs = [seq for _, seq in events] + assert seqs[::2] == seqs[1::2], events diff --git a/tests/integration/test_scheduler_background_wake.py b/tests/integration/test_scheduler_background_wake.py new file mode 100644 index 0000000000..fa7d34f494 --- /dev/null +++ b/tests/integration/test_scheduler_background_wake.py @@ -0,0 +1,84 @@ +"""A background scheduler insertion must interrupt the loop's current sleep. + +Covers both set_timeout and defer: a zero-delay insert from another thread +takes the separate defer queue on multi-threaded builds. +""" + +from __future__ import annotations + +import asyncio +from pathlib import Path +import re + +from aioesphomeapi import ButtonInfo +import pytest + +from .state_utils import require_entity +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_scheduler_background_wake( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + external_components_path = str( + Path(__file__).parent / "fixtures" / "external_components" + ) + yaml_config = yaml_config.replace( + "EXTERNAL_COMPONENT_PATH", external_components_path + ) + + loop = asyncio.get_running_loop() + results: dict[int, asyncio.Future[tuple[int, int]]] = { + 100: loop.create_future(), + 0: loop.create_future(), + } + + def on_log_line(line: str) -> None: + match = re.search( + r"SCHEDULER_WAKE_RESULT delay=(\d+) elapsed=(\d+) loop_delta=(-?\d+)", + line, + ) + if match is None: + return + result = results[int(match.group(1))] + if not result.done(): + result.set_result((int(match.group(2)), int(match.group(3)))) + + async with ( + run_compiled(yaml_config, line_callback=on_log_line), + api_client_connected() as client, + ): + device_info = await client.device_info() + assert device_info is not None + assert device_info.name == "scheduler-background-wake" + entities, _ = await client.list_entities_services() + for object_id, delay in ( + ("start_scheduler_timeout", 100), + ("start_scheduler_defer", 0), + ): + start_button = require_entity( + entities, + object_id, + ButtonInfo, + description=f"{object_id} button", + ) + client.button_command(start_button.key) + + try: + elapsed, loop_delta = await asyncio.wait_for( + results[delay], timeout=10.0 + ) + except TimeoutError: + pytest.fail(f"background insert with delay={delay} did not fire") + + assert elapsed < 1000, ( + f"background insert with delay={delay} should interrupt the " + f"five-second sleep; it fired after {elapsed}ms" + ) + assert loop_delta == 0, ( + f"scheduler-only wake must not run component loops; observed " + f"loop_delta={loop_delta} for delay={delay}" + ) diff --git a/tests/integration/test_sensor_timeout_throttle_filter.py b/tests/integration/test_sensor_timeout_throttle_filter.py new file mode 100644 index 0000000000..739421ca3a --- /dev/null +++ b/tests/integration/test_sensor_timeout_throttle_filter.py @@ -0,0 +1,61 @@ +"""The merged timeout + throttle_with_priority filter publishes exactly what the two filter chain does.""" + +from __future__ import annotations + +import asyncio +import math + +from aioesphomeapi import ButtonInfo, EntityState, SensorState +import pytest + +from .state_utils import InitialStateHelper, build_key_to_entity_mapping +from .types import APIClientConnectedFactory, RunCompiledFunction + + +@pytest.mark.asyncio +async def test_sensor_timeout_throttle_filter( + yaml_config: str, + run_compiled: RunCompiledFunction, + api_client_connected: APIClientConnectedFactory, +) -> None: + loop = asyncio.get_running_loop() + values: dict[str, list[float]] = {"merged": [], "reference": []} + done = {name: loop.create_future() for name in values} + + def on_state(state: EntityState) -> None: + if not isinstance(state, SensorState): + return + name = key_to_sensor.get(state.key) + if name not in values: + return + values[name].append(math.nan if state.missing_state else state.state) + # 99 is published last and republished once by the timeout + if values[name].count(99.0) == 2 and not done[name].done(): + done[name].set_result(True) + + async with run_compiled(yaml_config), api_client_connected() as client: + entities, _ = await client.list_entities_services() + key_to_sensor = build_key_to_entity_mapping( + entities, {"merged": "Merged", "reference": "Reference"} + ) + initial_state_helper = InitialStateHelper(entities) + client.subscribe_states(initial_state_helper.on_state_wrapper(on_state)) + await initial_state_helper.wait_for_initial_states() + + button = next( + e.key for e in entities if isinstance(e, ButtonInfo) and e.name == "Drive" + ) + client.button_command(button) + try: + await asyncio.wait_for(asyncio.gather(*done.values()), timeout=5.0) + except TimeoutError: + pytest.fail(f"Timed out: {values}") + + merged, reference = values["merged"], values["reference"] + assert len(merged) == len(reference), (merged, reference) + for got, want in zip(merged, reference, strict=True): + assert (math.isnan(got) and math.isnan(want)) or got == want, ( + merged, + reference, + ) + assert any(math.isnan(v) for v in merged) diff --git a/tests/script/test_ci_custom.py b/tests/script/test_ci_custom.py index 5a943cda08..998650079e 100644 --- a/tests/script/test_ci_custom.py +++ b/tests/script/test_ci_custom.py @@ -386,3 +386,131 @@ def test_ternary_error_message_names_the_literal() -> None: ) assert len(errs) == 1 assert 'LOG_STR_LITERAL("enabled")' in errs[0][2] + + +@pytest.mark.parametrize( + "line", + [ + 'static const char *const TAG = "x";', + 'static const char* const TAG = "x";', + 'static const char *TAG = "x";', + 'constexpr const char *TAG = "x";', + 'static const char *const TAG{"x"};', + 'static constexpr char TAG[] = "x";', + 'static constexpr char TAG[] = {"x"};', + 'static const char TAG[8] = "x";', + ' static const char *const TAG = "x";', + ], +) +def test_log_tag_macro_flags_plain_tags(line: str) -> None: + assert ci_custom.lint_log_tag_macro("test.cpp", line + "\n") + + +@pytest.mark.parametrize( + "line", + ['ESPHOME_LOG_TAG(TAG, "x");', 'static const char *const NAME = "x";'], +) +def test_log_tag_macro_ignores_macro_and_other_names(line: str) -> None: + assert not ci_custom.lint_log_tag_macro("test.cpp", line + "\n") + + +@pytest.mark.parametrize( + "line", + [ + "this->set_timeout(TAG, 100, cb);", + "strcmp(TAG, name);", + "strcmp(name, TAG);", + "strncpy(buf, TAG, sizeof(buf));", + "strcmp(get_name(), TAG);", + "this->set_timeout(make_id(x), TAG, cb);", + "std::string(TAG);", + "std::string name = TAG;", + "std::string name{TAG};", + ], +) +def test_log_tag_as_string_flags_string_uses(line: str) -> None: + assert ci_custom.lint_log_tag_as_string("test.cpp", line + "\n") + + +@pytest.mark.parametrize( + "line", + [ + 'ESP_LOGD(TAG, "x");', + 'this->set_timeout(100, [this]() { ESP_LOGD(TAG, "x"); });', + "strcmp(name, OTHER_TAG);", + ], +) +def test_log_tag_as_string_ignores_logging(line: str) -> None: + assert not ci_custom.lint_log_tag_as_string("test.cpp", line + "\n") + + +# --- rule: every component __init__.py defines DOMAIN as its own name --- + + +def _domain_check() -> dict: + return next( + c + for c in ci_custom.LINT_CONTENT_CHECKS + if c["func"] is ci_custom.lint_component_domain + ) + + +def _lint_domain(fname: str, content: str) -> str | None: + return ci_custom.run_check(_domain_check(), fname, Path(fname), content) + + +@pytest.mark.parametrize( + "content", + [ + 'CODEOWNERS = ["@x"]\nDOMAIN = "uart"\n', + 'DOMAIN: str = "uart"\n', + ], +) +def test_domain_matching_component_name_passes(content: str) -> None: + assert _lint_domain("esphome/components/uart/__init__.py", content) is None + + +def test_domain_leaves_syntax_errors_to_the_python_linters() -> None: + assert _lint_domain("esphome/components/uart/__init__.py", "def (\n") is None + + +@pytest.mark.parametrize( + "content", + [ + 'CODEOWNERS = ["@x"]\n', + 'DOMAIN = "spi"\n', + "DOMAIN = CONF_UART\n", + '"""Docstring.\n\nDOMAIN = "uart"\n"""\n', + 'def f() -> None:\n DOMAIN = "uart"\n', + 'OTHER = "uart"\n', + "DOMAIN: str\n", + ], +) +def test_domain_missing_or_wrong_is_flagged(content: str) -> None: + err = _lint_domain("esphome/components/uart/__init__.py", content) + assert err is not None + assert 'DOMAIN = "uart"' in err + + +@pytest.mark.parametrize( + "fname", + [ + "esphome/components/uart/sensor/__init__.py", + "esphome/components/uart/sensor.py", + "esphome/core/__init__.py", + ], +) +def test_domain_ignores_files_other_than_component_init(fname: str) -> None: + assert _lint_domain(fname, "") is None + + +def test_every_component_defines_its_domain() -> None: + components = SCRIPT_DIR.parent / "esphome" / "components" + missing = [ + init.parent.name + for init in sorted(components.glob("*/__init__.py")) + if ci_custom.lint_component_domain( + init.relative_to(SCRIPT_DIR.parent), init.read_text(encoding="utf-8") + ) + ] + assert missing == [] diff --git a/tests/script/test_determine_jobs.py b/tests/script/test_determine_jobs.py index 7e9b09b2b9..e63e0849de 100644 --- a/tests/script/test_determine_jobs.py +++ b/tests/script/test_determine_jobs.py @@ -2613,8 +2613,8 @@ def test_component_batching_beta_branch_groups_evenly( For beta/release branches, all components should be groupable (not isolated), so they share one grouped build per runner and spread evenly. """ - # Create 120 test components with test files - component_names = [f"comp_{i:03d}" for i in range(120)] + # Create 200 test components with test files + component_names = [f"comp_{i:03d}" for i in range(200)] tests_dir = tmp_path / "tests" / "components" for comp in component_names: @@ -2675,15 +2675,15 @@ def test_component_batching_beta_branch_groups_evenly( assert "component_test_batches" in output batches = output["component_test_batches"] - # One grouped esp32-idf build plus 5 s per extra component is 640 s, - # so two runners of 60 components each - assert [len(batch["components"].split()) for batch in batches] == [60, 60] + # One grouped esp32-idf build plus 3 s per extra component is 652 s, + # so two runners of 100 components each + assert [len(batch["components"].split()) for batch in batches] == [100, 100] - # Verify all 120 components are in batches + # Verify all 200 components are in batches all_components = [] for batch in batches: all_components.extend(batch["components"].split()) - assert len(all_components) == 120 + assert len(all_components) == 200 assert set(all_components) == set(component_names) diff --git a/tests/script/test_split_components_for_ci.py b/tests/script/test_split_components_for_ci.py index 88a1a17b33..718d754f01 100644 --- a/tests/script/test_split_components_for_ci.py +++ b/tests/script/test_split_components_for_ci.py @@ -35,13 +35,46 @@ def test_build_seconds() -> None: """Known platforms use the table; nrf52 boards share one estimate.""" build_seconds = split_components_for_ci.build_seconds assert build_seconds("host") == 10 - assert build_seconds("esp32-idf") == 45 + assert build_seconds("esp32-idf") == 55 assert ( build_seconds("nrf52-xiao-ble") == split_components_for_ci.NRF52_BUILD_SECONDS ) assert ( - build_seconds("esp32-s3-idf") == split_components_for_ci.DEFAULT_BUILD_SECONDS + build_seconds("esp32-c5-idf") == split_components_for_ci.DEFAULT_BUILD_SECONDS ) + assert ( + build_seconds("esp32-c3-ard") + == split_components_for_ci.ESP32_ARDUINO_BUILD_SECONDS + ) + + +def test_heavy_components(tests_dir: Path) -> None: + """A heavy component, or a test that enables one, costs its library time.""" + heavy_components = split_components_for_ci.heavy_components + heavy_seconds = split_components_for_ci.heavy_seconds + table = split_components_for_ci.HEAVY_COMPONENT_SECONDS + _add_component(tests_dir, "dht", ["test.esp32-idf.yaml"]) + _add_component(tests_dir, "micro_wake_word", ["test.esp32-idf.yaml"]) + assert heavy_components("dht", tests_dir / "dht/test.esp32-idf.yaml") == set() + assert heavy_components( + "micro_wake_word", tests_dir / "micro_wake_word/test.esp32-idf.yaml" + ) == {"micro_wake_word"} + assert heavy_seconds(frozenset({"micro_wake_word"})) == table["micro_wake_word"] + + display = tests_dir / "mipi_spi" + display.mkdir() + (display / "common.yaml").write_text("web_server:\n port: 80\n") + (display / "test-lvgl.esp32-s3-idf.yaml").write_text( + "packages:\n" + " mipi_spi: !include common.yaml\n" + "lvgl:\n pages: []\n" + "light:\n - platform: fastled_spi\n num_leds: 1\n" + ) + assert heavy_components("mipi_spi", display / "test-lvgl.esp32-s3-idf.yaml") == { + "lvgl", + "web_server", + "fastled_spi", + } def test_make_item_isolated_and_grouped(tests_dir: Path) -> None: @@ -61,29 +94,93 @@ def test_make_item_isolated_and_grouped(tests_dir: Path) -> None: isolated = split_components_for_ci._make_item( tests_dir, "comp", "isolated_comp", True ) - assert isolated.own_seconds == 45 + 10 + 45 + 50 + assert isolated.own_seconds == 55 + 10 + 55 + 45 assert isolated.grouped_builds == {} # The rp2040 variant has no base test to group with, so it always runs grouped = split_components_for_ci._make_item(tests_dir, "comp", "i2c", False) - assert grouped.own_seconds == 50 - assert grouped.grouped_builds == {("i2c", "esp32-idf"): 90, ("i2c", "host"): 10} + assert grouped.own_seconds == 45 + assert grouped.grouped_builds == { + ("i2c", "esp32-idf"): (110, frozenset()), + ("i2c", "host"): (10, frozenset()), + } + + +def test_make_item_heavy_component(tests_dir: Path) -> None: + """A heavy component's library cost follows it into every build.""" + _add_component(tests_dir, "lvgl", ["test.esp32-idf.yaml", "test-a.host.yaml"]) + extra = split_components_for_ci.HEAVY_COMPONENT_SECONDS["lvgl"] + + item = split_components_for_ci._make_item(tests_dir, "lvgl", "spi", False) + assert item.own_seconds == 10 + extra + assert item.grouped_builds == { + ("spi", "esp32-idf"): (55 + extra, frozenset({"lvgl"})) + } + + +def test_heavy_cost_is_per_platform(tests_dir: Path) -> None: + """A base test that enables a heavy component only charges its own platform.""" + comp = tests_dir / "web_server_idf" + comp.mkdir() + (comp / "test.esp32-idf.yaml").write_text("web_server:\n port: 80\n") + (comp / "test.host.yaml").write_text("logger:\n") + extra = split_components_for_ci.HEAVY_COMPONENT_SECONDS["web_server"] + + item = split_components_for_ci._make_item( + tests_dir, "web_server_idf", "none", False + ) + assert item.grouped_builds == { + ("none", "esp32-idf"): (55 + extra, frozenset({"web_server"})), + ("none", "host"): (10, frozenset()), + } def test_grouped_component_joins_existing_build() -> None: """A second member turns a lone build into a group that skips variants.""" item = split_components_for_ci._BatchItem + share = split_components_for_ci._GroupedShare batch = split_components_for_ci._Batch() - batch.add(item("a", 0, {("i2c", "esp32-idf"): 90})) - assert batch.seconds == 90 + batch.add(item("a", 0, {("i2c", "esp32-idf"): share(110, frozenset())})) + assert batch.seconds == 110 - other = item("b", 0, {("i2c", "esp32-idf"): 45, ("i2c", "host"): 10}) - grouped = 45 + split_components_for_ci.GROUPED_COMPONENT_SECONDS - assert batch.added_seconds(other) == grouped - 90 + 10 + other = item( + "b", + 0, + { + ("i2c", "esp32-idf"): share(55, frozenset()), + ("i2c", "host"): share(10, frozenset()), + }, + ) + grouped = 55 + split_components_for_ci.GROUPED_COMPONENT_SECONDS + assert batch.added_seconds(other) == grouped - 110 + 10 batch.add(other) assert batch.seconds == grouped + 10 +def test_heavy_component_charges_the_shared_build() -> None: + """A heavy member makes the whole grouped build slower, once per library.""" + item = split_components_for_ci._BatchItem + share = split_components_for_ci._GroupedShare + batch = split_components_for_ci._Batch() + extra = split_components_for_ci.HEAVY_COMPONENT_SECONDS["lvgl"] + per_member = split_components_for_ci.GROUPED_COMPONENT_SECONDS + lvgl = frozenset({"lvgl"}) + batch.add(item("a", 0, {("spi", "esp32-idf"): share(55, frozenset())})) + + heavy = item("lvgl", 0, {("spi", "esp32-idf"): share(55 + extra, lvgl)}) + grouped = 55 + per_member + extra + assert batch.added_seconds(heavy) == grouped - 55 + batch.add(heavy) + assert batch.seconds == grouped + + light = item("b", 0, {("spi", "esp32-idf"): share(55, frozenset())}) + assert batch.added_seconds(light) == per_member + + # A second member that also draws with lvgl compiles the library once + also_heavy = item("c", 0, {("spi", "esp32-idf"): share(55 + extra, lvgl)}) + assert batch.added_seconds(also_heavy) == per_member + + def test_isolated_components_balance_across_runners(tests_dir: Path) -> None: """Heavy isolated components spread out and light ones fill the gaps.""" platforms = ["esp32-idf", "esp8266-ard", "rp2040-ard", "bk72xx-ard"] @@ -138,7 +235,7 @@ def test_groupable_components_split_evenly(tests_dir: Path) -> None: components=names, tests_dir=tests_dir, target_seconds=100 ) - assert sorted(len(batch) for batch in batches) == [2, 2, 2, 3, 3] + assert sorted(len(batch) for batch in batches) == [3, 3, 3, 3] assert sorted(c for batch in batches for c in batch) == names @@ -149,7 +246,25 @@ def test_make_item_reads_tests_dir(tmp_path: Path) -> None: _add_component(other, "comp", ["test.esp32-idf.yaml"]) item = split_components_for_ci._make_item(other, "comp", "isolated_comp", True) - assert item.own_seconds == 45 + assert item.own_seconds == 55 + + +def test_balance_batches_keeps_partners_together() -> None: + """Two components sharing a build land on one runner even when the + lightest runner is the other one.""" + share = { + ("i2c", "esp32-idf"): split_components_for_ci._GroupedShare(55, frozenset()), + ("i2c", "esp8266-ard"): split_components_for_ci._GroupedShare(30, frozenset()), + } + items = [ + split_components_for_ci._BatchItem("partner_a", 0, dict(share)), + split_components_for_ci._BatchItem("partner_b", 0, dict(share)), + split_components_for_ci._BatchItem("alone", 80), + ] + + batches = split_components_for_ci.balance_batches(items, target_seconds=120) + + assert sorted(batches) == [["alone"], ["partner_a", "partner_b"]] def test_balance_batches_empty() -> None: diff --git a/tests/unit_tests/build_gen/test_arduino8266.py b/tests/unit_tests/build_gen/test_arduino8266.py index 066746d36d..2ea4ffabe6 100644 --- a/tests/unit_tests/build_gen/test_arduino8266.py +++ b/tests/unit_tests/build_gen/test_arduino8266.py @@ -34,7 +34,12 @@ from esphome.components.esp8266.boards import ( board_ld_script, ) from esphome.components.esp8266.build_surgery import RATETABLE_RULE -from esphome.components.esp8266.const import KEY_BOARD, KEY_ESP8266, KEY_SCANF_FLOAT +from esphome.components.esp8266.const import ( + KEY_BOARD, + KEY_ESP8266, + KEY_SCANF_FLOAT, + THROW_STUBS_HEADER, +) import esphome.config_validation as cv from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION from esphome.core import CORE, EsphomeError @@ -360,18 +365,22 @@ def test_write_project_link_line_and_exclusions(tmp_path: Path) -> None: # Assembly and C sources compile through their own rules assert "cont.S.o: aspp" in content assert "abi.c.o: c" in content - # throw_stubs is force-included for ESPHome sources only, via one shared - # srcflags variable rather than a copy of the flags line per edge + # throw_stubs reaches src through srcflags (after the pch include) and the + # core through frameworkflags: one shared variable each, not a copy per edge src_lines = [line for line in content.splitlines() if "obj/src/" in line] assert any("main.cpp.o: cxx" in line for line in src_lines) - assert content.count("throw_stubs.h") == 1 + assert content.count("throw_stubs.h") == 2 assert "srcflags = -include" in content flags_lines = [ line for line in content.splitlines() if line.startswith(" flags = ") ] assert flags_lines # C++ src edges consume the precompiled header; C/assembly keep srcflags - assert set(flags_lines) == {" flags = $srcflags", " flags = $srccxxflags"} + assert set(flags_lines) == { + " flags = $srcflags", + " flags = $srccxxflags", + " flags = $frameworkflags", + } def test_write_project_pch(tmp_path: Path) -> None: @@ -715,6 +724,8 @@ def test_write_project_libraries_and_variant( assert "libHeadersOnly.a" not in content assert "Library HeadersOnly has no source files" in caplog.text assert " flags = -DMYLIB=1" in content + # With exceptions on, nothing gets the stubs + assert "throw_stubs.h" not in content # A library's own include dirs lead its compile lines assert " own_includes = -I" in content assert "$own_includes $cxxflags $flags" in content @@ -729,6 +740,30 @@ def test_write_project_libraries_and_variant( assert f"ccache = {_shq('/cc/ccache')}" in content +def test_write_project_throw_stubs_reach_core_and_libraries(tmp_path: Path) -> None: + """Without exceptions the core and libraries take the throw stubs too.""" + paths = _make_framework(tmp_path) + lib_dir = tmp_path / "libsrc" + lib_dir.mkdir() + (lib_dir / "lib.cpp").write_text("") + library = ArduinoLibrary( + name="MyLib", + sources=[lib_dir / "lib.cpp"], + include_dirs=[lib_dir], + flags=["-DMYLIB=1"], + ) + content = _write_ninja(paths, libraries=[library]) + stubs = CORE.relative_src_path() / THROW_STUBS_HEADER + assert f"frameworkflags = -include {_shq(str(stubs))}" in content + # Before the library's own flags; core edges take the variable alone + assert " flags = $frameworkflags -DMYLIB=1" in content + lines = content.splitlines() + core_edge = next( + i for i, line in enumerate(lines) if "core_esp8266_main.cpp.o: cxx" in line + ) + assert lines[core_edge + 1] == " flags = $frameworkflags" + + def test_get_flash_ld_path(tmp_path: Path) -> None: paths = InstalledPaths( diff --git a/tests/unit_tests/build_gen/test_espidf.py b/tests/unit_tests/build_gen/test_espidf.py index 2c95c61b37..c439b87d16 100644 --- a/tests/unit_tests/build_gen/test_espidf.py +++ b/tests/unit_tests/build_gen/test_espidf.py @@ -4,6 +4,7 @@ from __future__ import annotations import json import logging +import os from pathlib import Path from unittest.mock import patch @@ -350,17 +351,54 @@ def test_get_project_cmakelists_emits_exclude_components(tmp_path: Path) -> None assert "ESPHOME_PROJECT_BUILTIN_COMPONENTS esp_lcd" not in content -def test_get_project_cmakelists_minimal_emits_exclude_components() -> None: - """The discovery (minimal) write also excludes components so they never - register in project_description.json.""" +def test_get_project_cmakelists_minimal_excludes_only_managed_components( + tmp_path: Path, +) -> None: + """The discovery (minimal) write lets built-in components register so + the cached list serves every exclusion set; managed ones stay excluded.""" + (tmp_path / "components" / "unity").mkdir(parents=True) + CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS] = {"unity", "espressif__esp_hosted"} + register_exclude_components_cmake_arg() + + with patch("esphome.espidf.toolchain._get_idf_path", return_value=tmp_path): + minimal = _render(minimal=True) + full = _render() + + assert 'set(EXCLUDE_COMPONENTS "espressif__esp_hosted")' in minimal + assert 'set(EXCLUDE_COMPONENTS "espressif__esp_hosted;unity")' in full + + +def test_get_project_cmakelists_minimal_keeps_exclusions_for_custom_idf_path( + tmp_path: Path, +) -> None: + """Nothing is cached for a user-supplied IDF_PATH, so its discovery write + keeps every exclusion.""" + (tmp_path / "components" / "unity").mkdir(parents=True) CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS] = {"unity"} register_exclude_components_cmake_arg() - content = _render(minimal=True) + with ( + patch("esphome.espidf.toolchain._get_idf_path", return_value=tmp_path), + patch.dict(os.environ, {"IDF_PATH": str(tmp_path)}), + ): + content = _render(minimal=True) assert 'set(EXCLUDE_COMPONENTS "unity")' in content +def test_get_project_cmakelists_minimal_drops_exclude_line_when_all_builtin( + tmp_path: Path, +) -> None: + (tmp_path / "components" / "unity").mkdir(parents=True) + CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS] = {"unity"} + register_exclude_components_cmake_arg() + + with patch("esphome.espidf.toolchain._get_idf_path", return_value=tmp_path): + content = _render(minimal=True) + + assert "EXCLUDE_COMPONENTS" not in content + + def test_get_project_cmakelists_no_exclude_components_line_when_empty() -> None: """No EXCLUDE_COMPONENTS line at all when nothing is excluded.""" register_exclude_components_cmake_arg() diff --git a/tests/unit_tests/components/api/test_api_proto.py b/tests/unit_tests/components/api/test_api_proto.py index 3e2f37e16c..ca3c0ce7b5 100644 --- a/tests/unit_tests/components/api/test_api_proto.py +++ b/tests/unit_tests/components/api/test_api_proto.py @@ -40,6 +40,7 @@ API_DIR = Path(esphome.__file__).parent / "components" / "api" PROTO_TEXT = (API_DIR / "api.proto").read_text(encoding="utf-8") HEADER_TEXT = (API_DIR / "api_pb2.h").read_text(encoding="utf-8") CPP_TEXT = (API_DIR / "api_pb2.cpp").read_text(encoding="utf-8") +SERVICE_TEXT = (API_DIR / "api_pb2_service.cpp").read_text(encoding="utf-8") API_CONNECTION_TEXT = (API_DIR / "api_connection.cpp").read_text(encoding="utf-8") # Fields on DeviceInfoResponse that were superseded by DeviceCapabilitiesResponse @@ -66,7 +67,11 @@ NEW_CAPABILITY_FIELDS: dict[str, dict[str, int]] = { "voice_assistant": 2, "zwave_proxy": 3, "serial_proxies": 4, + "wizard": 5, + "sendspin": 6, }, + "WizardCapabilities": {"configured": 1}, + "SendspinCapabilities": {"feature_flags": 1}, "BluetoothProxyCapabilities": { "feature_flags": 1, "mac_address": 2, @@ -359,6 +364,123 @@ def test_device_capabilities_rpc_requires_authentication() -> None: ) +def test_device_wizard_messages_keep_their_wire_ids() -> None: + """Message ids are part of the wire protocol and must not change.""" + for message, expected in ( + ("DeviceWizardRequest", 156), + ("DeviceWizardResponse", 157), + ("WizardInputSetRequest", 158), + ): + body = _extract_proto_message(PROTO_TEXT, message) + match = re.search(r"option \(id\) = (\d+);", body) + assert match is not None, f"{message} is missing `option (id)`" + assert int(match.group(1)) == expected, ( + f"{message} has id {match.group(1)}, expected {expected}" + ) + + +def test_device_wizard_message_fields_keep_their_wire_numbers() -> None: + """Clients decode the wizard purely by field number.""" + fields: dict[str, dict[str, int]] = { + "WizardInputSetRequest": {"key": 1, "entity_id": 2}, + "DeviceWizardResponse": {"data": 1}, + } + for message, message_fields in fields.items(): + body = _extract_proto_message(PROTO_TEXT, message) + for field_name, number in message_fields.items(): + line = _field_declaration_line(body, field_name) + assert re.search(rf"\b{field_name}\s*=\s*{number}\b", line), ( + f"{field_name} in {message} is no longer field number {number}" + ) + + +def _enclosing_ifdefs(text: str, anchor: str) -> list[str]: + """The conditions of the #if blocks that enclose the first line starting with anchor.""" + stack: list[str] = [] + for line in text.splitlines(): + if line.startswith(anchor): + return stack + if line.startswith("#if"): + stack.append(line) + elif line.startswith("#endif"): + stack.pop() + raise AssertionError(f"{anchor} not found") + + +# The define that compiles each wizard message in, so a device only pays for what its wizard uses +WIZARD_MESSAGE_DEFINES: dict[str, str] = { + "DeviceWizardRequest": "USE_API_WIZARD", + "DeviceWizardResponse": "USE_API_WIZARD", + "WizardInputSetRequest": "USE_API_WIZARD_INPUTS", +} +# Not marked in api.proto: the generator gives it the guard of the field that uses it +NESTED_WIZARD_MESSAGE_DEFINES: dict[str, str] = {"WizardCapabilities": "USE_API_WIZARD"} + + +def test_wizard_messages_are_compiled_out_without_a_wizard() -> None: + """A device must pay nothing for wizard parts it does not use: every wizard + message carries its define in api.proto, and the generated code guards the + nested message with the define of the field that holds it. + """ + for message, define in WIZARD_MESSAGE_DEFINES.items(): + body = _extract_proto_message(PROTO_TEXT, message) + assert f'option (ifdef) = "{define}";' in body, message + # The request is empty, so it has no class, only its dispatch case + assert "#ifdef USE_API_WIZARD\n case 156 /* DeviceWizardRequest" in SERVICE_TEXT + classes = {**WIZARD_MESSAGE_DEFINES, **NESTED_WIZARD_MESSAGE_DEFINES} + for message, define in classes.items(): + if message == "DeviceWizardRequest": + continue + assert any( + define in cond + for cond in _enclosing_ifdefs(HEADER_TEXT, f"class {message} final") + ), f"{message} is not guarded by {define} in api_pb2.h" + assert "#ifdef USE_API_WIZARD\n resp.wizard.configured = true;" in ( + API_CONNECTION_TEXT + ) + + +def test_device_wizard_rpc_requires_authentication() -> None: + """The wizard is only served on an authenticated connection, so the rpc + must not set `needs_authentication` and inherits the default of true. + """ + body = _extract_rpc_body(PROTO_TEXT, "device_wizard") + assert "needs_authentication" not in body + + +def test_sendspin_pairing_token_messages_keep_their_wire_ids() -> None: + """Message ids are part of the wire protocol and must not change.""" + for message, expected in ( + ("SendspinPairingTokenRequest", 159), + ("SendspinPairingTokenResponse", 160), + ): + body = _extract_proto_message(PROTO_TEXT, message) + match = re.search(r"option \(id\) = (\d+);", body) + assert match is not None, f"{message} is missing `option (id)`" + assert int(match.group(1)) == expected, ( + f"{message} has id {match.group(1)}, expected {expected}" + ) + + +def test_sendspin_pairing_token_response_fields_keep_their_wire_numbers() -> None: + """Clients decode the response purely by field number.""" + body = _extract_proto_message(PROTO_TEXT, "SendspinPairingTokenResponse") + for field_name, number in (("status", 1), ("token", 2)): + line = _field_declaration_line(body, field_name) + assert re.search(rf"\b{field_name}\s*=\s*{number}\b", line), ( + f"{field_name} in SendspinPairingTokenResponse is no longer field " + f"number {number}" + ) + + +def test_sendspin_pairing_token_rpc_requires_authentication() -> None: + """The pairing token is a long-lived secret, so the rpc must not set + `needs_authentication` and inherits the default of true. + """ + body = _extract_rpc_body(PROTO_TEXT, "sendspin_pairing_token") + assert "needs_authentication" not in body + + # ==================== Group C: advertised API version ==================== diff --git a/tests/unit_tests/components/api/test_api_protobuf_generator.py b/tests/unit_tests/components/api/test_api_protobuf_generator.py index 2e4eb67a24..5e29b06ef0 100644 --- a/tests/unit_tests/components/api/test_api_protobuf_generator.py +++ b/tests/unit_tests/components/api/test_api_protobuf_generator.py @@ -360,3 +360,56 @@ def test_repeated_strings_generate_in_every_direction(source: int) -> None: assert "nullptr" not in header if source != SOURCE_SERVER: assert "case proto_tag(1, WIRE_TYPE_LENGTH_DELIMITED):" in cpp + + +def _progmem_field( + *, pointer_to_buffer: bool = True, force: bool = False, repeated: bool = False +) -> descriptor_pb2.FieldDescriptorProto: + field = _field(12, force=force, repeated=repeated) + field.options.Extensions[pb.progmem] = True + if pointer_to_buffer: + field.options.Extensions[pb.pointer_to_buffer] = True + return field + + +@pytest.mark.parametrize( + ("force", "encode_fn"), + [(False, "encode_progmem_bytes("), (True, "encode_progmem_bytes_force(")], +) +def test_progmem_bytes_field_copies_from_flash(force: bool, encode_fn: str) -> None: + """A (progmem) field encodes and dumps through the progmem_memcpy helpers.""" + ti = create_field_type_info(_progmem_field(force=force), needs_decode=False) + assert encode_fn in ti.encode_content + assert "encode_bytes" not in ti.encode_content + assert "dump_progmem_bytes_field(" in ti.dump_content + + +@pytest.mark.parametrize( + "field", + [ + pytest.param(_progmem_field(pointer_to_buffer=False), id="no_pointer"), + pytest.param(_progmem_field(repeated=True), id="repeated"), + ], +) +def test_progmem_rejected_where_it_cannot_apply( + field: descriptor_pb2.FieldDescriptorProto, +) -> None: + """(progmem) on a field that would not copy from flash fails instead of silently using memcpy.""" + with pytest.raises(ValueError, match="progmem on field 'value'"): + create_field_type_info(field, needs_decode=False) + + +def test_progmem_rejected_in_a_decoded_message() -> None: + """Received data is never in flash, so a decoded message cannot use (progmem).""" + desc = descriptor_pb2.DescriptorProto(name="Received") + desc.field.add().CopyFrom(_progmem_field()) + with pytest.raises(ValueError, match="progmem on field 'value' of Received"): + build_message_type(desc, {}, {"Received": SOURCE_CLIENT}) + + +def test_progmem_rejected_on_non_bytes_field() -> None: + """(progmem) only applies to bytes fields.""" + field = _field(9) + field.options.Extensions[pb.progmem] = True + with pytest.raises(ValueError, match="progmem on field 'value'"): + create_field_type_info(field, needs_decode=False) diff --git a/tests/unit_tests/components/esp8266/test_toolchain_validation.py b/tests/unit_tests/components/esp8266/test_toolchain_validation.py index e7f443c2d2..1092c168ad 100644 --- a/tests/unit_tests/components/esp8266/test_toolchain_validation.py +++ b/tests/unit_tests/components/esp8266/test_toolchain_validation.py @@ -12,10 +12,12 @@ import pytest from esphome.components import esp8266 from esphome.components.esp8266 import ( ARDUINO_FRAMEWORK_SCHEMA, + _resolve_board, _resolve_toolchain, _validate_native_toolchain, _warn_platformio_toolchain, ) +from esphome.components.esp8266.const import KEY_BOARD, KEY_ESP8266 import esphome.config_validation as cv from esphome.const import ( CONF_BOARD, @@ -118,6 +120,57 @@ def test_unsupported_board_rejected() -> None: _validate_native_toolchain(_config(board="not_a_board")) +def test_known_board_passes_unchanged() -> None: + config = {CONF_BOARD: "esp01_1m"} + assert _resolve_board(config) is config + + +@pytest.mark.parametrize( + ("board", "expected"), + [("ESP01-1M", "esp01_1m"), ("SPARKFUNBLYNK", "sparkfunBlynk")], +) +def test_misspelled_board_normalized( + board: str, expected: str, caplog: pytest.LogCaptureFixture +) -> None: + assert _resolve_board({CONF_BOARD: board})[CONF_BOARD] == expected + assert f"using '{expected}'" in caplog.text + + +@pytest.mark.parametrize( + ("board", "expected_board", "expected_toolchain"), + [ + ("esp01-1m", "esp01_1m", Toolchain.ARDUINO), + ("my_custom_board", "my_custom_board", Toolchain.PLATFORMIO), + ], +) +def test_config_schema_resolves_board_before_toolchain( + board: str, + expected_board: str, + expected_toolchain: Toolchain, + caplog: pytest.LogCaptureFixture, +) -> None: + """The full chain normalizes or falls back, and warns once.""" + CORE.toolchain = None + config = esp8266.CONFIG_SCHEMA({CONF_BOARD: board}) + assert config[CONF_BOARD] == expected_board + assert CORE.data[KEY_ESP8266][KEY_BOARD] == expected_board + assert CORE.toolchain == expected_toolchain + assert len(caplog.records) == 1 + + +@pytest.mark.parametrize( + ("cli_toolchain", "yaml"), + [(Toolchain.ARDUINO, {}), (None, {CONF_TOOLCHAIN: Toolchain.ARDUINO})], +) +def test_unknown_board_keeps_explicit_toolchain( + cli_toolchain: Toolchain | None, yaml: ConfigType +) -> None: + """A CLI or YAML toolchain is not overridden.""" + CORE.toolchain = cli_toolchain + config = {CONF_BOARD: "my_custom_board", **yaml} + assert _resolve_board(config) is config + + def test_yaml_toolchain_key_resolves() -> None: """The documented `toolchain: arduino` YAML key selects the native path.""" CORE.toolchain = None diff --git a/tests/unit_tests/components/nrf52/test_testing_mode_flash.py b/tests/unit_tests/components/nrf52/test_testing_mode_flash.py new file mode 100644 index 0000000000..7c66cd3361 --- /dev/null +++ b/tests/unit_tests/components/nrf52/test_testing_mode_flash.py @@ -0,0 +1,30 @@ +"""Testing mode fakes a larger flash so grouped component tests link.""" + +from esphome.components import nrf52 +from esphome.components.nrf52.boards import BOOTLOADER_CONFIG +from esphome.components.nrf52.const import BOOTLOADER_ADAFRUIT_NRF52_SD140_V7 +from esphome.components.zephyr import Section + + +def test_testing_mode_sections_move_only_the_bootloader() -> None: + """The bootloader pinned to the end of flash follows the faked end; the + SoftDevice at the start stays.""" + sections = nrf52._testing_mode_sections( + BOOTLOADER_CONFIG[BOOTLOADER_ADAFRUIT_NRF52_SD140_V7] + ) + + assert [(s.name, s.address, s.size) for s in sections] == [ + ("SoftDevice", 0x0, 0x27000), + ( + "Adafruit_nRF52_Bootloader", + nrf52.TESTING_FLASH_SIZE - 0xC000, + 0xC000, + ), + ] + assert sections[1].end_address == nrf52.TESTING_FLASH_SIZE + + +def test_testing_mode_sections_leave_unpinned_sections() -> None: + section = Section("other", 0x50000, 0x1000, "flash_primary") + + assert nrf52._testing_mode_sections([section]) == [section] diff --git a/tests/unit_tests/components/test_audio_http.py b/tests/unit_tests/components/test_audio_http.py new file mode 100644 index 0000000000..94355c09c9 --- /dev/null +++ b/tests/unit_tests/components/test_audio_http.py @@ -0,0 +1,71 @@ +"""Tests for the audio_http media source's ca_certificate_path inheritance.""" + +from pathlib import Path + +from esphome.components.audio_http.media_source import _inherit_ca_certificate_path +from esphome.core import ID +import esphome.final_validate as fv +from esphome.types import ConfigType + + +def _source_config() -> ConfigType: + return {"id": ID("audio_http_source")} + + +def _run(full_config: dict, config: ConfigType) -> ConfigType: + token = fv.full_config.set(full_config) + try: + return _inherit_ca_certificate_path(config) + finally: + fv.full_config.reset(token) + + +def test_explicit_option_wins() -> None: + """A source with its own ca_certificate_path is left untouched.""" + config = {"id": ID("s"), "ca_certificate_path": Path("own.pem")} + result = _run( + {"http_request": {"verify_ssl": True, "ca_certificate_path": Path("hr.pem")}}, + config, + ) + assert result["ca_certificate_path"] == Path("own.pem") + + +def test_inherited_from_http_request() -> None: + """Without its own option, the source picks up http_request's CA path.""" + result = _run( + { + "http_request": { + "verify_ssl": True, + "ca_certificate_path": Path("hr.pem"), + } + }, + _source_config(), + ) + assert result["ca_certificate_path"] == Path("hr.pem") + + +def test_no_http_request_component() -> None: + """No http_request block means no inheritance.""" + result = _run({}, _source_config()) + assert "ca_certificate_path" not in result + + +def test_http_request_without_ca() -> None: + """An http_request block without ca_certificate_path means no inheritance.""" + result = _run({"http_request": {"verify_ssl": True}}, _source_config()) + assert "ca_certificate_path" not in result + + +def test_not_inherited_when_verify_ssl_disabled() -> None: + """verify_ssl: false disables the CA for http_request itself, so the + inheritance must skip it too instead of silently pinning playback.""" + result = _run( + { + "http_request": { + "verify_ssl": False, + "ca_certificate_path": Path("hr.pem"), + } + }, + _source_config(), + ) + assert "ca_certificate_path" not in result diff --git a/tests/unit_tests/components/test_usb_host.py b/tests/unit_tests/components/test_usb_host.py new file mode 100644 index 0000000000..1a1df68f4d --- /dev/null +++ b/tests/unit_tests/components/test_usb_host.py @@ -0,0 +1,111 @@ +"""Tests for usb_host device matching validation.""" + +import pytest + +from esphome.components.usb_host import validate_usb_clients +import esphome.config_validation as cv +from esphome.types import ConfigType + + +@pytest.mark.parametrize( + ("first", "second"), + [ + pytest.param( + {"id": "a", "vid": 0x303A, "pid": 0x4001}, + {"id": "b", "vid": 0x303A, "pid": 0x4002}, + id="different_pid", + ), + pytest.param( + {"id": "a", "vid": 0x303A, "pid": 0x4001}, + {"id": "b", "vid": 0x1A86, "pid": 0x4001}, + id="different_vid", + ), + pytest.param( + {"id": "a", "vid": 0x303A, "pid": 0x4001, "product": "ZBT-2"}, + {"id": "b", "vid": 0x303A, "pid": 0x4001, "product": "ZWA-2"}, + id="different_product", + ), + pytest.param( + { + "id": "a", + "vid": 0x303A, + "pid": 0x4001, + "manufacturer": "Nabu Casa", + "product": "ZBT-2", + }, + { + "id": "b", + "vid": 0x303A, + "pid": 0x4001, + "manufacturer": "Espressif", + "product": "ZBT-2", + }, + id="different_manufacturer", + ), + pytest.param( + {"id": "a", "vid": 0x303A, "pid": 0, "product": "ZBT-2"}, + {"id": "b", "vid": 0x303A, "pid": 0x4001, "product": "ZWA-2"}, + id="wildcard_pid_separated_by_product", + ), + pytest.param( + {"id": "a", "vid": 0, "pid": 0x4001}, + {"id": "b", "vid": 0x303A, "pid": 0x4002}, + id="wildcard_vid_different_pid", + ), + ], +) +def test_disjoint_clients_are_accepted(first: ConfigType, second: ConfigType) -> None: + configs = [first, second] + assert validate_usb_clients(configs) is configs + + +@pytest.mark.parametrize( + ("first", "second"), + [ + pytest.param( + {"id": "a", "vid": 0x303A, "pid": 0x4001}, + {"id": "b", "vid": 0x303A, "pid": 0x4001}, + id="exact_duplicate", + ), + pytest.param( + {"id": "a", "vid": 0x303A, "pid": 0x4001}, + {"id": "b", "vid": 0x303A, "pid": 0x4001, "product": "ZBT-2"}, + id="unfiltered_shadows_filtered", + ), + pytest.param( + {"id": "a", "vid": 0x303A, "pid": 0x4001, "product": "ZBT-2"}, + {"id": "b", "vid": 0x303A, "pid": 0x4001, "product": "ZBT-2"}, + id="identical_filters", + ), + pytest.param( + {"id": "a", "vid": 0, "pid": 0}, + {"id": "b", "vid": 0x303A, "pid": 0x4001, "product": "ZBT-2"}, + id="zero_ids_match_every_device", + ), + pytest.param( + {"id": "a", "vid": 0x303A, "pid": 0}, + {"id": "b", "vid": 0x303A, "pid": 0x4001, "product": "ZBT-2"}, + id="wildcard_pid_shadows_filtered", + ), + pytest.param( + {"id": "a", "vid": 0, "pid": 0x4001}, + {"id": "b", "vid": 0x303A, "pid": 0}, + id="wildcards_on_different_fields", + ), + ], +) +def test_overlapping_clients_are_rejected( + first: ConfigType, second: ConfigType +) -> None: + with pytest.raises(cv.Invalid, match="overlap"): + validate_usb_clients([first, second]) + + +def test_every_pair_is_compared_not_just_neighbours() -> None: + configs = [ + {"id": "a", "vid": 0x303A, "pid": 0x4001, "product": "ZBT-2"}, + {"id": "b", "vid": 0x303A, "pid": 0x4002}, + {"id": "c", "vid": 0x303A, "pid": 0x4001, "product": "ZBT-2"}, + ] + with pytest.raises(cv.Invalid, match="a, c"): + validate_usb_clients(configs) diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index ff5564e815..7835156b01 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -18,6 +18,7 @@ from unittest.mock import Mock, patch import pytest from esphome.core import CORE +from esphome.espidf import toolchain here = Path(__file__).parent @@ -26,6 +27,21 @@ package_root = here.parent.parent sys.path.insert(0, package_root.as_posix()) +@pytest.fixture(autouse=True) +def _no_idf_install(monkeypatch: pytest.MonkeyPatch) -> None: + """Fail any test that would install ESP-IDF for real. + + An unmocked ``_get_idf_env`` reaches ``check_esp_idf_install``, which + downloads the framework on a bare CI runner (minutes on Windows). The + install path's own tests call it through ``esphome.espidf.framework``. + """ + + def refuse(*args: object, **kwargs: object) -> None: + raise AssertionError("test would install ESP-IDF; mock _get_idf_env") + + monkeypatch.setattr(toolchain, "check_esp_idf_install", refuse) + + @pytest.fixture(autouse=True) def reset_core(): """Reset CORE after each test.""" diff --git a/tests/unit_tests/core/test_config.py b/tests/unit_tests/core/test_config.py index 1a36fdc967..883f9d8b3d 100644 --- a/tests/unit_tests/core/test_config.py +++ b/tests/unit_tests/core/test_config.py @@ -447,6 +447,85 @@ def test_device_duplicate_id( assert "ID duplicate_device redefined!" in captured.out +def test_expander_pin_selected_by_address(yaml_file: Callable[[str], str]) -> None: + """A pin provider hub with an omitted id can be selected by its address.""" + result = load_config_from_fixture( + yaml_file, "expander_pin_by_address.yaml", FIXTURES_DIR + ) + assert result is not None + + resolved = result["binary_sensor"][0]["pin"]["xl9535"] + assert resolved.id == "xl9535_b" + # Explicit selection criteria must survive strip_default_ids(), unlike a + # plain omitted-id auto-pick. + assert resolved.is_manual is True + + +def test_expander_pin_selected_by_address_no_match( + yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] +) -> None: + """Selecting an address that matches no hub of that type fails clearly.""" + result = load_config_from_fixture( + yaml_file, "expander_pin_by_address_no_match.yaml", FIXTURES_DIR + ) + assert result is None + + captured = capsys.readouterr() + assert ( + "Couldn't find a 'xl9535::XL9535Component' matching address=0x21. " + "Are you missing a hub declaration, or is the address wrong?" in captured.out + ) + + +def test_expander_pin_selected_by_address_ambiguous( + yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] +) -> None: + """Two hubs sharing the same address (e.g. on different buses) still need an id.""" + result = load_config_from_fixture( + yaml_file, "expander_pin_by_address_ambiguous.yaml", FIXTURES_DIR + ) + assert result is None + + captured = capsys.readouterr() + assert ( + "Multiple 'xl9535::XL9535Component' instances match address=0x20: " + "'xl9535_a', 'xl9535_b'. You must assign an explicit ID to the one you " + "want to use." in captured.out + ) + + +def test_expander_pin_ambiguous_without_match_config( + yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] +) -> None: + """Omitting both id and address with multiple hubs of the same type still + falls back to the original "too many candidates" error.""" + result = load_config_from_fixture( + yaml_file, "expander_pin_ambiguous_no_address.yaml", FIXTURES_DIR + ) + assert result is None + + captured = capsys.readouterr() + assert ( + "Too many candidates found for 'xl9535' type 'xl9535::XL9535Component' " + "Some are 'xl9535_a', 'xl9535_b'" in captured.out + ) + + +def test_expander_pin_reuse_detected_across_reference_syntax( + yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] +) -> None: + """The same physical pin, reached once by explicit id and once by address, + must still be flagged as reused -- reuse detection keys on the resolved + provider id, not on which syntax was used to reference it.""" + result = load_config_from_fixture( + yaml_file, "expander_pin_reuse_across_syntax.yaml", FIXTURES_DIR + ) + assert result is None + + captured = capsys.readouterr() + assert "Pin 5 is used in multiple places" in captured.out + + def test_substitution_with_id( yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] ) -> None: diff --git a/tests/unit_tests/fixtures/core/config/expander_pin_ambiguous_no_address.yaml b/tests/unit_tests/fixtures/core/config/expander_pin_ambiguous_no_address.yaml new file mode 100644 index 0000000000..7b22ad3bea --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/expander_pin_ambiguous_no_address.yaml @@ -0,0 +1,24 @@ +esphome: + name: test + +esp32: + board: esp32dev + +i2c: + sda: GPIO21 + scl: GPIO22 + +xl9535: + - id: xl9535_a + address: 0x20 + - id: xl9535_b + address: 0x21 + +binary_sensor: + - platform: gpio + name: "Expander Pin" + pin: + xl9535: + number: 1 + mode: + input: true diff --git a/tests/unit_tests/fixtures/core/config/expander_pin_by_address.yaml b/tests/unit_tests/fixtures/core/config/expander_pin_by_address.yaml new file mode 100644 index 0000000000..e4cdff9e29 --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/expander_pin_by_address.yaml @@ -0,0 +1,25 @@ +esphome: + name: test + +esp32: + board: esp32dev + +i2c: + sda: GPIO21 + scl: GPIO22 + +xl9535: + - id: xl9535_a + address: 0x20 + - id: xl9535_b + address: 0x21 + +binary_sensor: + - platform: gpio + name: "Expander Pin" + pin: + xl9535: + address: 0x21 + number: 1 + mode: + input: true diff --git a/tests/unit_tests/fixtures/core/config/expander_pin_by_address_ambiguous.yaml b/tests/unit_tests/fixtures/core/config/expander_pin_by_address_ambiguous.yaml new file mode 100644 index 0000000000..b951417c2b --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/expander_pin_by_address_ambiguous.yaml @@ -0,0 +1,31 @@ +esphome: + name: test + +esp32: + board: esp32dev + +i2c: + - id: bus_a + sda: GPIO21 + scl: GPIO22 + - id: bus_b + sda: GPIO18 + scl: GPIO19 + +xl9535: + - id: xl9535_a + i2c_id: bus_a + address: 0x20 + - id: xl9535_b + i2c_id: bus_b + address: 0x20 + +binary_sensor: + - platform: gpio + name: "Expander Pin" + pin: + xl9535: + address: 0x20 + number: 1 + mode: + input: true diff --git a/tests/unit_tests/fixtures/core/config/expander_pin_by_address_no_match.yaml b/tests/unit_tests/fixtures/core/config/expander_pin_by_address_no_match.yaml new file mode 100644 index 0000000000..126f88f31a --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/expander_pin_by_address_no_match.yaml @@ -0,0 +1,23 @@ +esphome: + name: test + +esp32: + board: esp32dev + +i2c: + sda: GPIO21 + scl: GPIO22 + +xl9535: + - id: xl9535_a + address: 0x20 + +binary_sensor: + - platform: gpio + name: "Expander Pin" + pin: + xl9535: + address: 0x21 + number: 1 + mode: + input: true diff --git a/tests/unit_tests/fixtures/core/config/expander_pin_reuse_across_syntax.yaml b/tests/unit_tests/fixtures/core/config/expander_pin_reuse_across_syntax.yaml new file mode 100644 index 0000000000..6a6a90fd34 --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/expander_pin_reuse_across_syntax.yaml @@ -0,0 +1,30 @@ +esphome: + name: test + +esp32: + board: esp32dev + +i2c: + sda: GPIO21 + scl: GPIO22 + +xl9535: + - id: xl9535_a + address: 0x20 + +binary_sensor: + - platform: gpio + name: "Expander Pin By Id" + pin: + xl9535: xl9535_a + number: 5 + mode: + input: true + - platform: gpio + name: "Expander Pin By Address" + pin: + xl9535: + address: 0x20 + number: 5 + mode: + input: true diff --git a/tests/unit_tests/test_automation.py b/tests/unit_tests/test_automation.py index 3a902a429f..ddd777fdb0 100644 --- a/tests/unit_tests/test_automation.py +++ b/tests/unit_tests/test_automation.py @@ -16,10 +16,16 @@ from esphome.automation import ( TriggerForwarder, TriggerOnFalseForwarder, TriggerOnTrueForwarder, + build_callback_automation, build_callback_automations, + build_parent_callback_automation, + build_trigger_automations, + build_trigger_callback, has_non_synchronous_actions, literal_with_length, maybe_simple_id, + parent_ref, + progmem_bytes, register_apply_action, register_apply_condition, register_bare_action, @@ -28,10 +34,12 @@ from esphome.automation import ( register_parented_condition, register_simple_action, register_simple_condition, + string_ref_literal, + templatable_bytes, ) import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ID +from esphome.const import CONF_AUTOMATION_ID, CONF_ID, CONF_THEN from esphome.core import CORE, ID, KEY_CORE, KEY_TARGET_PLATFORM, EsphomeError, Lambda from esphome.cpp_generator import MockObj, RawExpression from esphome.util import Registry, RegistryEntry @@ -325,7 +333,14 @@ async def test_build_callback_automations_single_entry( (CallbackAutomation("on_state", "add_on_state_callback", [(bool, "x")]),), ) mock_build_callback.assert_called_once_with( - parent, "add_on_state_callback", [(bool, "x")], conf, forwarder=None + parent, + "add_on_state_callback", + [(bool, "x")], + conf, + forwarder=None, + params=None, + forward=None, + when=None, ) @@ -345,10 +360,24 @@ async def test_build_callback_automations_multiple_configs( ) assert mock_build_callback.call_count == 2 mock_build_callback.assert_any_call( - parent, "add_on_state_callback", [(bool, "x")], conf1, forwarder=None + parent, + "add_on_state_callback", + [(bool, "x")], + conf1, + forwarder=None, + params=None, + forward=None, + when=None, ) mock_build_callback.assert_any_call( - parent, "add_on_state_callback", [(bool, "x")], conf2, forwarder=None + parent, + "add_on_state_callback", + [(bool, "x")], + conf2, + forwarder=None, + params=None, + forward=None, + when=None, ) @@ -376,9 +405,25 @@ async def test_build_callback_automations_multiple_entries( ) assert mock_build_callback.call_count == 2 assert mock_build_callback.call_args_list == [ - call(parent, "add_on_value_callback", [(float, "x")], conf_a, forwarder=None), call( - parent, "add_on_raw_value_callback", [(float, "x")], conf_b, forwarder=None + parent, + "add_on_value_callback", + [(float, "x")], + conf_a, + forwarder=None, + params=None, + forward=None, + when=None, + ), + call( + parent, + "add_on_raw_value_callback", + [(float, "x")], + conf_b, + forwarder=None, + params=None, + forward=None, + when=None, ), ] @@ -401,7 +446,14 @@ async def test_build_callback_automations_with_forwarder( ), ) mock_build_callback.assert_called_once_with( - parent, "add_on_state_callback", [], conf, forwarder=TriggerOnTrueForwarder + parent, + "add_on_state_callback", + [], + conf, + forwarder=TriggerOnTrueForwarder, + params=None, + forward=None, + when=None, ) @@ -435,7 +487,14 @@ async def test_build_callback_automations_mixed_entries( assert mock_build_callback.call_count == 3 assert mock_build_callback.call_args_list == [ call( - parent, "add_on_state_callback", [(bool, "x")], conf_state, forwarder=None + parent, + "add_on_state_callback", + [(bool, "x")], + conf_state, + forwarder=None, + params=None, + forward=None, + when=None, ), call( parent, @@ -443,6 +502,9 @@ async def test_build_callback_automations_mixed_entries( [], conf_press, forwarder=TriggerOnTrueForwarder, + params=None, + forward=None, + when=None, ), call( parent, @@ -450,6 +512,9 @@ async def test_build_callback_automations_mixed_entries( [], conf_release, forwarder=TriggerOnFalseForwarder, + params=None, + forward=None, + when=None, ), ] @@ -475,7 +540,14 @@ async def test_build_callback_automations_skips_missing_keys( ), ) mock_build_callback.assert_called_once_with( - parent, "add_on_state_callback", [], conf, forwarder=TriggerOnTrueForwarder + parent, + "add_on_state_callback", + [], + conf, + forwarder=TriggerOnTrueForwarder, + params=None, + forward=None, + when=None, ) @@ -493,7 +565,14 @@ async def test_build_callback_automations_defaults( (CallbackAutomation("on_press", "add_on_press_callback"),), ) mock_build_callback.assert_called_once_with( - parent, "add_on_press_callback", [], conf, forwarder=None + parent, + "add_on_press_callback", + [], + conf, + forwarder=None, + params=None, + forward=None, + when=None, ) @@ -510,9 +589,59 @@ class MockCodegen(NamedTuple): new_pvariable: MagicMock register_parented: AsyncMock add_global: MagicMock + add: MagicMock calls: MagicMock # new_pvariable and add_global attached, to check their order +@pytest.fixture +def mock_build_automation() -> Generator[AsyncMock]: + with patch("esphome.automation.build_automation", new_callable=AsyncMock) as mock: + yield mock + + +@pytest.mark.asyncio +async def test_build_trigger_automations_with_parent( + mock_build_automation: AsyncMock, +) -> None: + """Each entry's Trigger class is instantiated with the parent and built with its args.""" + parent = MockObj("var", "->") + on_conf = {"trigger_id": ID("trig_1"), "then": []} + set_conf = {"trigger_id": ID("trig_2"), "then": []} + config = {"on_turn_on": [on_conf], "on_speed_set": [set_conf]} + with patch("esphome.codegen.new_Pvariable") as new_pvariable: + new_pvariable.side_effect = lambda id_, *args: MockObj(str(id_), "->") + await build_trigger_automations( + parent, + config, + ( + ("on_turn_on", []), + ("on_turn_off", []), + ("on_speed_set", [(cg.int_, "x")]), + ), + ) + assert [c.args for c in new_pvariable.call_args_list] == [ + (ID("trig_1"), parent), + (ID("trig_2"), parent), + ] + calls = mock_build_automation.call_args_list + assert [(str(c.args[0]), c.args[1], c.args[2]) for c in calls] == [ + ("trig_1", [], on_conf), + ("trig_2", [(cg.int_, "x")], set_conf), + ] + + +@pytest.mark.asyncio +async def test_build_trigger_automations_without_parent( + mock_build_automation: AsyncMock, +) -> None: + """A None parent instantiates the Trigger class with no constructor arguments.""" + conf = {"trigger_id": ID("trig_1"), "then": []} + with patch("esphome.codegen.new_Pvariable") as new_pvariable: + await build_trigger_automations(None, {"on_boot": [conf]}, (("on_boot", []),)) + new_pvariable.assert_called_once_with(ID("trig_1")) + mock_build_automation.assert_awaited_once() + + @pytest.fixture def mock_cg() -> Generator[MockCodegen]: """Patch the codegen calls the shared builders make.""" @@ -523,6 +652,7 @@ def mock_cg() -> Generator[MockCodegen]: "esphome.codegen.register_parented", new_callable=AsyncMock ) as register_parented, patch("esphome.cpp_generator.add_global") as add_global, + patch("esphome.codegen.add") as add, ): get_variable.return_value = PARENT_OBJ new_pvariable.return_value = NEW_OBJ @@ -530,7 +660,7 @@ def mock_cg() -> Generator[MockCodegen]: calls.attach_mock(new_pvariable, "new_pvariable") calls.attach_mock(add_global, "add_global") yield MockCodegen( - get_variable, new_pvariable, register_parented, add_global, calls + get_variable, new_pvariable, register_parented, add_global, add, calls ) @@ -925,6 +1055,26 @@ async def test_apply_literal_with_length_is_plain_on_every_platform( assert "progmem_string" not in text +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("platform", "rendered"), + [("esp32", 'StringRef("ON", 2)'), ("esp8266", 'ESPHOME_F("ON")')], +) +async def test_apply_string_ref_literal_stays_in_flash_on_esp8266( + registries: tuple[Registry, Registry], + mock_cg: MockCodegen, + platform: str, + rendered: str, +) -> None: + fields = ( + ApplyField( + "payload", "set_payload", cg.std_string, const_fn=string_ref_literal + ), + ) + await _run_apply_action(registries, fields, {"payload": "ON"}, platform=platform) + assert f"::{PARENT_OBJ}->set_payload({rendered});" in _apply_definition(mock_cg) + + @pytest.mark.asyncio async def test_register_apply_condition_predicate( registries: tuple[Registry, Registry], mock_cg: MockCodegen @@ -989,3 +1139,165 @@ async def test_apply_condition_string_lambda_paths( text = _apply_definition(mock_cg) assert expected in text assert ("-> std::string {" in text) is called + + +def test_progmem_bytes_shares_equal_payloads() -> None: + CORE.config = {} + a = progmem_bytes("payload", [1, 2]) + b = progmem_bytes("payload", b"\x01\x02") + assert a is b + assert str(progmem_bytes("payload", [])) == "nullptr" + + +@pytest.mark.asyncio +async def test_templatable_bytes_static_payload() -> None: + CORE.config = {} + var = MockObj("act", "->") + await templatable_bytes( + [0xA1, 0x02], [], var.set_code_template, var.set_code_static, "payload" + ) + assert "act->set_code_static(payload, 2);" in CORE.cpp_main_section + + +@pytest.mark.asyncio +async def test_templatable_bytes_lambda_payload() -> None: + CORE.config = {} + var = MockObj("act", "->") + await templatable_bytes( + Lambda("return {0x01, 0x02};"), + [], + var.set_code_template, + var.set_code_static, + "payload", + ) + text = CORE.cpp_main_section + assert "act->set_code_template(" in text + assert "-> std::vector" in text + assert "set_code_static" not in text + + +@pytest.mark.asyncio +async def test_templatable_bytes_rejects_oversized_payload() -> None: + CORE.config = {} + var = MockObj("act", "->") + with pytest.raises(EsphomeError, match="maximum is 65535"): + await templatable_bytes( + [0] * 0x10000, [], var.set_code_template, var.set_code_static, "payload" + ) + + +TRIGGER_CONF = {CONF_AUTOMATION_ID: ID("automation_1"), CONF_THEN: []} + + +def _squash(expr: object) -> str: + """Generated text with its whitespace folded, for one-line assertions.""" + return " ".join(str(expr).split()) + + +def test_parent_ref_is_global_scoped() -> None: + """A parent named through parent_ref cannot be shadowed by a trigger argument.""" + assert ( + str(parent_ref(MockObj("sel", "->")).option_at(RawExpression("i"))) + == "::sel->option_at(i)" + ) + + +@pytest.mark.asyncio +async def test_trigger_callback_forwards_params(mock_cg: MockCodegen) -> None: + """Without forward or when the callback passes its parameters straight to trigger().""" + text = str( + await build_trigger_callback( + [(cg.bool_, "state")], TRIGGER_CONF, [(cg.bool_, "state")] + ) + ) + assert text.startswith("[](const std::remove_cvref_t & state) -> void {") + assert f"::{NEW_OBJ}->trigger(state);" in text + assert "if (" not in text + + +@pytest.mark.asyncio +async def test_trigger_callback_reshapes_and_filters(mock_cg: MockCodegen) -> None: + """A filter returns early, forward picks the trigger args, a string constant is a plain literal.""" + when = ApplyCall("payload == {}", (("payload", cg.std_string),)) + params = [(cg.std_string, "topic"), (cg.std_string, "payload")] + text = _squash( + await build_trigger_callback( + [(cg.std_string, "x")], + {**TRIGGER_CONF, "payload": "hi"}, + params, + forward=["payload"], + when=when, + ) + ) + assert "& topic, const std::remove_cvref_t & payload) -> void" in text + assert f'if (!(payload == "hi")) return; ::{NEW_OBJ}->trigger(payload);' in text + # An absent optional key skips the filter, as for any ApplyCall. + text = str( + await build_trigger_callback( + [(cg.std_string, "x")], TRIGGER_CONF, params, forward=["payload"], when=when + ) + ) + assert "if (" not in text + + +@pytest.mark.asyncio +async def test_trigger_callback_filter_has_no_parent(mock_cg: MockCodegen) -> None: + """A filter type that names {parent} is rejected up front, a trigger callback has none.""" + when = ApplyCall("mode == {}", (("mode", "{parent}::Mode"),)) + with pytest.raises(ValueError, match="names {parent}"): + await build_trigger_callback( + [], {**TRIGGER_CONF, "mode": 1}, [(cg.int_, "mode")], forward=[], when=when + ) + + +@pytest.mark.asyncio +async def test_build_parent_callback_automation(mock_cg: MockCodegen) -> None: + """A no-argument callback registers a lambda that hands the parent to the automation.""" + parent = MockObj("fan", "->") + await build_parent_callback_automation( + parent, + "add_on_state_callback", + (cg.RawExpression("Fan *"), "x"), + TRIGGER_CONF, + ) + assert _squash(mock_cg.add.call_args.args[0]) == ( + f"fan->add_on_state_callback([]() -> void {{ ::{NEW_OBJ}->trigger(::fan); }})" + ) + + +@pytest.mark.asyncio +async def test_build_callback_automation_lambda(mock_cg: MockCodegen) -> None: + """Reshaping keywords switch the registration to the lambda; forwarder cannot join them.""" + parent = MockObj("sel", "->") + await build_callback_automation( + parent, + "add_cb", + [(cg.std_string, "x"), (cg.size_t, "i")], + TRIGGER_CONF, + params=[(cg.size_t, "index")], + forward=[parent_ref(parent).option_at(RawExpression("index")), "index"], + ) + assert _squash(mock_cg.add.call_args.args[0]) == ( + "sel->add_cb([](const std::remove_cvref_t & index) -> void { " + f"::{NEW_OBJ}->trigger(::sel->option_at(index), index); }})" + ) + with pytest.raises(ValueError, match="forwarder"): + await build_callback_automation( + parent, + "add_cb", + [], + TRIGGER_CONF, + forwarder=TriggerOnTrueForwarder, + when="x", + ) + + +@pytest.mark.asyncio +async def test_build_callback_automation_forwarder(mock_cg: MockCodegen) -> None: + """The forwarder path registers a pointer-sized TriggerForwarder, not a lambda.""" + await build_callback_automation( + MockObj("parent", "->"), "add_cb", [(cg.bool_, "x")], TRIGGER_CONF + ) + assert str(mock_cg.add.call_args.args[0]) == ( + f"parent->add_cb(TriggerForwarder{{{NEW_OBJ}}})" + ) diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index 230a8e1f9e..742f07c8de 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -3120,6 +3120,11 @@ def test_rename_key_absent() -> None: assert cv.rename_key("old", "new")({"other": 5}) == {"other": 5} +@pytest.mark.parametrize("value", ["an_id", 1, None, ["old"]]) +def test_rename_key_non_dict_passes_through(value: object) -> None: + assert cv.rename_key("old", "new")(value) == value + + def test_rename_key_no_removed_in_is_silent( caplog: pytest.LogCaptureFixture, ) -> None: diff --git a/tests/unit_tests/test_cpp_generator.py b/tests/unit_tests/test_cpp_generator.py index 06b7c56bae..b6f8fd1ec1 100644 --- a/tests/unit_tests/test_cpp_generator.py +++ b/tests/unit_tests/test_cpp_generator.py @@ -845,3 +845,11 @@ class TestSharedProgmemArray: CORE.register_variable(ID("table", is_declaration=True), cg.MockObj("table")) array = cg.shared_progmem_array("table", ct.uint8, [1]) assert str(array) == "table_2" + + +def test_extern_progmem_array_is_an_externally_linked_global() -> None: + array = cg.extern_progmem_array("esphome::api::API_TABLE", ct.uint8, [1, 2]) + assert str(array) == "esphome::api::API_TABLE" + assert [str(st) for st in CORE.global_statements if "PROGMEM" in str(st)] == [ + "const uint8_t esphome::api::API_TABLE[] PROGMEM = {1, 2};" + ] diff --git a/tests/unit_tests/test_espidf_toolchain.py b/tests/unit_tests/test_espidf_toolchain.py index 620d08fe3b..2a799a4d84 100644 --- a/tests/unit_tests/test_espidf_toolchain.py +++ b/tests/unit_tests/test_espidf_toolchain.py @@ -647,7 +647,7 @@ def test_run_compile_cache_hit_skips_discovery(setup_core: Path) -> None: @contextmanager -def _cache_env(tmp_path: Path, excluded: str) -> Iterator[Path]: +def _cache_env(tmp_path: Path) -> Iterator[Path]: """Patch everything the cache key derives from onto a temp IDF tree and yield that tree's path.""" idf_path = tmp_path / "idf" @@ -655,7 +655,6 @@ def _cache_env(tmp_path: Path, excluded: str) -> Iterator[Path]: with ( patch.object(toolchain, "_get_idf_path", return_value=idf_path), patch.dict(CORE.data, {KEY_ESP32: {KEY_VARIANT: "ESP32"}}), - patch.dict(CORE.cmake_args, {"EXCLUDE_COMPONENTS": excluded}), ): yield idf_path @@ -663,7 +662,7 @@ def _cache_env(tmp_path: Path, excluded: str) -> Iterator[Path]: def test_component_cache_round_trip(setup_core: Path, tmp_path: Path) -> None: """A saved list is read back until it is dropped.""" _setup_build(setup_core) - with _cache_env(tmp_path, "fatfs") as idf_path: + with _cache_env(tmp_path) as idf_path: for name in ("lwip", "esp_timer"): (idf_path / "components" / name).mkdir() assert toolchain.load_cached_builtin_components() is None @@ -673,25 +672,28 @@ def test_component_cache_round_trip(setup_core: Path, tmp_path: Path) -> None: assert toolchain.load_cached_builtin_components() is None -def test_component_cache_misses_on_key_change_or_missing_component( +def test_component_cache_is_per_target_and_drops_missing_component( setup_core: Path, tmp_path: Path ) -> None: - """A different exclusion set uses another entry, an entry naming a + """One entry per target serves every exclusion set, an entry naming a component that no longer exists is ignored, and a custom IDF_PATH is never cached.""" _setup_build(setup_core) - with _cache_env(tmp_path, "fatfs") as idf_path: + with _cache_env(tmp_path) as idf_path: (idf_path / "components" / "lwip").mkdir() toolchain.save_cached_builtin_components(["lwip"]) path = toolchain._builtin_component_cache_path() assert path.parent == idf_path / ".esphome_component_lists" - assert path.name.startswith("esp32-") + assert path.name == "esp32.json" assert toolchain.load_cached_builtin_components() == ["lwip"] with patch.dict(os.environ, {"IDF_PATH": str(idf_path)}): assert toolchain.load_cached_builtin_components() is None - with _cache_env(tmp_path, "fatfs;unity"): - assert toolchain.load_cached_builtin_components() is None - with _cache_env(tmp_path, "fatfs") as idf_path: + with ( + _cache_env(tmp_path), + patch.dict(CORE.cmake_args, {"EXCLUDE_COMPONENTS": "fatfs;unity"}), + ): + assert toolchain.load_cached_builtin_components() == ["lwip"] + with _cache_env(tmp_path) as idf_path: path.write_text(json.dumps(["lwip", "gone"])) assert toolchain.load_cached_builtin_components() is None # A plain file with the right name is not a component directory. @@ -703,7 +705,7 @@ def test_component_cache_save_skips_empty_list_or_custom_idf_path( setup_core: Path, tmp_path: Path ) -> None: _setup_build(setup_core) - with _cache_env(tmp_path, "") as idf_path: + with _cache_env(tmp_path) as idf_path: toolchain.save_cached_builtin_components([]) with patch.dict(os.environ, {"IDF_PATH": str(idf_path)}): toolchain.save_cached_builtin_components(["lwip"]) @@ -715,7 +717,7 @@ def test_component_cache_write_failure_is_logged( ) -> None: _setup_build(setup_core) with ( - _cache_env(tmp_path, ""), + _cache_env(tmp_path), patch.object(toolchain, "write_file", side_effect=EsphomeError("disk full")), ): toolchain.save_cached_builtin_components(["lwip"]) @@ -725,7 +727,7 @@ def test_component_cache_write_failure_is_logged( def test_component_cache_ignores_corrupt_file(setup_core: Path, tmp_path: Path) -> None: _setup_build(setup_core) - with _cache_env(tmp_path, ""): + with _cache_env(tmp_path): path = toolchain._builtin_component_cache_path() path.parent.mkdir(parents=True) path.write_text("{not json") @@ -1519,6 +1521,7 @@ def test_run_reconfigure_flip_into_skip_mode_cleans_up(setup_core: Path) -> None with ( patch.object(toolchain, "_skip_bootloader", return_value=True), patch.object(toolchain, "_get_idf_tool", side_effect=lambda n: f"/tools/{n}"), + patch.object(toolchain, "_get_idf_env", return_value={}), patch.object(toolchain, "_tool_env", return_value={}), patch.object(toolchain, "run_build_tool", return_value=0), patch.object(toolchain, "_idf_py") as mock_idf_py, @@ -1542,6 +1545,7 @@ def test_run_reconfigure_skip_steady_state_cleans_nothing(setup_core: Path) -> N with ( patch.object(toolchain, "_skip_bootloader", return_value=True), patch.object(toolchain, "_get_idf_tool", side_effect=lambda n: f"/tools/{n}"), + patch.object(toolchain, "_get_idf_env", return_value={}), patch.object(toolchain, "_tool_env", return_value={}), patch.object(toolchain, "run_build_tool", return_value=0), patch.object(toolchain, "_idf_py") as mock_idf_py, diff --git a/tests/unit_tests/test_helpers.py b/tests/unit_tests/test_helpers.py index 5bdecf2fd3..764f160cbc 100644 --- a/tests/unit_tests/test_helpers.py +++ b/tests/unit_tests/test_helpers.py @@ -94,6 +94,21 @@ def test_cpp_string_escape(string, expected): assert actual == expected +@pytest.mark.parametrize( + "string, expected", + ( + ("foo", 'u"foo"'), + ("foo\nbar", 'u"foo\\012bar"'), + ("foo\\bar", 'u"foo\\134bar"'), + ('foo "bar"', 'u"foo \\042bar\\042"'), + ("caf\u00e9", 'u"caf\\U000000E9"'), + ("foo ๐Ÿ", 'u"foo \\U0001F40D"'), + ), +) +def test_cpp_u16string_escape(string: str, expected: str) -> None: + assert helpers.cpp_u16string_escape(string) == expected + + @pytest.mark.parametrize( "value, expected", ( @@ -1270,3 +1285,20 @@ def test_get_usable_cpu_count_sources() -> None: mock_os_unknown = types.SimpleNamespace(cpu_count=lambda: None) with patch("esphome.helpers.os", mock_os_unknown): assert helpers.get_usable_cpu_count() == 1 + + +def test_zstd_module_falls_back_to_the_backport( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Before Python 3.14 the standard library has no zstd, so the backport is used.""" + backport = object() + + def import_module(name: str) -> object: + if name == "compression.zstd": + raise ImportError(name) + assert name == "backports.zstd" + return backport + + monkeypatch.setattr(helpers.importlib, "import_module", import_module) + + assert helpers.zstd_module() is backport diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index 4edf6d5fc3..37d8e7486f 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -6705,6 +6705,45 @@ def test_check_permissions_unreadable_port() -> None: check_permissions("/dev/ttyUSB99") +@pytest.mark.parametrize( + ("is_macos", "machine", "warned", "warns"), + [ + pytest.param(True, "x86_64", False, True, id="intel_mac"), + pytest.param(True, "x86_64", True, False, id="intel_mac_child"), + pytest.param(True, "arm64", False, False, id="apple_silicon"), + pytest.param(False, "x86_64", False, False, id="linux_x86_64"), + ], +) +def test_warn_if_intel_macos( + is_macos: bool, + machine: str, + warned: bool, + warns: bool, + monkeypatch: pytest.MonkeyPatch, + caplog: pytest.LogCaptureFixture, +) -> None: + """Only Intel Python on macOS gets the warning, and only once per invocation.""" + monkeypatch.setattr(main, "IS_MACOS", is_macos) + monkeypatch.setattr(main.platform, "machine", lambda: machine) + if warned: + monkeypatch.setenv(main._INTEL_MACOS_WARNED_ENV, "1") + else: + monkeypatch.delenv(main._INTEL_MACOS_WARNED_ENV, raising=False) + + with caplog.at_level(logging.WARNING): + main._warn_if_intel_macos() + + assert (main._INTEL_MACOS_REMOVAL in caplog.text) is warns + + +def test_run_multiple_configs_marks_children_warned(tmp_path: Path) -> None: + """Multi-config children get the env marker so they skip the Intel warning.""" + with patch.object(main, "run_external_process", return_value=0) as mock_run: + main.run_multiple_configs([tmp_path / "a.yaml"], lambda f: ["esphome", str(f)]) + + assert mock_run.call_args.kwargs["env"][main._INTEL_MACOS_WARNED_ENV] == "1" + + def _make_checkout(root: Path) -> Path: """Create a directory that looks like an esphome checkout.""" (root / "esphome").mkdir(parents=True) diff --git a/tests/unit_tests/test_pins.py b/tests/unit_tests/test_pins.py new file mode 100644 index 0000000000..18dc95a5c6 --- /dev/null +++ b/tests/unit_tests/test_pins.py @@ -0,0 +1,110 @@ +"""Unit tests for esphome/pins.py.""" + +import pytest + +from esphome import config_validation as cv, pins +import esphome.codegen as cg +from esphome.const import CONF_ADDRESS +from esphome.core import ID +from esphome.schema_extractors import SCHEMA_EXTRACT + +SomeHub = cg.esphome_ns.class_("SomeHub") + + +def test_use_id_or_address_with_explicit_id() -> None: + """An explicit id string still resolves exactly like cv.use_id.""" + validator = pins.use_id_or_address(SomeHub) + result = validator("my_hub") + + assert isinstance(result, ID) + assert result.id == "my_hub" + assert result.type is SomeHub + assert result.is_declaration is False + assert result.match_config is None + + +def test_use_id_or_address_with_omitted_id() -> None: + """Omitting the id (None) still falls back to the single-instance auto-pick.""" + validator = pins.use_id_or_address(SomeHub) + result = validator(None) + + assert isinstance(result, ID) + assert result.id is None + assert result.type is SomeHub + assert result.match_config is None + + +def test_use_id_or_address_with_address_mapping() -> None: + """A {address: ...} mapping produces an unnamed ID with match_config set.""" + validator = pins.use_id_or_address(SomeHub) + result = validator({CONF_ADDRESS: 0x21}) + + assert isinstance(result, ID) + assert result.id is None + assert result.type is SomeHub + assert result.is_declaration is False + assert result.match_config == {CONF_ADDRESS: 0x21} + + +def test_use_id_or_address_with_string_address() -> None: + """A hex string address is normalized the same way cv.i2c_address does.""" + validator = pins.use_id_or_address(SomeHub) + result = validator({CONF_ADDRESS: "0x21"}) + + assert result.match_config == {CONF_ADDRESS: 0x21} + + +def test_use_id_or_address_mapping_requires_address_key() -> None: + """A mapping without the address key is rejected, not silently ignored.""" + validator = pins.use_id_or_address(SomeHub) + + with pytest.raises(cv.Invalid): + validator({}) + + +def test_use_id_or_address_mapping_rejects_invalid_address() -> None: + """An out-of-range address is still validated by cv.i2c_address.""" + validator = pins.use_id_or_address(SomeHub) + + with pytest.raises(cv.Invalid): + validator({CONF_ADDRESS: 0x1FF}) + + +def test_use_id_or_address_custom_address_key() -> None: + """A custom address_key is honored both in the mapping and match_config.""" + validator = pins.use_id_or_address(SomeHub, address_key="i2c_address") + result = validator({"i2c_address": 0x10}) + + assert result.match_config == {"i2c_address": 0x10} + + +def test_use_id_or_address_marks_address_match_as_manual() -> None: + """The resolved id must survive strip_default_ids(), unlike a plain + omitted-id auto-pick, since the user explicitly gave selection criteria.""" + validator = pins.use_id_or_address(SomeHub) + result = validator({CONF_ADDRESS: 0x21}) + + assert result.is_manual is True + + +def test_use_id_or_address_schema_extract_passthrough() -> None: + """Called with the SCHEMA_EXTRACT sentinel (as build_language_schema.py + does), the wrapper must fall through to cv.use_id's own handling and + return the hub type, not try to treat it as an address mapping.""" + validator = pins.use_id_or_address(SomeHub) + + assert validator(SCHEMA_EXTRACT) is SomeHub + + +def test_use_id_or_address_registers_as_use_id_schema(monkeypatch) -> None: + """The wrapper must be discoverable by build_language_schema.py as a + `use_id` schema (like a bare cv.use_id), or the generated language schema + silently drops hub-id completion for every pin schema using it.""" + from esphome import schema_extractors + + monkeypatch.setattr(schema_extractors, "EnableSchemaExtraction", True) + monkeypatch.setattr(schema_extractors, "hidden_schemas", {}) + + validator = pins.use_id_or_address(SomeHub) + + assert schema_extractors.hidden_schemas.get(repr(validator)) == "use_id" diff --git a/tests/unit_tests/test_platformio_prefetch.py b/tests/unit_tests/test_platformio_prefetch.py index ef573767f7..3f01fb22e6 100644 --- a/tests/unit_tests/test_platformio_prefetch.py +++ b/tests/unit_tests/test_platformio_prefetch.py @@ -661,7 +661,8 @@ def test_registry_jobs_one_bad_spec_keeps_the_rest(tmp_path: Path) -> None: def test_uri_jobs_head_sizes_the_bar(tmp_path: Path) -> None: - """HEAD sizes direct-URL specs; git and unreachable URLs are skipped.""" + """HEAD sizes direct-URL specs; VCS specs skip the download but are + still installable (the pre-install clones them in parallel).""" m = _fake_manager(tmp_path) resp = MagicMock() resp.headers = {"content-length": "2222"} @@ -670,15 +671,24 @@ def test_uri_jobs_head_sizes_the_bar(tmp_path: Path) -> None: m, [ _FakeSpec(uri="https://x/big.zip", name="big", custom_name=True), - _FakeSpec(uri="git+https://x/repo.git", name="repo"), - _FakeSpec(uri="https://x/repo.git#v1", name="barevcs"), + _FakeSpec(uri="git+https://x/repo.git", name="repo", custom_name=True), _FakeSpec(name="registry"), ], set(), ) assert failed == 0 assert [(n, s) for n, s, _ in jobs] == [("big", 2222)] - assert [n for n, _ in installable] == ["big"] + assert [n for n, _ in installable] == ["repo", "big"] + # A derived-name platform archive sized in the same run is trusted too + with patch("esphome.net_retry.http_request", return_value=resp): + jobs, _, installable = pf._uri_jobs( + m, + [_FakeSpec(uri="https://x/fresh.zip", name="fresh")], + set(), + trusted_names=True, + ) + assert [n for n, _, _ in jobs] == ["fresh"] + assert [n for n, _ in installable] == ["fresh"] # a successful HEAD with no Content-Length is a clean skip resp.headers = {} with patch("esphome.net_retry.http_request", return_value=resp): @@ -687,6 +697,53 @@ def test_uri_jobs_head_sizes_the_bar(tmp_path: Path) -> None: ) == ([], 0, []) +def test_uri_jobs_vcs_specs_installable_without_probe(tmp_path: Path) -> None: + """VCS specs never probe the network here (there is no archive); an + uninstalled custom-named one is handed to the pre-install, while + derived names, installed specs, and file/symlink specs are skipped.""" + m = _fake_manager(tmp_path) + with patch("esphome.net_retry.http_request") as mock_head: + jobs, failed, installable = pf._uri_jobs( + m, + [ + _FakeSpec( + uri="git+https://x/tool.git#1.0", name="tool", custom_name=True + ), + _FakeSpec(uri="hg+https://x/old", name="mercurial", custom_name=True), + # A derived name is not the destination dir; pio run clones it + _FakeSpec(uri="git+https://x/derived#v2", name="derived"), + _FakeSpec(uri="file:///local/dir", name="local"), + _FakeSpec(uri="symlink:///local/dir", name="link"), + ], + set(), + ) + mock_head.assert_not_called() + assert (jobs, failed) == ([], 0) + assert [n for n, _ in installable] == ["tool", "mercurial"] + + # trusted_names admits platform packages, which have no custom name + with patch("esphome.net_retry.http_request"): + _, _, installable = pf._uri_jobs( + m, + [_FakeSpec(uri="git+https://x/derived#v2", name="derived")], + set(), + trusted_names=True, + ) + assert [n for n, _ in installable] == ["derived"] + + m.get_package.return_value = object() # already installed: warm and silent + with patch("esphome.net_retry.http_request"): + assert pf._uri_jobs( + m, + [ + _FakeSpec( + uri="git+https://x/tool.git#1.0", name="tool", custom_name=True + ) + ], + set(), + ) == ([], 0, []) + + def test_uri_jobs_head_failure_counts_as_unresolved( tmp_path: Path, caplog: pytest.LogCaptureFixture ) -> None: @@ -750,6 +807,11 @@ def test_uri_jobs_skips_installed_cached_and_seen(tmp_path: Path) -> None: jobs, failed, installable = pf._uri_jobs(m, spec, set()) assert (jobs, failed) == ([], 0) assert [n for n, _ in installable] == ["a"] + # a cached platform archive with a derived name is trusted the same way + derived = [_FakeSpec(uri="https://x/a.zip", name="a")] + assert pf._uri_jobs(m, derived, set())[2] == [] + _, _, installable = pf._uri_jobs(m, derived, set(), trusted_names=True) + assert [n for n, _ in installable] == ["a"] dl.unlink() # a registry job already claimed this download path assert pf._uri_jobs(m, spec, {str(dl)}) == ([], 0, []) @@ -875,6 +937,44 @@ def test_dependency_entries_filter_seen_names(tmp_path: Path) -> None: assert pf._dependency_entries(m, [("top@1", _FakeSpec(name="top"))], {"dep"}) == [] +def test_preinstall_widens_the_pool_for_clones( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """Network-bound clones all run at once even when the CPU-sized + extraction pool would serialize them; the barrier deadlocks otherwise.""" + m = _fake_manager(tmp_path) + monkeypatch.setattr(pf, "extract_workers", lambda jobs=None: 1) + barrier = threading.Barrier(3) + m._install.side_effect = lambda spec, **kw: barrier.wait(timeout=5) + pf._preinstall( + m, + [ + (f"g{i}", _FakeSpec(uri=f"git+https://x/g{i}.git", name=f"g{i}")) + for i in range(3) + ], + ) + assert barrier.broken is False + + +def test_preinstall_orders_clones_before_extractions( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """A clone must not queue behind CPU-bound archive extractions.""" + m = _fake_manager(tmp_path) + monkeypatch.setattr(pf, "extract_workers", lambda jobs=None: 1) + calls: list[str] = [] + m._install.side_effect = lambda spec, **kw: calls.append(spec.name) + pf._preinstall( + m, + [ + ("a@1", _FakeSpec(name="a")), + ("b@1", _FakeSpec(name="b")), + ("g@1", _FakeSpec(uri="git+https://x/g.git", name="g")), + ], + ) + assert calls[0] == "g" + + def test_preinstall_cleanup_cannot_displace_the_inflight_error( tmp_path: Path, caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch ) -> None: @@ -1890,3 +1990,9 @@ def test_platformio_private_api_contract() -> None: derived = PackageSpec("https://x/y/archive/master.zip") assert derived.name and not derived.has_custom_name() assert PackageSpec("Foo=https://x/y/archive/master.zip").has_custom_name() + # _is_vcs_spec_uri relies on bare .git URLs normalizing to git+, on + # both parse paths (raw string, and requirements= for platform tools) + assert PackageSpec("https://github.com/x/y.git#v1").uri.startswith("git+") + assert PackageSpec( + owner="o", name="tool-x", requirements="https://github.com/x/y.git" + ).uri.startswith("git+")