mirror of
https://github.com/esphome/esphome.git
synced 2026-10-22 05:46:12 +00:00
[core] Add relation option to sub-devices (#21232)
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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<enums::DeviceRelation>(device->get_relation());
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
|
||||
@@ -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<uint32_t>(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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -154,6 +154,16 @@ template<> const char *proto_enum_to_string<enums::DisconnectReason>(enums::Disc
|
||||
return ESPHOME_PSTR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::DeviceRelation>(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>(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<enums::DeviceRelation>(this->relation));
|
||||
return out.c_str();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -686,6 +686,7 @@ DEVICES_HEADER = """
|
||||
devices:
|
||||
- id: kitchen_dev
|
||||
name: Kitchen
|
||||
relation: via
|
||||
|
||||
esp32:
|
||||
variant: esp32
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -351,6 +351,7 @@ esphome:
|
||||
- id: my_device
|
||||
name: "Test Device"
|
||||
area_id: living_room
|
||||
relation: via
|
||||
|
||||
# API services
|
||||
api:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}'"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
@@ -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
|
||||
|
||||
@@ -3,8 +3,10 @@ esphome:
|
||||
devices:
|
||||
- id: test_2258
|
||||
name: "Device 1"
|
||||
relation: via
|
||||
- id: d6ka
|
||||
name: "Device 2"
|
||||
relation: via
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
@@ -7,6 +7,7 @@ esphome:
|
||||
- id: test_device
|
||||
name: "Test Device"
|
||||
area_id: nonexistent_area
|
||||
relation: via
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
esphome:
|
||||
name: test-device-invalid-relation
|
||||
devices:
|
||||
- id: test_device
|
||||
name: Test Device
|
||||
relation: parent
|
||||
|
||||
host:
|
||||
@@ -0,0 +1,7 @@
|
||||
esphome:
|
||||
name: test-device-missing-relation
|
||||
devices:
|
||||
- id: test_device
|
||||
name: Test Device
|
||||
|
||||
host:
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -3,5 +3,6 @@ esphome:
|
||||
devices:
|
||||
- id: test_device
|
||||
name: Test Device
|
||||
relation: via
|
||||
|
||||
host:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -7,5 +7,6 @@ esphome:
|
||||
- id: test_device
|
||||
name: Test Device
|
||||
area_id: bedroom_area
|
||||
relation: via
|
||||
|
||||
host:
|
||||
|
||||
@@ -3,8 +3,10 @@ esphome:
|
||||
devices:
|
||||
- id: device1
|
||||
name: "Device 1"
|
||||
relation: via
|
||||
- id: device2
|
||||
name: "Device 2"
|
||||
relation: via
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
Reference in New Issue
Block a user