Skip to content

Commit 5b08724

Browse files
AlCalzonefrenck
authored andcommitted
Keep entities of dead Z-Wave devices available (home-assistant#148611)
1 parent 0675e34 commit 5b08724

File tree

5 files changed

+54
-48
lines changed

5 files changed

+54
-48
lines changed

homeassistant/components/zwave_js/entity.py

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from collections.abc import Sequence
66
from typing import Any
77

8-
from zwave_js_server.const import NodeStatus
98
from zwave_js_server.exceptions import BaseZwaveJSServerError
109
from zwave_js_server.model.driver import Driver
1110
from zwave_js_server.model.value import (
@@ -27,8 +26,6 @@
2726
from .helpers import get_device_id, get_unique_id, get_valueless_base_unique_id
2827

2928
EVENT_VALUE_REMOVED = "value removed"
30-
EVENT_DEAD = "dead"
31-
EVENT_ALIVE = "alive"
3229

3330

3431
class ZWaveBaseEntity(Entity):
@@ -141,11 +138,6 @@ async def async_added_to_hass(self) -> None:
141138
)
142139
)
143140

144-
for status_event in (EVENT_ALIVE, EVENT_DEAD):
145-
self.async_on_remove(
146-
self.info.node.on(status_event, self._node_status_alive_or_dead)
147-
)
148-
149141
self.async_on_remove(
150142
async_dispatcher_connect(
151143
self.hass,
@@ -211,19 +203,7 @@ def generate_name(
211203
@property
212204
def available(self) -> bool:
213205
"""Return entity availability."""
214-
return (
215-
self.driver.client.connected
216-
and bool(self.info.node.ready)
217-
and self.info.node.status != NodeStatus.DEAD
218-
)
219-
220-
@callback
221-
def _node_status_alive_or_dead(self, event_data: dict) -> None:
222-
"""Call when node status changes to alive or dead.
223-
224-
Should not be overridden by subclasses.
225-
"""
226-
self.async_write_ha_state()
206+
return self.driver.client.connected and bool(self.info.node.ready)
227207

228208
@callback
229209
def _value_changed(self, event_data: dict) -> None:

homeassistant/components/zwave_js/update.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -200,18 +200,13 @@ async def _async_update(self, _: HomeAssistant | datetime | None = None) -> None
200200
)
201201
return
202202

203-
# If device is asleep/dead, wait for it to wake up/become alive before
204-
# attempting an update
205-
for status, event_name in (
206-
(NodeStatus.ASLEEP, "wake up"),
207-
(NodeStatus.DEAD, "alive"),
208-
):
209-
if self.node.status == status:
210-
if not self._status_unsub:
211-
self._status_unsub = self.node.once(
212-
event_name, self._update_on_status_change
213-
)
214-
return
203+
# If device is asleep, wait for it to wake up before attempting an update
204+
if self.node.status == NodeStatus.ASLEEP:
205+
if not self._status_unsub:
206+
self._status_unsub = self.node.once(
207+
"wake up", self._update_on_status_change
208+
)
209+
return
215210

216211
try:
217212
# Retrieve all firmware updates including non-stable ones but filter

tests/components/zwave_js/test_init.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@
3737
)
3838
from homeassistant.setup import async_setup_component
3939

40-
from .common import AIR_TEMPERATURE_SENSOR, EATON_RF9640_ENTITY
40+
from .common import (
41+
AIR_TEMPERATURE_SENSOR,
42+
BULB_6_MULTI_COLOR_LIGHT_ENTITY,
43+
EATON_RF9640_ENTITY,
44+
)
4145

4246
from tests.common import (
4347
MockConfigEntry,
@@ -2168,3 +2172,39 @@ async def test_factory_reset_node(
21682172
assert len(notifications) == 1
21692173
assert list(notifications)[0] == msg_id
21702174
assert "network with the home ID `3245146787`" in notifications[msg_id]["message"]
2175+
2176+
2177+
async def test_entity_available_when_node_dead(
2178+
hass: HomeAssistant, client, bulb_6_multi_color, integration
2179+
) -> None:
2180+
"""Test that entities remain available even when the node is dead."""
2181+
2182+
node = bulb_6_multi_color
2183+
state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY)
2184+
2185+
assert state
2186+
assert state.state != STATE_UNAVAILABLE
2187+
2188+
# Send dead event to the node
2189+
event = Event(
2190+
"dead", data={"source": "node", "event": "dead", "nodeId": node.node_id}
2191+
)
2192+
node.receive_event(event)
2193+
await hass.async_block_till_done()
2194+
2195+
# Entity should remain available even though the node is dead
2196+
state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY)
2197+
assert state
2198+
assert state.state != STATE_UNAVAILABLE
2199+
2200+
# Send alive event to bring the node back
2201+
event = Event(
2202+
"alive", data={"source": "node", "event": "alive", "nodeId": node.node_id}
2203+
)
2204+
node.receive_event(event)
2205+
await hass.async_block_till_done()
2206+
2207+
# Entity should still be available
2208+
state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY)
2209+
assert state
2210+
assert state.state != STATE_UNAVAILABLE

tests/components/zwave_js/test_lock.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
SERVICE_SET_LOCK_CONFIGURATION,
2929
SERVICE_SET_LOCK_USERCODE,
3030
)
31-
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, STATE_UNKNOWN
31+
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN
3232
from homeassistant.core import HomeAssistant
3333
from homeassistant.exceptions import HomeAssistantError
3434

@@ -295,7 +295,8 @@ async def test_door_lock(
295295
assert node.status == NodeStatus.DEAD
296296
state = hass.states.get(SCHLAGE_BE469_LOCK_ENTITY)
297297
assert state
298-
assert state.state == STATE_UNAVAILABLE
298+
# The state should still be locked, even if the node is dead
299+
assert state.state == LockState.LOCKED
299300

300301

301302
async def test_only_one_lock(

tests/components/zwave_js/test_update.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ async def test_update_entity_dead(
277277
zen_31,
278278
integration,
279279
) -> None:
280-
"""Test update occurs when device is dead after it becomes alive."""
280+
"""Test update occurs even when device is dead."""
281281
event = Event(
282282
"dead",
283283
data={"source": "node", "event": "dead", "nodeId": zen_31.node_id},
@@ -290,17 +290,7 @@ async def test_update_entity_dead(
290290
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
291291
await hass.async_block_till_done()
292292

293-
# Because node is asleep we shouldn't attempt to check for firmware updates
294-
assert len(client.async_send_command.call_args_list) == 0
295-
296-
event = Event(
297-
"alive",
298-
data={"source": "node", "event": "alive", "nodeId": zen_31.node_id},
299-
)
300-
zen_31.receive_event(event)
301-
await hass.async_block_till_done()
302-
303-
# Now that the node is up we can check for updates
293+
# Checking for firmware updates should proceed even for dead nodes
304294
assert len(client.async_send_command.call_args_list) > 0
305295

306296
args = client.async_send_command.call_args_list[0][0][0]

0 commit comments

Comments
 (0)