diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 99700efa0f8..ee185190960 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -227,11 +227,17 @@ message AreaInfo { string name = 2 [(max_data_length) = 120, (force) = true]; } +enum DeviceRelation { + DEVICE_RELATION_VIA = 0; + DEVICE_RELATION_CHILD = 1; +} + message DeviceInfo { uint32 device_id = 1; // max_data_length matches core/config.FRIENDLY_NAME_MAX_LEN via DEVICE_SCHEMA string name = 2 [(max_data_length) = 120, (force) = true]; uint32 area_id = 3; + DeviceRelation relation = 4; } enum SerialProxyPortType { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 9ff62b16032..73fe9e858a5 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -24,6 +24,7 @@ #endif #include "esphome/components/network/util.h" #include "esphome/core/application.h" +#include "esphome/core/device.h" #include "esphome/core/entity_base.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" @@ -2055,6 +2056,7 @@ bool APIConnection::send_device_info_response_() { device_info.device_id = device->get_device_id(); device_info.name = StringRef(device->get_name()); device_info.area_id = device->get_area_id(); + device_info.relation = static_cast(device->get_relation()); } #endif #ifdef USE_AREAS diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 68660ea901a..cf4314fad85 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -85,6 +85,7 @@ uint8_t *DeviceInfo::encode_msg(const void *self, uint8_t *__restrict__ pos PROT 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); + pos = ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 4, static_cast(msg.relation)); return pos; } uint32_t DeviceInfo::calc_size_msg(const void *self) { @@ -93,6 +94,7 @@ uint32_t DeviceInfo::calc_size_msg(const void *self) { size += ProtoSize::calc_uint32(1, msg.device_id); size += 2 + msg.name.size(); size += ProtoSize::calc_uint32(1, msg.area_id); + size += msg.relation ? 2 : 0; return size; } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 5ff014267d3..456b5a64610 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -19,6 +19,10 @@ enum DisconnectReason : uint32_t { DISCONNECT_REASON_UNSPECIFIED = 0, DISCONNECT_REASON_PROVISIONING_CLOSED = 1, }; +enum DeviceRelation : uint32_t { + DEVICE_RELATION_VIA = 0, + DEVICE_RELATION_CHILD = 1, +}; enum SerialProxyPortType : uint32_t { SERIAL_PROXY_PORT_TYPE_TTL = 0, SERIAL_PROXY_PORT_TYPE_RS232 = 1, @@ -567,6 +571,7 @@ class DeviceInfo final : public ProtoMessage { uint32_t device_id{0}; StringRef name{}; uint32_t area_id{0}; + enums::DeviceRelation relation{}; 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); diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 0ef853f8eff..7a4d42464c6 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -154,6 +154,16 @@ template<> const char *proto_enum_to_string(enums::Disc return ESPHOME_PSTR("UNKNOWN"); } } +template<> const char *proto_enum_to_string(enums::DeviceRelation value) { + switch (value) { + case enums::DEVICE_RELATION_VIA: + return ESPHOME_PSTR("DEVICE_RELATION_VIA"); + case enums::DEVICE_RELATION_CHILD: + return ESPHOME_PSTR("DEVICE_RELATION_CHILD"); + default: + return ESPHOME_PSTR("UNKNOWN"); + } +} template<> const char *proto_enum_to_string(enums::SerialProxyPortType value) { switch (value) { case enums::SERIAL_PROXY_PORT_TYPE_TTL: @@ -1005,6 +1015,7 @@ const char *DeviceInfo::dump_to(DumpBuffer &out) const { dump_field(out, ESPHOME_PSTR("device_id"), this->device_id); dump_field(out, ESPHOME_PSTR("name"), this->name); dump_field(out, ESPHOME_PSTR("area_id"), this->area_id); + dump_field(out, ESPHOME_PSTR("relation"), static_cast(this->relation)); return out.c_str(); } #endif diff --git a/esphome/core/config.py b/esphome/core/config.py index 667d780f71f..45fe3ca0a94 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -69,6 +69,7 @@ from esphome.helpers import ( from esphome.types import ConfigType CONF_LOOP_INTERVAL = "loop_interval" +CONF_RELATION = "relation" CONF_SUSPEND_LOOP = "suspend_loop" _LOGGER = logging.getLogger(__name__) @@ -118,6 +119,17 @@ ProjectUpdateTrigger = cg.esphome_ns.class_( "ProjectUpdateTrigger", cg.Component, automation.Trigger.template(cg.std_string) ) Device = cg.esphome_ns.class_("Device") +DeviceRelation = cg.esphome_ns.enum("DeviceRelation", is_class=True) +DEVICE_RELATIONS: dict[str, cg.MockObj] = DeviceRelation.options( + "via", "child", prefix="DEVICE_RELATION_" +) +# Relation the C++ getter and field use when nothing sets one, and the API's default +DEFAULT_DEVICE_RELATION: str = "via" +# Define for a relation shared by every device +SHARED_DEVICE_RELATION_DEFINES: dict[str, str | None] = { + DEFAULT_DEVICE_RELATION: None, + "child": "USE_DEVICE_RELATION_ALL_CHILD", +} Area = cg.esphome_ns.class_("Area") VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"} @@ -295,6 +307,7 @@ DEVICE_SCHEMA = cv.Schema( cv.string_no_slash, cv.ByteLength(max=FRIENDLY_NAME_MAX_LEN) ), cv.Optional(CONF_AREA_ID): cv.use_id(Area), + cv.Required(CONF_RELATION): cv.enum(DEVICE_RELATIONS, lower=True), } ) @@ -943,6 +956,15 @@ async def to_code(config: ConfigType) -> None: cg.add_define("USE_DEVICES") cg.add_define("ESPHOME_DEVICE_COUNT", len(devices)) + # When every device shares one relation the getter returns it as a constant, + # so only a mix of relations needs a per-device field + relations: set[str] = {dev_conf[CONF_RELATION] for dev_conf in devices} + mixed_relations: bool = len(relations) > 1 + if mixed_relations: + cg.add_define("USE_DEVICE_RELATION") + elif (shared_define := SHARED_DEVICE_RELATION_DEFINES[relations.pop()]) is not None: + cg.add_define(shared_define) + # Process each device for dev_conf in devices: device_id: core.ID = dev_conf[CONF_ID] @@ -959,6 +981,13 @@ async def to_code(config: ConfigType) -> None: area_id_hash = fnv1a_32bit_hash(area_id.id) cg.add(dev.set_area_id(area_id_hash)) + # The field only exists for a mix of relations, and starts as VIA + if ( + mixed_relations + and (relation := dev_conf[CONF_RELATION]) != DEFAULT_DEVICE_RELATION + ): + cg.add(dev.set_relation(relation)) + cg.add(cg.App.register_device(dev)) diff --git a/esphome/core/defines.h b/esphome/core/defines.h index e432620f050..d5a776cc28a 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -70,6 +70,7 @@ #define USE_DEEP_SLEEP #define USE_DEEP_SLEEP_ON_WAKE #define USE_DEVICES +#define USE_DEVICE_RELATION #define USE_DFROBOT_SEN0465_TEMPERATURE_COMPENSATION #define DFROBOT_SEN0465_COMPENSATION_RANGES 3 #define USE_DISPLAY diff --git a/esphome/core/device.h b/esphome/core/device.h index 3d0d1e7c23b..29101d7f2d3 100644 --- a/esphome/core/device.h +++ b/esphome/core/device.h @@ -1,7 +1,19 @@ #pragma once +#include + +#include "esphome/core/defines.h" + namespace esphome { +/// How a sub-device relates to the node that exposes it. Values match the API's DeviceRelation enum. +enum class DeviceRelation : uint8_t { + /// A separate physical device reached through this node (for example over RF, UART or ESP-NOW). + DEVICE_RELATION_VIA = 0, + /// A logical part of this node (for example one zone of a multi-zone controller). + DEVICE_RELATION_CHILD = 1, +}; + class Device { public: void set_device_id(uint32_t device_id) { this->device_id_ = device_id; } @@ -10,11 +22,23 @@ class Device { const char *get_name() { return this->name_; } void set_area_id(uint32_t area_id) { this->area_id_ = area_id; } uint32_t get_area_id() { return this->area_id_; } +#if defined(USE_DEVICE_RELATION) + // Only emitted when devices have different relations; otherwise the getter is a constant and costs no RAM. + void set_relation(DeviceRelation relation) { this->relation_ = relation; } + DeviceRelation get_relation() const { return this->relation_; } +#elif defined(USE_DEVICE_RELATION_ALL_CHILD) + DeviceRelation get_relation() const { return DeviceRelation::DEVICE_RELATION_CHILD; } +#else + DeviceRelation get_relation() const { return DeviceRelation::DEVICE_RELATION_VIA; } +#endif protected: uint32_t device_id_{}; uint32_t area_id_{}; const char *name_ = ""; +#ifdef USE_DEVICE_RELATION + DeviceRelation relation_{DeviceRelation::DEVICE_RELATION_VIA}; +#endif }; } // namespace esphome diff --git a/tests/benchmarks/components/api/benchmark.yaml b/tests/benchmarks/components/api/benchmark.yaml index e57276ea660..f0be642df73 100644 --- a/tests/benchmarks/components/api/benchmark.yaml +++ b/tests/benchmarks/components/api/benchmark.yaml @@ -49,63 +49,83 @@ esphome: - id: device_1 name: "Device 1" area_id: area_1 + relation: via - id: device_2 name: "Device 2" area_id: area_2 + relation: via - id: device_3 name: "Device 3" area_id: area_3 + relation: via - id: device_4 name: "Device 4" area_id: area_4 + relation: via - id: device_5 name: "Device 5" area_id: area_5 + relation: via - id: device_6 name: "Device 6" area_id: area_6 + relation: via - id: device_7 name: "Device 7" area_id: area_7 + relation: via - id: device_8 name: "Device 8" area_id: area_8 + relation: via - id: device_9 name: "Device 9" area_id: area_9 + relation: via - id: device_10 name: "Device 10" area_id: area_10 + relation: via - id: device_11 name: "Device 11" area_id: area_11 + relation: via - id: device_12 name: "Device 12" area_id: area_12 + relation: via - id: device_13 name: "Device 13" area_id: area_13 + relation: via - id: device_14 name: "Device 14" area_id: area_14 + relation: via - id: device_15 name: "Device 15" area_id: area_15 + relation: via - id: device_16 name: "Device 16" area_id: area_16 + relation: via - id: device_17 name: "Device 17" area_id: area_17 + relation: via - id: device_18 name: "Device 18" area_id: area_18 + relation: via - id: device_19 name: "Device 19" area_id: area_19 + relation: via - id: device_20 name: "Device 20" area_id: area_20 + relation: via api: encryption: diff --git a/tests/component_tests/api/test_wizard.py b/tests/component_tests/api/test_wizard.py index 77551e787ab..0048657f579 100644 --- a/tests/component_tests/api/test_wizard.py +++ b/tests/component_tests/api/test_wizard.py @@ -686,6 +686,7 @@ DEVICES_HEADER = """ devices: - id: kitchen_dev name: Kitchen + relation: via esp32: variant: esp32 diff --git a/tests/components/esphome/common.yaml b/tests/components/esphome/common.yaml index fd3d6a0efec..cb897f70c55 100644 --- a/tests/components/esphome/common.yaml +++ b/tests/components/esphome/common.yaml @@ -37,11 +37,14 @@ esphome: - id: other_device name: Another device area_id: another_area + relation: child - id: test_device name: Test device in main area area_id: testing_area # Reference the main area (not in areas) + relation: child - id: no_area_device name: Device without area # This device has no area_id + relation: child binary_sensor: - platform: template diff --git a/tests/integration/README.md b/tests/integration/README.md index 75a52b3ac8f..c7ffbd87577 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -351,6 +351,7 @@ esphome: - id: my_device name: "Test Device" area_id: living_room + relation: via # API services api: diff --git a/tests/integration/fixtures/areas_and_devices.yaml b/tests/integration/fixtures/areas_and_devices.yaml index 08b02e6e1e0..445b878977e 100644 --- a/tests/integration/fixtures/areas_and_devices.yaml +++ b/tests/integration/fixtures/areas_and_devices.yaml @@ -15,15 +15,19 @@ esphome: - id: light_controller_device name: Light Controller area_id: living_room_area # Uses top-level area + relation: via - id: temp_sensor_device name: Temperature Sensor area_id: bedroom_area + relation: via - id: motion_detector_device name: Motion Detector area_id: living_room_area # Reuses top-level area + relation: child - id: smart_switch_device name: Smart Switch area_id: kitchen_area + relation: child host: api: diff --git a/tests/integration/fixtures/device_id_in_state.yaml b/tests/integration/fixtures/device_id_in_state.yaml index a5dfb7ee45c..4e6f06e40e1 100644 --- a/tests/integration/fixtures/device_id_in_state.yaml +++ b/tests/integration/fixtures/device_id_in_state.yaml @@ -11,12 +11,15 @@ esphome: - id: temperature_monitor name: Temperature Monitor area_id: living_room + relation: via - id: humidity_monitor name: Humidity Monitor area_id: bedroom + relation: via - id: motion_sensor name: Motion Sensor area_id: living_room + relation: via host: api: diff --git a/tests/integration/fixtures/duplicate_entities_on_different_devices.yaml b/tests/integration/fixtures/duplicate_entities_on_different_devices.yaml index ecc502ad280..5f9d9fbba0e 100644 --- a/tests/integration/fixtures/duplicate_entities_on_different_devices.yaml +++ b/tests/integration/fixtures/duplicate_entities_on_different_devices.yaml @@ -4,10 +4,13 @@ esphome: devices: - id: controller_1 name: Controller 1 + relation: via - id: controller_2 name: Controller 2 + relation: via - id: controller_3 name: Controller 3 + relation: via host: api: # Port will be automatically injected diff --git a/tests/integration/fixtures/multi_device_preferences.yaml b/tests/integration/fixtures/multi_device_preferences.yaml index 01e4394559b..c036789f168 100644 --- a/tests/integration/fixtures/multi_device_preferences.yaml +++ b/tests/integration/fixtures/multi_device_preferences.yaml @@ -4,8 +4,10 @@ esphome: devices: - id: device_a name: Device A + relation: via - id: device_b name: Device B + relation: via host: api: # Port will be automatically injected diff --git a/tests/integration/fixtures/object_id_api_verification.yaml b/tests/integration/fixtures/object_id_api_verification.yaml index 386270fc2c1..877bf40f160 100644 --- a/tests/integration/fixtures/object_id_api_verification.yaml +++ b/tests/integration/fixtures/object_id_api_verification.yaml @@ -8,8 +8,10 @@ esphome: devices: - id: sub_device_1 name: Sub Device One + relation: via - id: sub_device_2 name: Sub Device Two + relation: via host: diff --git a/tests/integration/fixtures/varint_five_byte_device_id.yaml b/tests/integration/fixtures/varint_five_byte_device_id.yaml index 08259869ca7..d0164e871d3 100644 --- a/tests/integration/fixtures/varint_five_byte_device_id.yaml +++ b/tests/integration/fixtures/varint_five_byte_device_id.yaml @@ -9,9 +9,11 @@ esphome: - id: sub_device_one name: Sub Device One area_id: test_area + relation: via - id: sub_device_two name: Sub Device Two area_id: test_area + relation: via host: api: diff --git a/tests/integration/test_areas_and_devices.py b/tests/integration/test_areas_and_devices.py index 7455ec7428d..9d79f682c7b 100644 --- a/tests/integration/test_areas_and_devices.py +++ b/tests/integration/test_areas_and_devices.py @@ -69,6 +69,11 @@ async def test_areas_and_devices( "Smart Switch should be in Kitchen" ) + # TODO: once aioesphomeapi with SubDeviceInfo.relation is pinned + # (esphome/aioesphomeapi#1955), assert the relations: Light Controller + # and Temperature Sensor VIA, Motion Detector and Smart Switch + # CHILD. The fixture mixes them so the per-device field path is built. + # Verify suggested_area is set to the top-level area name assert device_info.suggested_area == "Living Room", ( f"Expected suggested_area to be 'Living Room', got '{device_info.suggested_area}'" diff --git a/tests/unit_tests/core/test_config.py b/tests/unit_tests/core/test_config.py index bb565564c6e..743c6e12207 100644 --- a/tests/unit_tests/core/test_config.py +++ b/tests/unit_tests/core/test_config.py @@ -28,7 +28,10 @@ from esphome.const import ( ) from esphome.core import CORE, KEY_CONTROLLER_REGISTRY_CONTROLLERS, config from esphome.core.config import ( + CONF_RELATION, CONF_SUSPEND_LOOP, + DEVICE_RELATIONS, + SHARED_DEVICE_RELATION_DEFINES, Area, make_app_name_cpp, preload_core_config, @@ -364,6 +367,83 @@ def test_device_without_area(yaml_file: Callable[[str], str]) -> None: assert "area_id" not in device +def test_device_relation(yaml_file: Callable[[str], str]) -> None: + """Test that device relation accepts via and child in any case.""" + result = load_config_from_fixture(yaml_file, "device_relation.yaml", FIXTURES_DIR) + assert result is not None + + devices = result[CONF_ESPHOME][CONF_DEVICES] + assert [device[CONF_RELATION] for device in devices] == ["via", "via", "child"] + + +def test_device_without_relation( + yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] +) -> None: + """Test that a device without a relation fails validation.""" + result = load_config_from_fixture( + yaml_file, "device_missing_relation.yaml", FIXTURES_DIR + ) + assert result is None + + captured = capsys.readouterr() + assert "'relation' is a required option" in captured.out + + +def test_device_with_invalid_relation( + yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] +) -> None: + """Test that an unknown device relation fails validation.""" + result = load_config_from_fixture( + yaml_file, "device_invalid_relation.yaml", FIXTURES_DIR + ) + assert result is None + + captured = capsys.readouterr() + assert "Unknown value 'parent', valid options are 'via', 'child'" in captured.out + + +def test_shared_device_relation_defines_cover_all_relations() -> None: + """Every relation needs an entry, so a new one cannot silently report VIA.""" + assert SHARED_DEVICE_RELATION_DEFINES.keys() == DEVICE_RELATIONS.keys() + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("fixture", "expected_relations", "expected_define"), + [ + ("device_relation.yaml", ["DEVICE_RELATION_CHILD"], "USE_DEVICE_RELATION"), + ("device_child_only.yaml", [], "USE_DEVICE_RELATION_ALL_CHILD"), + ("device_via_only.yaml", [], None), + ], +) +async def test_device_relation_to_code( + yaml_file: Callable[[str], Path], + fixture: str, + expected_relations: list[str], + expected_define: str | None, +) -> None: + """A shared relation becomes a define; only a mix needs per-device setters.""" + result = load_config_from_fixture(yaml_file, fixture, FIXTURES_DIR) + assert result is not None + + with patch("esphome.core.config.cg") as mock_cg: + mock_cg.RawStatement.side_effect = lambda *args, **kwargs: MagicMock() + mock_cg.RawExpression.side_effect = lambda *args, **kwargs: MagicMock() + await config.to_code(result[CONF_ESPHOME]) + + set_relation = mock_cg.new_Pvariable.return_value.set_relation + assert [ + str(c.args[0].enum_value).rsplit("::", 1)[-1] + for c in set_relation.call_args_list + ] == expected_relations + relation_defines = [ + c.args[0] + for c in mock_cg.add_define.call_args_list + if c.args[0].startswith("USE_DEVICE_RELATION") + ] + assert relation_defines == ([expected_define] if expected_define else []) + + def test_device_with_invalid_area_id( yaml_file: Callable[[str], str], capsys: pytest.CaptureFixture[str] ) -> None: diff --git a/tests/unit_tests/fixtures/core/config/device_child_only.yaml b/tests/unit_tests/fixtures/core/config/device_child_only.yaml new file mode 100644 index 00000000000..0fb66adb710 --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/device_child_only.yaml @@ -0,0 +1,11 @@ +esphome: + name: test-device-child-only + devices: + - id: zone_1_device + name: Zone 1 + relation: child + - id: zone_2_device + name: Zone 2 + relation: child + +host: diff --git a/tests/unit_tests/fixtures/core/config/device_duplicate_id.yaml b/tests/unit_tests/fixtures/core/config/device_duplicate_id.yaml index 2aa30556862..5ae2f03c33e 100644 --- a/tests/unit_tests/fixtures/core/config/device_duplicate_id.yaml +++ b/tests/unit_tests/fixtures/core/config/device_duplicate_id.yaml @@ -3,8 +3,10 @@ esphome: devices: - id: duplicate_device name: "Device 1" + relation: via - id: duplicate_device name: "Device 2" + relation: via esp32: board: esp32dev diff --git a/tests/unit_tests/fixtures/core/config/device_id_collision.yaml b/tests/unit_tests/fixtures/core/config/device_id_collision.yaml index 9cf04e0595c..cb5280faec1 100644 --- a/tests/unit_tests/fixtures/core/config/device_id_collision.yaml +++ b/tests/unit_tests/fixtures/core/config/device_id_collision.yaml @@ -3,8 +3,10 @@ esphome: devices: - id: test_2258 name: "Device 1" + relation: via - id: d6ka name: "Device 2" + relation: via esp32: board: esp32dev diff --git a/tests/unit_tests/fixtures/core/config/device_invalid_area.yaml b/tests/unit_tests/fixtures/core/config/device_invalid_area.yaml index 9a8ec0a1eb8..61c5a2c5d1d 100644 --- a/tests/unit_tests/fixtures/core/config/device_invalid_area.yaml +++ b/tests/unit_tests/fixtures/core/config/device_invalid_area.yaml @@ -7,6 +7,7 @@ esphome: - id: test_device name: "Test Device" area_id: nonexistent_area + relation: via esp32: board: esp32dev diff --git a/tests/unit_tests/fixtures/core/config/device_invalid_relation.yaml b/tests/unit_tests/fixtures/core/config/device_invalid_relation.yaml new file mode 100644 index 00000000000..e2a2f7abba3 --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/device_invalid_relation.yaml @@ -0,0 +1,8 @@ +esphome: + name: test-device-invalid-relation + devices: + - id: test_device + name: Test Device + relation: parent + +host: diff --git a/tests/unit_tests/fixtures/core/config/device_missing_relation.yaml b/tests/unit_tests/fixtures/core/config/device_missing_relation.yaml new file mode 100644 index 00000000000..30454a9b3a6 --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/device_missing_relation.yaml @@ -0,0 +1,7 @@ +esphome: + name: test-device-missing-relation + devices: + - id: test_device + name: Test Device + +host: diff --git a/tests/unit_tests/fixtures/core/config/device_relation.yaml b/tests/unit_tests/fixtures/core/config/device_relation.yaml new file mode 100644 index 00000000000..81955b8b027 --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/device_relation.yaml @@ -0,0 +1,14 @@ +esphome: + name: test-device-relation + devices: + - id: modbus_meter_device + name: Modbus Meter + relation: via + - id: rf_sensor_device + name: RF Sensor + relation: via + - id: zone_device + name: Zone 1 + relation: CHILD + +host: diff --git a/tests/unit_tests/fixtures/core/config/device_via_only.yaml b/tests/unit_tests/fixtures/core/config/device_via_only.yaml new file mode 100644 index 00000000000..e020a65e333 --- /dev/null +++ b/tests/unit_tests/fixtures/core/config/device_via_only.yaml @@ -0,0 +1,11 @@ +esphome: + name: test-device-via-only + devices: + - id: modbus_meter_device + name: Modbus Meter + relation: via + - id: rf_sensor_device + name: RF Sensor + relation: via + +host: diff --git a/tests/unit_tests/fixtures/core/config/device_without_area.yaml b/tests/unit_tests/fixtures/core/config/device_without_area.yaml index 8464cf37df0..cf2f5eb3c58 100644 --- a/tests/unit_tests/fixtures/core/config/device_without_area.yaml +++ b/tests/unit_tests/fixtures/core/config/device_without_area.yaml @@ -3,5 +3,6 @@ esphome: devices: - id: test_device name: Test Device + relation: via host: diff --git a/tests/unit_tests/fixtures/core/config/multiple_areas_devices.yaml b/tests/unit_tests/fixtures/core/config/multiple_areas_devices.yaml index ef3b4f6e675..f5d0dd9baea 100644 --- a/tests/unit_tests/fixtures/core/config/multiple_areas_devices.yaml +++ b/tests/unit_tests/fixtures/core/config/multiple_areas_devices.yaml @@ -12,11 +12,14 @@ esphome: - id: device1 name: Device 1 area_id: main_area + relation: via - id: device2 name: Device 2 area_id: area1 + relation: via - id: device3 name: Device 3 area_id: area2 + relation: via host: diff --git a/tests/unit_tests/fixtures/core/config/valid_area_device.yaml b/tests/unit_tests/fixtures/core/config/valid_area_device.yaml index fc978945864..2afd2aa521c 100644 --- a/tests/unit_tests/fixtures/core/config/valid_area_device.yaml +++ b/tests/unit_tests/fixtures/core/config/valid_area_device.yaml @@ -7,5 +7,6 @@ esphome: - id: test_device name: Test Device area_id: bedroom_area + relation: via host: diff --git a/tests/unit_tests/fixtures/core/entity_helpers/duplicate_entity_with_devices.yaml b/tests/unit_tests/fixtures/core/entity_helpers/duplicate_entity_with_devices.yaml index 42e16231a57..7ab2d4546d1 100644 --- a/tests/unit_tests/fixtures/core/entity_helpers/duplicate_entity_with_devices.yaml +++ b/tests/unit_tests/fixtures/core/entity_helpers/duplicate_entity_with_devices.yaml @@ -3,8 +3,10 @@ esphome: devices: - id: device1 name: "Device 1" + relation: via - id: device2 name: "Device 2" + relation: via esp32: board: esp32dev