diff --git a/esphome/core/config.py b/esphome/core/config.py index 00b36e7899..641c73a292 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -125,22 +125,12 @@ def validate_ids_and_references(config: ConfigType) -> ConfigType: # Validate device hash collisions and area references device_hashes: dict[int, str] = {} - for i, device in enumerate(config[CONF_DEVICES]): + for device in config[CONF_DEVICES]: device_id: core.ID = device[CONF_ID] check_hash_collision( device_id, device_hashes, "Device", [CONF_DEVICES, device_id.id] ) - # Validate area_id reference if present - if CONF_AREA_ID in device: - area_ref_id: core.ID = device[CONF_AREA_ID] - if area_ref_id.id not in area_ids: - raise cv.Invalid( - f"Device '{device[CONF_NAME]}' has an area_id '{area_ref_id.id}'" - " that does not exist.", - path=[CONF_DEVICES, i, CONF_AREA_ID], - ) - return config @@ -200,12 +190,16 @@ DEVICE_SCHEMA = cv.Schema( ) +def validate_area_config(config: dict | str) -> dict[str, str | core.ID]: + return cv.maybe_simple_value(AREA_SCHEMA, key=CONF_NAME)(config) + + CONFIG_SCHEMA = cv.All( cv.Schema( { cv.Required(CONF_NAME): cv.valid_name, cv.Optional(CONF_FRIENDLY_NAME, ""): cv.string, - cv.Optional(CONF_AREA): cv.maybe_simple_value(AREA_SCHEMA, key=CONF_NAME), + cv.Optional(CONF_AREA): validate_area_config, cv.Optional(CONF_COMMENT): cv.string, cv.Required(CONF_BUILD_PATH): cv.string, cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema( @@ -260,9 +254,12 @@ CONFIG_SCHEMA = cv.All( } ), validate_hostname, - validate_ids_and_references, ) + +FINAL_VALIDATE_SCHEMA = cv.All(validate_ids_and_references) + + PRELOAD_CONFIG_SCHEMA = cv.Schema( { cv.Required(CONF_NAME): cv.valid_name, diff --git a/tests/unit_tests/core/test_config.py b/tests/unit_tests/core/test_config.py index 6a28925dd3..372c1df7ee 100644 --- a/tests/unit_tests/core/test_config.py +++ b/tests/unit_tests/core/test_config.py @@ -7,7 +7,7 @@ from unittest.mock import patch import pytest -from esphome import config, config_validation as cv, yaml_util +from esphome import config, config_validation as cv, core, yaml_util from esphome.config import Config from esphome.const import CONF_AREA, CONF_AREAS, CONF_DEVICES from esphome.core import CORE @@ -67,8 +67,9 @@ def test_validate_area_config_with_string() -> None: assert "id" in result assert "name" in result assert result["name"] == "Living Room" - # ID should be based on slugified name - assert result["id"].id == "living_room" + assert isinstance(result["id"], core.ID) + assert result["id"].is_declaration + assert not result["id"].is_manual def test_validate_area_config_with_dict() -> None: @@ -157,10 +158,9 @@ def test_legacy_string_area( area = esphome_config[CONF_AREA] assert isinstance(area, dict) assert area["name"] == "Living Room" - assert area["id"].id == "living_room" - - # Check for deprecation warning - assert "Using 'area' as a string is deprecated" in caplog.text + assert isinstance(area["id"], core.ID) + assert area["id"].is_declaration + assert not area["id"].is_manual def test_area_id_collision( @@ -205,8 +205,9 @@ def test_device_with_invalid_area_id( # Check for the specific error message in stdout captured = capsys.readouterr() + print(captured.out) assert ( - "Device 'Test Device' has an area_id 'nonexistent_area' that does not exist." + "Couldn't find ID 'nonexistent_area'. Please check you have defined an ID with that name in your configuration." in captured.out )