Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 1 addition & 21 deletions homeassistant/components/zwave_js/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from collections.abc import Sequence
from typing import Any

from zwave_js_server.const import NodeStatus
from zwave_js_server.exceptions import BaseZwaveJSServerError
from zwave_js_server.model.driver import Driver
from zwave_js_server.model.value import (
Expand All @@ -27,8 +26,6 @@
from .helpers import get_device_id, get_unique_id, get_valueless_base_unique_id

EVENT_VALUE_REMOVED = "value removed"
EVENT_DEAD = "dead"
EVENT_ALIVE = "alive"


class ZWaveBaseEntity(Entity):
Expand Down Expand Up @@ -141,11 +138,6 @@ async def async_added_to_hass(self) -> None:
)
)

for status_event in (EVENT_ALIVE, EVENT_DEAD):
self.async_on_remove(
self.info.node.on(status_event, self._node_status_alive_or_dead)
)

self.async_on_remove(
async_dispatcher_connect(
self.hass,
Expand Down Expand Up @@ -211,19 +203,7 @@ def generate_name(
@property
def available(self) -> bool:
"""Return entity availability."""
return (
self.driver.client.connected
and bool(self.info.node.ready)
and self.info.node.status != NodeStatus.DEAD
)

@callback
def _node_status_alive_or_dead(self, event_data: dict) -> None:
"""Call when node status changes to alive or dead.

Should not be overridden by subclasses.
"""
self.async_write_ha_state()
return self.driver.client.connected and bool(self.info.node.ready)

@callback
def _value_changed(self, event_data: dict) -> None:
Expand Down
19 changes: 7 additions & 12 deletions homeassistant/components/zwave_js/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,18 +199,13 @@ async def _async_update(self, _: HomeAssistant | datetime | None = None) -> None
)
return

# If device is asleep/dead, wait for it to wake up/become alive before
# attempting an update
for status, event_name in (
(NodeStatus.ASLEEP, "wake up"),
(NodeStatus.DEAD, "alive"),
):
if self.node.status == status:
if not self._status_unsub:
self._status_unsub = self.node.once(
event_name, self._update_on_status_change
)
return
# If device is asleep, wait for it to wake up before attempting an update
if self.node.status == NodeStatus.ASLEEP:
if not self._status_unsub:
self._status_unsub = self.node.once(
"wake up", self._update_on_status_change
)
return

try:
# Retrieve all firmware updates including non-stable ones but filter
Expand Down
42 changes: 41 additions & 1 deletion tests/components/zwave_js/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@
)
from homeassistant.setup import async_setup_component

from .common import AIR_TEMPERATURE_SENSOR, EATON_RF9640_ENTITY
from .common import (
AIR_TEMPERATURE_SENSOR,
BULB_6_MULTI_COLOR_LIGHT_ENTITY,
EATON_RF9640_ENTITY,
)

from tests.common import (
MockConfigEntry,
Expand Down Expand Up @@ -2168,3 +2172,39 @@ async def test_factory_reset_node(
assert len(notifications) == 1
assert list(notifications)[0] == msg_id
assert "network with the home ID `3245146787`" in notifications[msg_id]["message"]


async def test_entity_available_when_node_dead(
hass: HomeAssistant, client, bulb_6_multi_color, integration
) -> None:
"""Test that entities remain available even when the node is dead."""

node = bulb_6_multi_color
state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY)

assert state
assert state.state != STATE_UNAVAILABLE

# Send dead event to the node
event = Event(
"dead", data={"source": "node", "event": "dead", "nodeId": node.node_id}
)
node.receive_event(event)
await hass.async_block_till_done()

# Entity should remain available even though the node is dead
state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY)
assert state
assert state.state != STATE_UNAVAILABLE

# Send alive event to bring the node back
event = Event(
"alive", data={"source": "node", "event": "alive", "nodeId": node.node_id}
)
node.receive_event(event)
await hass.async_block_till_done()

# Entity should still be available
state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY)
assert state
assert state.state != STATE_UNAVAILABLE
5 changes: 3 additions & 2 deletions tests/components/zwave_js/test_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
SERVICE_SET_LOCK_CONFIGURATION,
SERVICE_SET_LOCK_USERCODE,
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError

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


async def test_only_one_lock(
Expand Down
14 changes: 2 additions & 12 deletions tests/components/zwave_js/test_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ async def test_update_entity_dead(
zen_31,
integration,
) -> None:
"""Test update occurs when device is dead after it becomes alive."""
"""Test update occurs even when device is dead."""
event = Event(
"dead",
data={"source": "node", "event": "dead", "nodeId": zen_31.node_id},
Expand All @@ -290,17 +290,7 @@ async def test_update_entity_dead(
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
await hass.async_block_till_done()

# Because node is asleep we shouldn't attempt to check for firmware updates
assert len(client.async_send_command.call_args_list) == 0

event = Event(
"alive",
data={"source": "node", "event": "alive", "nodeId": zen_31.node_id},
)
zen_31.receive_event(event)
await hass.async_block_till_done()

# Now that the node is up we can check for updates
# Checking for firmware updates should proceed even for dead nodes
assert len(client.async_send_command.call_args_list) > 0

args = client.async_send_command.call_args_list[0][0][0]
Expand Down
Loading