Skip to content

Commit 3cd17a2

Browse files
authored
Add mute alarm button for Shelly Plus Smoke (home-assistant#154673)
1 parent d689400 commit 3cd17a2

File tree

3 files changed

+126
-20
lines changed

3 files changed

+126
-20
lines changed

homeassistant/components/shelly/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
]
9999
RPC_SLEEPING_PLATFORMS: Final = [
100100
Platform.BINARY_SENSOR,
101+
Platform.BUTTON,
101102
Platform.SENSOR,
102103
Platform.UPDATE,
103104
]

homeassistant/components/shelly/button.py

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from homeassistant.helpers.update_coordinator import CoordinatorEntity
2525

2626
from .const import (
27+
CONF_SLEEP_PERIOD,
2728
DOMAIN,
2829
LOGGER,
2930
MODEL_FRANKEVER_WATER_VALVE,
@@ -34,6 +35,7 @@
3435
from .entity import (
3536
RpcEntityDescription,
3637
ShellyRpcAttributeEntity,
38+
ShellySleepingRpcAttributeEntity,
3739
async_setup_entry_rpc,
3840
get_entity_block_device_info,
3941
get_entity_rpc_device_info,
@@ -190,9 +192,10 @@ async def async_setup_entry(
190192
if TYPE_CHECKING:
191193
assert coordinator is not None
192194

193-
await er.async_migrate_entries(
194-
hass, config_entry.entry_id, partial(async_migrate_unique_ids, coordinator)
195-
)
195+
if coordinator.device.initialized:
196+
await er.async_migrate_entries(
197+
hass, config_entry.entry_id, partial(async_migrate_unique_ids, coordinator)
198+
)
196199

197200
entities: list[ShellyButton] = []
198201

@@ -208,22 +211,31 @@ async def async_setup_entry(
208211
return
209212

210213
# add RPC buttons
211-
async_setup_entry_rpc(
212-
hass, config_entry, async_add_entities, RPC_BUTTONS, RpcVirtualButton
213-
)
214+
if config_entry.data[CONF_SLEEP_PERIOD]:
215+
async_setup_entry_rpc(
216+
hass,
217+
config_entry,
218+
async_add_entities,
219+
RPC_BUTTONS,
220+
RpcSleepingSmokeMuteButton,
221+
)
222+
else:
223+
async_setup_entry_rpc(
224+
hass, config_entry, async_add_entities, RPC_BUTTONS, RpcVirtualButton
225+
)
214226

215-
# the user can remove virtual components from the device configuration, so
216-
# we need to remove orphaned entities
217-
virtual_button_component_ids = get_virtual_component_ids(
218-
coordinator.device.config, BUTTON_PLATFORM
219-
)
220-
async_remove_orphaned_entities(
221-
hass,
222-
config_entry.entry_id,
223-
coordinator.mac,
224-
BUTTON_PLATFORM,
225-
virtual_button_component_ids,
226-
)
227+
# the user can remove virtual components from the device configuration, so
228+
# we need to remove orphaned entities
229+
virtual_button_component_ids = get_virtual_component_ids(
230+
coordinator.device.config, BUTTON_PLATFORM
231+
)
232+
async_remove_orphaned_entities(
233+
hass,
234+
config_entry.entry_id,
235+
coordinator.mac,
236+
BUTTON_PLATFORM,
237+
virtual_button_component_ids,
238+
)
227239

228240

229241
class ShellyBaseButton(
@@ -354,6 +366,31 @@ async def async_press(self) -> None:
354366
await self.coordinator.device.button_trigger(self._id, "single_push")
355367

356368

369+
class RpcSleepingSmokeMuteButton(ShellySleepingRpcAttributeEntity, ButtonEntity):
370+
"""Defines a Shelly RPC Smoke mute alarm button."""
371+
372+
entity_description: RpcButtonDescription
373+
374+
@rpc_call
375+
async def async_press(self) -> None:
376+
"""Triggers the Shelly button press service."""
377+
if TYPE_CHECKING:
378+
assert isinstance(self.coordinator, ShellyRpcCoordinator)
379+
380+
_id = int(self.key.split(":")[-1])
381+
await self.coordinator.device.smoke_mute_alarm(_id)
382+
383+
@property
384+
def available(self) -> bool:
385+
"""Available."""
386+
available = super().available
387+
388+
if self.coordinator.device.initialized:
389+
return available and self.status["alarm"]
390+
391+
return False
392+
393+
357394
RPC_BUTTONS = {
358395
"button_generic": RpcButtonDescription(
359396
key="button",
@@ -379,4 +416,10 @@ async def async_press(self) -> None:
379416
entity_class=ShellyBluTrvButton,
380417
models={MODEL_BLU_GATEWAY_G3},
381418
),
419+
"smoke_mute": RpcButtonDescription(
420+
key="smoke",
421+
sub_key="mute",
422+
name="Mute alarm",
423+
translation_key="mute",
424+
),
382425
}

tests/components/shelly/test_button.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,20 @@
33
from copy import deepcopy
44
from unittest.mock import Mock
55

6-
from aioshelly.const import MODEL_BLU_GATEWAY_G3
6+
from aioshelly.const import MODEL_BLU_GATEWAY_G3, MODEL_PLUS_SMOKE
77
from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError, RpcCallError
88
import pytest
99
from syrupy.assertion import SnapshotAssertion
1010

1111
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
1212
from homeassistant.components.shelly.const import DOMAIN, MODEL_FRANKEVER_WATER_VALVE
1313
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
14-
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, Platform
14+
from homeassistant.const import (
15+
ATTR_ENTITY_ID,
16+
STATE_UNAVAILABLE,
17+
STATE_UNKNOWN,
18+
Platform,
19+
)
1520
from homeassistant.core import HomeAssistant
1621
from homeassistant.exceptions import HomeAssistantError
1722
from homeassistant.helpers.device_registry import DeviceRegistry
@@ -20,6 +25,7 @@
2025
from . import (
2126
MOCK_MAC,
2227
init_integration,
28+
mutate_rpc_device_status,
2329
patch_platforms,
2430
register_device,
2531
register_entity,
@@ -476,3 +482,59 @@ async def test_migrate_unique_id_virtual_components_roles(
476482
assert entity_entry.unique_id == new_unique_id
477483

478484
assert "Migrating unique_id for button.test_name_test_button" in caplog.text
485+
486+
487+
async def test_rpc_smoke_mute_alarm_button(
488+
hass: HomeAssistant,
489+
mock_rpc_device: Mock,
490+
device_registry: DeviceRegistry,
491+
monkeypatch: pytest.MonkeyPatch,
492+
) -> None:
493+
"""Test RPC smoke mute alarm button."""
494+
entity_id = f"{BUTTON_DOMAIN}.test_name_mute_alarm"
495+
status = {
496+
"sys": {"wakeup_period": 1000},
497+
"smoke:0": {
498+
"id": 0,
499+
"alarm": False,
500+
"mute": False,
501+
},
502+
}
503+
monkeypatch.setattr(mock_rpc_device, "status", status)
504+
config = {"smoke:0": {"id": 0, "name": None}}
505+
monkeypatch.setattr(mock_rpc_device, "config", config)
506+
monkeypatch.setattr(mock_rpc_device, "connected", False)
507+
entry = await init_integration(hass, 2, sleep_period=1000, model=MODEL_PLUS_SMOKE)
508+
509+
# Sensor should be created when device is online
510+
assert hass.states.get(entity_id) is None
511+
512+
register_entity(
513+
hass,
514+
BUTTON_DOMAIN,
515+
"test_name_mute_alarm",
516+
"smoke:0-smoke_mute",
517+
entry,
518+
)
519+
520+
# Make device online
521+
mock_rpc_device.mock_online()
522+
await hass.async_block_till_done(wait_background_tasks=True)
523+
524+
assert (state := hass.states.get(entity_id))
525+
assert state.state == STATE_UNAVAILABLE
526+
527+
mutate_rpc_device_status(monkeypatch, mock_rpc_device, "smoke:0", "alarm", True)
528+
mock_rpc_device.mock_update()
529+
530+
assert (state := hass.states.get(entity_id))
531+
assert state.state == STATE_UNKNOWN
532+
533+
await hass.services.async_call(
534+
BUTTON_DOMAIN,
535+
SERVICE_PRESS,
536+
{ATTR_ENTITY_ID: entity_id},
537+
blocking=True,
538+
)
539+
mock_rpc_device.mock_update()
540+
mock_rpc_device.smoke_mute_alarm.assert_called_once_with(0)

0 commit comments

Comments
 (0)