mirror of
https://github.com/esphome/esphome.git
synced 2025-09-02 11:22:24 +01:00
[entity] Allow `device_id
` to be blank on entities (#10217)
This commit is contained in:
@@ -393,10 +393,13 @@ def icon(value):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def sub_device_id(value: str | None) -> core.ID:
|
def sub_device_id(value: str | None) -> core.ID | None:
|
||||||
# Lazy import to avoid circular imports
|
# Lazy import to avoid circular imports
|
||||||
from esphome.core.config import Device
|
from esphome.core.config import Device
|
||||||
|
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
|
||||||
return use_id(Device)(value)
|
return use_id(Device)(value)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -77,8 +77,8 @@ async def setup_entity(var: MockObj, config: ConfigType, platform: str) -> None:
|
|||||||
"""
|
"""
|
||||||
# Get device info
|
# Get device info
|
||||||
device_name: str | None = None
|
device_name: str | None = None
|
||||||
if CONF_DEVICE_ID in config:
|
device_id_obj: ID | None
|
||||||
device_id_obj: ID = config[CONF_DEVICE_ID]
|
if device_id_obj := config.get(CONF_DEVICE_ID):
|
||||||
device: MockObj = await get_variable(device_id_obj)
|
device: MockObj = await get_variable(device_id_obj)
|
||||||
add(var.set_device(device))
|
add(var.set_device(device))
|
||||||
# Get device name for object ID calculation
|
# Get device name for object ID calculation
|
||||||
@@ -199,8 +199,8 @@ def entity_duplicate_validator(platform: str) -> Callable[[ConfigType], ConfigTy
|
|||||||
# Get device name if entity is on a sub-device
|
# Get device name if entity is on a sub-device
|
||||||
device_name = None
|
device_name = None
|
||||||
device_id = "" # Empty string for main device
|
device_id = "" # Empty string for main device
|
||||||
if CONF_DEVICE_ID in config:
|
device_id_obj: ID | None
|
||||||
device_id_obj = config[CONF_DEVICE_ID]
|
if device_id_obj := config.get(CONF_DEVICE_ID):
|
||||||
device_name = device_id_obj.id
|
device_name = device_id_obj.id
|
||||||
# Use the device ID string directly for uniqueness
|
# Use the device ID string directly for uniqueness
|
||||||
device_id = device_id_obj.id
|
device_id = device_id_obj.id
|
||||||
|
@@ -55,6 +55,12 @@ sensor:
|
|||||||
lambda: return 4.0;
|
lambda: return 4.0;
|
||||||
update_interval: 0.1s
|
update_interval: 0.1s
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
name: Living Room Sensor
|
||||||
|
device_id: ""
|
||||||
|
lambda: return 5.0;
|
||||||
|
update_interval: 0.1s
|
||||||
|
|
||||||
# Switches with the same name on different devices to test device_id lookup
|
# Switches with the same name on different devices to test device_id lookup
|
||||||
switch:
|
switch:
|
||||||
# Switch with no device_id (defaults to 0)
|
# Switch with no device_id (defaults to 0)
|
||||||
@@ -96,3 +102,23 @@ switch:
|
|||||||
- logger.log: "Turning on Test Switch on Motion Detector"
|
- logger.log: "Turning on Test Switch on Motion Detector"
|
||||||
turn_off_action:
|
turn_off_action:
|
||||||
- logger.log: "Turning off Test Switch on Motion Detector"
|
- logger.log: "Turning off Test Switch on Motion Detector"
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
name: Living Room Blank Switch
|
||||||
|
device_id: ""
|
||||||
|
id: test_switch_blank_living_room
|
||||||
|
optimistic: true
|
||||||
|
turn_on_action:
|
||||||
|
- logger.log: "Turning on Living Room Blank Switch"
|
||||||
|
turn_off_action:
|
||||||
|
- logger.log: "Turning off Living Room Blank Switch"
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
name: Living Room None Switch
|
||||||
|
device_id:
|
||||||
|
id: test_switch_none_living_room
|
||||||
|
optimistic: true
|
||||||
|
turn_on_action:
|
||||||
|
- logger.log: "Turning on Living Room None Switch"
|
||||||
|
turn_off_action:
|
||||||
|
- logger.log: "Turning off Living Room None Switch"
|
||||||
|
@@ -132,6 +132,7 @@ async def test_areas_and_devices(
|
|||||||
"Temperature Sensor Reading": temp_sensor.device_id,
|
"Temperature Sensor Reading": temp_sensor.device_id,
|
||||||
"Motion Detector Status": motion_detector.device_id,
|
"Motion Detector Status": motion_detector.device_id,
|
||||||
"Smart Switch Power": smart_switch.device_id,
|
"Smart Switch Power": smart_switch.device_id,
|
||||||
|
"Living Room Sensor": 0, # Main device
|
||||||
}
|
}
|
||||||
|
|
||||||
for entity in sensor_entities:
|
for entity in sensor_entities:
|
||||||
@@ -160,6 +161,18 @@ async def test_areas_and_devices(
|
|||||||
"Should have a switch with device_id 0 (main device)"
|
"Should have a switch with device_id 0 (main device)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Verify extra switches with blank and none device_id are correctly available
|
||||||
|
extra_switches = [
|
||||||
|
e for e in switch_entities if e.name.startswith("Living Room")
|
||||||
|
]
|
||||||
|
assert len(extra_switches) == 2, (
|
||||||
|
f"Expected 2 extra switches for Living Room, got {len(extra_switches)}"
|
||||||
|
)
|
||||||
|
extra_switch_device_ids = [e.device_id for e in extra_switches]
|
||||||
|
assert all(d == 0 for d in extra_switch_device_ids), (
|
||||||
|
"All extra switches should have device_id 0 (main device)"
|
||||||
|
)
|
||||||
|
|
||||||
# Wait for initial states to be received for all switches
|
# Wait for initial states to be received for all switches
|
||||||
await asyncio.wait_for(initial_states_future, timeout=2.0)
|
await asyncio.wait_for(initial_states_future, timeout=2.0)
|
||||||
|
|
||||||
|
@@ -689,3 +689,19 @@ def test_entity_duplicate_validator_internal_entities() -> None:
|
|||||||
Invalid, match=r"Duplicate sensor entity with name 'Temperature' found"
|
Invalid, match=r"Duplicate sensor entity with name 'Temperature' found"
|
||||||
):
|
):
|
||||||
validator(config4)
|
validator(config4)
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_or_null_device_id_on_entity() -> None:
|
||||||
|
"""Test that empty or null device IDs are handled correctly."""
|
||||||
|
# Create validator for sensor platform
|
||||||
|
validator = entity_duplicate_validator("sensor")
|
||||||
|
|
||||||
|
# Entity with empty device_id should pass
|
||||||
|
config1 = {CONF_NAME: "Battery", CONF_DEVICE_ID: ""}
|
||||||
|
validated1 = validator(config1)
|
||||||
|
assert validated1 == config1
|
||||||
|
|
||||||
|
# Entity with None device_id should pass
|
||||||
|
config2 = {CONF_NAME: "Temperature", CONF_DEVICE_ID: None}
|
||||||
|
validated2 = validator(config2)
|
||||||
|
assert validated2 == config2
|
||||||
|
Reference in New Issue
Block a user