Skip to content

Commit 2ed92c7

Browse files
authored
Add entities for Shelly presence component (home-assistant#151816)
1 parent 7389f23 commit 2ed92c7

File tree

6 files changed

+127
-1
lines changed

6 files changed

+127
-1
lines changed

homeassistant/components/shelly/binary_sensor.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,17 @@ def is_on(self) -> bool:
7373
return bool(self.attribute_value)
7474

7575

76+
class RpcPresenceBinarySensor(RpcBinarySensor):
77+
"""Represent a RPC binary sensor entity for presence component."""
78+
79+
@property
80+
def available(self) -> bool:
81+
"""Available."""
82+
available = super().available
83+
84+
return available and self.coordinator.device.config[self.key]["enable"]
85+
86+
7687
class RpcBluTrvBinarySensor(RpcBinarySensor):
7788
"""Represent a RPC BluTrv binary sensor."""
7889

@@ -283,6 +294,14 @@ def __init__(
283294
name="Mute",
284295
entity_category=EntityCategory.DIAGNOSTIC,
285296
),
297+
"presence_num_objects": RpcBinarySensorDescription(
298+
key="presence",
299+
sub_key="num_objects",
300+
value=lambda status, _: bool(status),
301+
name="Occupancy",
302+
device_class=BinarySensorDeviceClass.OCCUPANCY,
303+
entity_class=RpcPresenceBinarySensor,
304+
),
286305
}
287306

288307

homeassistant/components/shelly/icons.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
}
2121
},
2222
"sensor": {
23+
"detected_objects": {
24+
"default": "mdi:account-group"
25+
},
2326
"gas_concentration": {
2427
"default": "mdi:gauge"
2528
},

homeassistant/components/shelly/sensor.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,17 @@ def native_value(self) -> StateType:
124124
return self.option_map[attribute_value]
125125

126126

127+
class RpcPresenceSensor(RpcSensor):
128+
"""Represent a RPC presence sensor."""
129+
130+
@property
131+
def available(self) -> bool:
132+
"""Available."""
133+
available = super().available
134+
135+
return available and self.coordinator.device.config[self.key]["enable"]
136+
137+
127138
class RpcEmeterPhaseSensor(RpcSensor):
128139
"""Represent a RPC energy meter phase sensor."""
129140

@@ -1428,6 +1439,14 @@ def __init__(
14281439
device_class=SensorDeviceClass.ENUM,
14291440
options=["dark", "twilight", "bright"],
14301441
),
1442+
"presence_num_objects": RpcSensorDescription(
1443+
key="presence",
1444+
sub_key="num_objects",
1445+
translation_key="detected_objects",
1446+
name="Detected objects",
1447+
state_class=SensorStateClass.MEASUREMENT,
1448+
entity_class=RpcPresenceSensor,
1449+
),
14311450
}
14321451

14331452

homeassistant/components/shelly/strings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@
141141
}
142142
},
143143
"sensor": {
144+
"detected_objects": {
145+
"unit_of_measurement": "objects"
146+
},
144147
"gas_detected": {
145148
"state": {
146149
"none": "None",

tests/components/shelly/test_binary_sensor.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
1212
from homeassistant.components.shelly.const import UPDATE_PERIOD_MULTIPLIER
13-
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN
13+
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN
1414
from homeassistant.core import HomeAssistant, State
1515
from homeassistant.helpers.device_registry import DeviceRegistry
1616
from homeassistant.helpers.entity_registry import EntityRegistry
@@ -527,3 +527,44 @@ async def test_rpc_flood_entities(
527527

528528
entry = entity_registry.async_get(entity_id)
529529
assert entry == snapshot(name=f"{entity_id}-entry")
530+
531+
532+
async def test_rpc_presence_component(
533+
hass: HomeAssistant,
534+
mock_rpc_device: Mock,
535+
monkeypatch: pytest.MonkeyPatch,
536+
entity_registry: EntityRegistry,
537+
) -> None:
538+
"""Test RPC binary sensor entity for presence component."""
539+
config = deepcopy(mock_rpc_device.config)
540+
config["presence"] = {"enable": True}
541+
monkeypatch.setattr(mock_rpc_device, "config", config)
542+
543+
status = deepcopy(mock_rpc_device.status)
544+
status["presence"] = {"num_objects": 2}
545+
monkeypatch.setattr(mock_rpc_device, "status", status)
546+
547+
mock_config_entry = await init_integration(hass, 4)
548+
549+
entity_id = f"{BINARY_SENSOR_DOMAIN}.test_name_occupancy"
550+
551+
assert (state := hass.states.get(entity_id))
552+
assert state.state == STATE_ON
553+
554+
assert (entry := entity_registry.async_get(entity_id))
555+
assert entry.unique_id == "123456789ABC-presence-presence_num_objects"
556+
557+
mutate_rpc_device_status(monkeypatch, mock_rpc_device, "presence", "num_objects", 0)
558+
mock_rpc_device.mock_update()
559+
560+
assert (state := hass.states.get(entity_id))
561+
assert state.state == STATE_OFF
562+
563+
config = deepcopy(mock_rpc_device.config)
564+
config["presence"] = {"enable": False}
565+
monkeypatch.setattr(mock_rpc_device, "config", config)
566+
await hass.config_entries.async_reload(mock_config_entry.entry_id)
567+
mock_rpc_device.mock_update()
568+
569+
assert (state := hass.states.get(entity_id))
570+
assert state.state == STATE_UNAVAILABLE

tests/components/shelly/test_sensor.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,3 +1630,44 @@ async def test_block_friendly_name_sleeping_sensor(
16301630

16311631
assert (state := hass.states.get(entity.entity_id))
16321632
assert state.attributes[ATTR_FRIENDLY_NAME] == "Test name Temperature"
1633+
1634+
1635+
async def test_rpc_presence_component(
1636+
hass: HomeAssistant,
1637+
mock_rpc_device: Mock,
1638+
monkeypatch: pytest.MonkeyPatch,
1639+
entity_registry: EntityRegistry,
1640+
) -> None:
1641+
"""Test RPC sensor entity for presence component."""
1642+
config = deepcopy(mock_rpc_device.config)
1643+
config["presence"] = {"enable": True}
1644+
monkeypatch.setattr(mock_rpc_device, "config", config)
1645+
1646+
status = deepcopy(mock_rpc_device.status)
1647+
status["presence"] = {"num_objects": 2}
1648+
monkeypatch.setattr(mock_rpc_device, "status", status)
1649+
1650+
mock_config_entry = await init_integration(hass, 4)
1651+
1652+
entity_id = f"{SENSOR_DOMAIN}.test_name_detected_objects"
1653+
1654+
assert (state := hass.states.get(entity_id))
1655+
assert state.state == "2"
1656+
1657+
assert (entry := entity_registry.async_get(entity_id))
1658+
assert entry.unique_id == "123456789ABC-presence-presence_num_objects"
1659+
1660+
mutate_rpc_device_status(monkeypatch, mock_rpc_device, "presence", "num_objects", 0)
1661+
mock_rpc_device.mock_update()
1662+
1663+
assert (state := hass.states.get(entity_id))
1664+
assert state.state == "0"
1665+
1666+
config = deepcopy(mock_rpc_device.config)
1667+
config["presence"] = {"enable": False}
1668+
monkeypatch.setattr(mock_rpc_device, "config", config)
1669+
await hass.config_entries.async_reload(mock_config_entry.entry_id)
1670+
mock_rpc_device.mock_update()
1671+
1672+
assert (state := hass.states.get(entity_id))
1673+
assert state.state == STATE_UNAVAILABLE

0 commit comments

Comments
 (0)