diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index 8ed6c37a9be..7e77b0d0789 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -37,7 +37,6 @@ from .utils import ( async_remove_orphaned_entities, get_blu_trv_device_info, get_device_entry_gen, - get_virtual_component_ids, is_block_momentary_input, is_rpc_momentary_input, is_view_for_platform, @@ -307,6 +306,13 @@ RPC_SENSORS: Final = { device_class=BinarySensorDeviceClass.OCCUPANCY, entity_class=RpcPresenceBinarySensor, ), + "presencezone_state": RpcBinarySensorDescription( + key="presencezone", + sub_key="state", + name="Occupancy", + device_class=BinarySensorDeviceClass.OCCUPANCY, + entity_class=RpcPresenceBinarySensor, + ), } @@ -333,18 +339,12 @@ async def async_setup_entry( hass, config_entry, async_add_entities, RPC_SENSORS, RpcBinarySensor ) - # the user can remove virtual components from the device configuration, so - # we need to remove orphaned entities - virtual_binary_sensor_ids = get_virtual_component_ids( - coordinator.device.config, BINARY_SENSOR_PLATFORM - ) async_remove_orphaned_entities( hass, config_entry.entry_id, coordinator.mac, BINARY_SENSOR_PLATFORM, - virtual_binary_sensor_ids, - "boolean", + coordinator.device.status, ) return diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index 31b92f3ca58..5a5603747bd 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -269,7 +269,15 @@ DEVICES_WITHOUT_FIRMWARE_CHANGELOG = ( CONF_GEN = "gen" -VIRTUAL_COMPONENTS = ("boolean", "button", "enum", "input", "number", "text") +VIRTUAL_COMPONENTS = ( + "boolean", + "button", + "enum", + "input", + "number", + "presencezone", + "text", +) VIRTUAL_COMPONENTS_MAP = { "binary_sensor": {"types": ["boolean"], "modes": ["label"]}, "button": {"types": ["button"], "modes": ["button"]}, diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 675a2223769..d852583c497 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -61,7 +61,6 @@ from .utils import ( get_device_entry_gen, get_device_uptime, get_shelly_air_lamp_life, - get_virtual_component_ids, get_virtual_component_unit, is_rpc_wifi_stations_disabled, is_view_for_platform, @@ -1459,6 +1458,14 @@ RPC_SENSORS: Final = { state_class=SensorStateClass.MEASUREMENT, entity_class=RpcPresenceSensor, ), + "presencezone_num_objects": RpcSensorDescription( + key="presencezone", + sub_key="num_objects", + translation_key="detected_objects", + name="Detected objects", + state_class=SensorStateClass.MEASUREMENT, + entity_class=RpcPresenceSensor, + ), "object_water_consumption": RpcSensorDescription( key="object", sub_key="value", @@ -1570,21 +1577,6 @@ async def async_setup_entry( SENSOR_PLATFORM, coordinator.device.status, ) - - # the user can remove virtual components from the device configuration, so - # we need to remove orphaned entities - virtual_component_ids = get_virtual_component_ids( - coordinator.device.config, SENSOR_PLATFORM - ) - for component in ("enum", "number", "text"): - async_remove_orphaned_entities( - hass, - config_entry.entry_id, - coordinator.mac, - SENSOR_PLATFORM, - virtual_component_ids, - component, - ) return if config_entry.data[CONF_SLEEP_PERIOD]: diff --git a/tests/components/shelly/test_binary_sensor.py b/tests/components/shelly/test_binary_sensor.py index 5aa4f59781e..113903ba140 100644 --- a/tests/components/shelly/test_binary_sensor.py +++ b/tests/components/shelly/test_binary_sensor.py @@ -591,3 +591,46 @@ async def test_rpc_presence_component( assert (state := hass.states.get(entity_id)) assert state.state == STATE_UNAVAILABLE + + +async def test_rpc_presencezone_component( + hass: HomeAssistant, + mock_rpc_device: Mock, + monkeypatch: pytest.MonkeyPatch, + entity_registry: EntityRegistry, +) -> None: + """Test RPC binary sensor entity for presencezone component.""" + config = deepcopy(mock_rpc_device.config) + config["presencezone:200"] = {"name": "Main zone", "enable": True} + monkeypatch.setattr(mock_rpc_device, "config", config) + + status = deepcopy(mock_rpc_device.status) + status["presencezone:200"] = {"state": True, "num_objects": 3} + monkeypatch.setattr(mock_rpc_device, "status", status) + + mock_config_entry = await init_integration(hass, 4) + + entity_id = f"{BINARY_SENSOR_DOMAIN}.test_name_main_zone_occupancy" + + assert (state := hass.states.get(entity_id)) + assert state.state == STATE_ON + + assert (entry := entity_registry.async_get(entity_id)) + assert entry.unique_id == "123456789ABC-presencezone:200-presencezone_state" + + mutate_rpc_device_status( + monkeypatch, mock_rpc_device, "presencezone:200", "state", False + ) + mock_rpc_device.mock_update() + + assert (state := hass.states.get(entity_id)) + assert state.state == STATE_OFF + + config = deepcopy(mock_rpc_device.config) + config["presencezone:200"] = {"enable": False} + monkeypatch.setattr(mock_rpc_device, "config", config) + await hass.config_entries.async_reload(mock_config_entry.entry_id) + mock_rpc_device.mock_update() + + assert (state := hass.states.get(entity_id)) + assert state.state == STATE_UNAVAILABLE diff --git a/tests/components/shelly/test_sensor.py b/tests/components/shelly/test_sensor.py index 02f81e5ac3b..408265d5320 100644 --- a/tests/components/shelly/test_sensor.py +++ b/tests/components/shelly/test_sensor.py @@ -1702,3 +1702,46 @@ async def test_rpc_presence_component( assert (state := hass.states.get(entity_id)) assert state.state == STATE_UNAVAILABLE + + +async def test_rpc_presencezone_component( + hass: HomeAssistant, + mock_rpc_device: Mock, + monkeypatch: pytest.MonkeyPatch, + entity_registry: EntityRegistry, +) -> None: + """Test RPC sensor entity for presencezone component.""" + config = deepcopy(mock_rpc_device.config) + config["presencezone:201"] = {"name": "Other zone", "enable": True} + monkeypatch.setattr(mock_rpc_device, "config", config) + + status = deepcopy(mock_rpc_device.status) + status["presencezone:201"] = {"state": True, "num_objects": 3} + monkeypatch.setattr(mock_rpc_device, "status", status) + + mock_config_entry = await init_integration(hass, 4) + + entity_id = f"{SENSOR_DOMAIN}.test_name_other_zone_detected_objects" + + assert (state := hass.states.get(entity_id)) + assert state.state == "3" + + assert (entry := entity_registry.async_get(entity_id)) + assert entry.unique_id == "123456789ABC-presencezone:201-presencezone_num_objects" + + mutate_rpc_device_status( + monkeypatch, mock_rpc_device, "presencezone:201", "num_objects", 2 + ) + mock_rpc_device.mock_update() + + assert (state := hass.states.get(entity_id)) + assert state.state == "2" + + config = deepcopy(mock_rpc_device.config) + config["presencezone:201"] = {"enable": False} + monkeypatch.setattr(mock_rpc_device, "config", config) + await hass.config_entries.async_reload(mock_config_entry.entry_id) + mock_rpc_device.mock_update() + + assert (state := hass.states.get(entity_id)) + assert state.state == STATE_UNAVAILABLE