Support Shelly presencezone component (#152393)

Co-authored-by: Shay Levy <levyshay1@gmail.com>
Co-authored-by: Simone Chemelli <simone.chemelli@gmail.com>
This commit is contained in:
Maciej Bieniek
2025-09-21 19:27:40 +02:00
committed by GitHub
parent e61ad10708
commit 2b6a125927
5 changed files with 111 additions and 25 deletions

View File

@@ -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

View File

@@ -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"]},

View File

@@ -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]:

View File

@@ -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

View File

@@ -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