fix: gate entity registration on field presence by default
_is_included now checks that an entity's field exists in the resource rep before registering it, preventing phantom entities when optional fields are absent on a shared resource. Explicit exists_fn still takes priority for cases requiring custom logic. All platforms use the shared helper; select.py's inline duplicate removed.
This commit is contained in:
@@ -10,7 +10,7 @@ from .ocf.registry.entities import BinarySensorDesc
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
from .entity import LocalThingsEntity
|
||||
from .entity import LocalThingsEntity, _is_included
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -22,7 +22,7 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
LocalThingsBinarySensor(coordinator, b)
|
||||
for b in coordinator.bound
|
||||
if isinstance(b.desc, BinarySensorDesc)
|
||||
if isinstance(b.desc, BinarySensorDesc) and _is_included(b, coordinator)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from .ocf.registry.entities import ButtonDesc
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
from .entity import LocalThingsEntity
|
||||
from .entity import LocalThingsEntity, _is_included
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -22,7 +22,7 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
LocalThingsButton(coordinator, b)
|
||||
for b in coordinator.bound
|
||||
if isinstance(b.desc, ButtonDesc)
|
||||
if isinstance(b.desc, ButtonDesc) and _is_included(b, coordinator)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -14,6 +14,21 @@ from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
|
||||
|
||||
def _is_included(bound: BoundEntity, coordinator: 'LocalThingsCoordinator') -> bool:
|
||||
"""Return False if the entity should not be registered for this device.
|
||||
|
||||
Explicit exists_fn takes priority. Otherwise, if the entity has a field,
|
||||
require that field to be present in the resource rep so that optional
|
||||
fields on shared resources don't create phantom entities.
|
||||
"""
|
||||
rep = coordinator.last_resources.get(bound.href) or {}
|
||||
if bound.desc.exists_fn is not None:
|
||||
return bound.desc.exists_fn(rep)
|
||||
if bound.desc.field:
|
||||
return bound.desc.field in rep
|
||||
return True # rep_fn or no-field entities (ButtonDesc) are always included
|
||||
|
||||
|
||||
def _derive_name(state_key: str) -> str:
|
||||
"""Turn a snake_case state key into a title-cased display name.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from .ocf.registry.entities import NumberDesc
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
from .entity import LocalThingsEntity
|
||||
from .entity import LocalThingsEntity, _is_included
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -22,7 +22,7 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
LocalThingsNumber(coordinator, b)
|
||||
for b in coordinator.bound
|
||||
if isinstance(b.desc, NumberDesc)
|
||||
if isinstance(b.desc, NumberDesc) and _is_included(b, coordinator)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from .ocf.registry.entities import SelectDesc
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
from .entity import LocalThingsEntity
|
||||
from .entity import LocalThingsEntity, _is_included
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -19,21 +19,10 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
def _include(b) -> bool:
|
||||
if not isinstance(b.desc, SelectDesc):
|
||||
return False
|
||||
# Skip at registration time if exists_fn says the entity isn't present
|
||||
if b.desc.exists_fn is not None:
|
||||
rep = coordinator.last_resources.get(b.href) or {}
|
||||
if not b.desc.exists_fn(rep):
|
||||
return False
|
||||
return True
|
||||
|
||||
async_add_entities(
|
||||
LocalThingsSelect(coordinator, b)
|
||||
for b in coordinator.bound
|
||||
if _include(b)
|
||||
if isinstance(b.desc, SelectDesc) and _is_included(b, coordinator)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from .ocf.registry.entities import SensorDesc
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
from .entity import LocalThingsEntity
|
||||
from .entity import LocalThingsEntity, _is_included
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -22,7 +22,7 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
LocalThingsSensor(coordinator, b)
|
||||
for b in coordinator.bound
|
||||
if isinstance(b.desc, SensorDesc)
|
||||
if isinstance(b.desc, SensorDesc) and _is_included(b, coordinator)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from .ocf.registry.entities import SwitchDesc
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
from .entity import LocalThingsEntity
|
||||
from .entity import LocalThingsEntity, _is_included
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -22,7 +22,7 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
LocalThingsSwitch(coordinator, b)
|
||||
for b in coordinator.bound
|
||||
if isinstance(b.desc, SwitchDesc)
|
||||
if isinstance(b.desc, SwitchDesc) and _is_included(b, coordinator)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from .ocf.registry.entities import TimeDesc
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
from .entity import LocalThingsEntity
|
||||
from .entity import LocalThingsEntity, _is_included
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -24,7 +24,7 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
LocalThingsTime(coordinator, b)
|
||||
for b in coordinator.bound
|
||||
if isinstance(b.desc, TimeDesc)
|
||||
if isinstance(b.desc, TimeDesc) and _is_included(b, coordinator)
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user