diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 548796911af..432bc2fa868 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -4,6 +4,7 @@ from __future__ import annotations import logging from zwave_js_server.client import Client as ZwaveClient +from zwave_js_server.model.node import NodeStatus from zwave_js_server.model.value import Value as ZwaveValue, get_value_id from homeassistant.config_entries import ConfigEntry @@ -18,6 +19,8 @@ from .helpers import get_device_id, get_unique_id LOGGER = logging.getLogger(__name__) EVENT_VALUE_UPDATED = "value updated" +EVENT_DEAD = "dead" +EVENT_ALIVE = "alive" class ZWaveBaseEntity(Entity): @@ -90,6 +93,11 @@ class ZWaveBaseEntity(Entity): self.async_on_remove( self.info.node.on(EVENT_VALUE_UPDATED, self._value_changed) ) + 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, @@ -135,7 +143,20 @@ class ZWaveBaseEntity(Entity): @property def available(self) -> bool: """Return entity availability.""" - return self.client.connected and bool(self.info.node.ready) + return ( + self.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() @callback def _value_changed(self, event_data: dict) -> None: diff --git a/tests/components/zwave_js/common.py b/tests/components/zwave_js/common.py index de80eb6bbc5..f14842609c5 100644 --- a/tests/components/zwave_js/common.py +++ b/tests/components/zwave_js/common.py @@ -22,6 +22,7 @@ CLIMATE_MAIN_HEAT_ACTIONNER = "climate.main_heat_actionner" BULB_6_MULTI_COLOR_LIGHT_ENTITY = "light.bulb_6_multi_color" EATON_RF9640_ENTITY = "light.allloaddimmer" AEON_SMART_SWITCH_LIGHT_ENTITY = "light.smart_switch_6" +SCHLAGE_BE469_LOCK_ENTITY = "lock.touchscreen_deadbolt" ID_LOCK_CONFIG_PARAMETER_SENSOR = ( "sensor.z_wave_module_for_id_lock_150_and_101_config_parameter_door_lock_mode" ) diff --git a/tests/components/zwave_js/test_lock.py b/tests/components/zwave_js/test_lock.py index 9ddc7abdd88..3727ab9d288 100644 --- a/tests/components/zwave_js/test_lock.py +++ b/tests/components/zwave_js/test_lock.py @@ -1,6 +1,7 @@ """Test the Z-Wave JS lock platform.""" from zwave_js_server.const import ATTR_CODE_SLOT, ATTR_USERCODE from zwave_js_server.event import Event +from zwave_js_server.model.node import NodeStatus from homeassistant.components.lock import ( DOMAIN as LOCK_DOMAIN, @@ -12,9 +13,14 @@ from homeassistant.components.zwave_js.lock import ( SERVICE_CLEAR_LOCK_USERCODE, SERVICE_SET_LOCK_USERCODE, ) -from homeassistant.const import ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED +from homeassistant.const import ( + ATTR_ENTITY_ID, + STATE_LOCKED, + STATE_UNAVAILABLE, + STATE_UNLOCKED, +) -SCHLAGE_BE469_LOCK_ENTITY = "lock.touchscreen_deadbolt" +from .common import SCHLAGE_BE469_LOCK_ENTITY async def test_door_lock(hass, client, lock_schlage_be469, integration): @@ -203,3 +209,16 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration): "value": 1, } assert args["value"] == 0 + + event = Event( + type="dead", + data={ + "source": "node", + "event": "dead", + "nodeId": 20, + }, + ) + node.receive_event(event) + + assert node.status == NodeStatus.DEAD + assert hass.states.get(SCHLAGE_BE469_LOCK_ENTITY).state == STATE_UNAVAILABLE