mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Add zwave_js node status sensor (#51181)
* Add zwave_js node status sensor * fix import * use parent class name property * Use more entity class attributes * Update homeassistant/components/zwave_js/sensor.py Co-authored-by: Franck Nijhof <frenck@frenck.nl> * return static values in property method * fix PR * switch to class atributes * create sensor platform task if needed Co-authored-by: Franck Nijhof <frenck@frenck.nl>
This commit is contained in:
parent
7654672dd0
commit
a8650f4e59
@ -15,6 +15,7 @@ from zwave_js_server.model.notification import (
|
|||||||
)
|
)
|
||||||
from zwave_js_server.model.value import Value, ValueNotification
|
from zwave_js_server.model.value import Value, ValueNotification
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_DEVICE_ID,
|
ATTR_DEVICE_ID,
|
||||||
@ -178,6 +179,19 @@ async def async_setup_entry( # noqa: C901
|
|||||||
if disc_info.assumed_state:
|
if disc_info.assumed_state:
|
||||||
value_updates_disc_info.append(disc_info)
|
value_updates_disc_info.append(disc_info)
|
||||||
|
|
||||||
|
# We need to set up the sensor platform if it hasn't already been setup in
|
||||||
|
# order to create the node status sensor
|
||||||
|
if SENSOR_DOMAIN not in platform_setup_tasks:
|
||||||
|
platform_setup_tasks[SENSOR_DOMAIN] = hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, SENSOR_DOMAIN)
|
||||||
|
)
|
||||||
|
await platform_setup_tasks[SENSOR_DOMAIN]
|
||||||
|
|
||||||
|
# Create a node status sensor for each device
|
||||||
|
async_dispatcher_send(
|
||||||
|
hass, f"{DOMAIN}_{entry.entry_id}_add_node_status_sensor", node
|
||||||
|
)
|
||||||
|
|
||||||
# add listener for value updated events if necessary
|
# add listener for value updated events if necessary
|
||||||
if value_updates_disc_info:
|
if value_updates_disc_info:
|
||||||
unsubscribe_callbacks.append(
|
unsubscribe_callbacks.append(
|
||||||
|
@ -6,6 +6,7 @@ from typing import cast
|
|||||||
|
|
||||||
from zwave_js_server.client import Client as ZwaveClient
|
from zwave_js_server.client import Client as ZwaveClient
|
||||||
from zwave_js_server.const import CommandClass, ConfigurationValueType
|
from zwave_js_server.const import CommandClass, ConfigurationValueType
|
||||||
|
from zwave_js_server.model.node import Node as ZwaveNode
|
||||||
from zwave_js_server.model.value import ConfigurationValue
|
from zwave_js_server.model.value import ConfigurationValue
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
@ -31,6 +32,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||||||
from .const import DATA_CLIENT, DATA_UNSUBSCRIBE, DOMAIN
|
from .const import DATA_CLIENT, DATA_UNSUBSCRIBE, DOMAIN
|
||||||
from .discovery import ZwaveDiscoveryInfo
|
from .discovery import ZwaveDiscoveryInfo
|
||||||
from .entity import ZWaveBaseEntity
|
from .entity import ZWaveBaseEntity
|
||||||
|
from .helpers import get_device_id
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -66,6 +68,11 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_add_node_status_sensor(node: ZwaveNode) -> None:
|
||||||
|
"""Add node status sensor."""
|
||||||
|
async_add_entities([ZWaveNodeStatusSensor(config_entry, client, node)])
|
||||||
|
|
||||||
hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append(
|
hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append(
|
||||||
async_dispatcher_connect(
|
async_dispatcher_connect(
|
||||||
hass,
|
hass,
|
||||||
@ -74,6 +81,14 @@ async def async_setup_entry(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append(
|
||||||
|
async_dispatcher_connect(
|
||||||
|
hass,
|
||||||
|
f"{DOMAIN}_{config_entry.entry_id}_add_node_status_sensor",
|
||||||
|
async_add_node_status_sensor,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ZwaveSensorBase(ZWaveBaseEntity, SensorEntity):
|
class ZwaveSensorBase(ZWaveBaseEntity, SensorEntity):
|
||||||
"""Basic Representation of a Z-Wave sensor."""
|
"""Basic Representation of a Z-Wave sensor."""
|
||||||
@ -295,3 +310,61 @@ class ZWaveConfigParameterSensor(ZwaveSensorBase):
|
|||||||
return None
|
return None
|
||||||
# add the value's int value as property for multi-value (list) items
|
# add the value's int value as property for multi-value (list) items
|
||||||
return {"value": self.info.primary_value.value}
|
return {"value": self.info.primary_value.value}
|
||||||
|
|
||||||
|
|
||||||
|
class ZWaveNodeStatusSensor(SensorEntity):
|
||||||
|
"""Representation of a node status sensor."""
|
||||||
|
|
||||||
|
_attr_should_poll = False
|
||||||
|
_attr_entity_registry_enabled_default = False
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, config_entry: ConfigEntry, client: ZwaveClient, node: ZwaveNode
|
||||||
|
) -> None:
|
||||||
|
"""Initialize a generic Z-Wave device entity."""
|
||||||
|
self.config_entry = config_entry
|
||||||
|
self.client = client
|
||||||
|
self.node = node
|
||||||
|
name: str = (
|
||||||
|
self.node.name
|
||||||
|
or self.node.device_config.description
|
||||||
|
or f"Node {self.node.node_id}"
|
||||||
|
)
|
||||||
|
# Entity class attributes
|
||||||
|
self._attr_name = f"{name}: Node Status"
|
||||||
|
self._attr_unique_id = (
|
||||||
|
f"{self.client.driver.controller.home_id}.{node.node_id}.node_status"
|
||||||
|
)
|
||||||
|
# device is precreated in main handler
|
||||||
|
self._attr_device_info = {
|
||||||
|
"identifiers": {get_device_id(self.client, self.node)},
|
||||||
|
}
|
||||||
|
self._attr_state: str = node.status.name.lower()
|
||||||
|
|
||||||
|
async def async_poll_value(self, _: bool) -> None:
|
||||||
|
"""Poll a value."""
|
||||||
|
raise ValueError("There is no value to poll for this entity")
|
||||||
|
|
||||||
|
def _status_changed(self, _: dict) -> None:
|
||||||
|
"""Call when status event is received."""
|
||||||
|
self._attr_state = self.node.status.name.lower()
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
async def async_added_to_hass(self) -> None:
|
||||||
|
"""Call when entity is added."""
|
||||||
|
# Add value_changed callbacks.
|
||||||
|
for evt in ("wake up", "sleep", "dead", "alive"):
|
||||||
|
self.async_on_remove(self.node.on(evt, self._status_changed))
|
||||||
|
self.async_on_remove(
|
||||||
|
async_dispatcher_connect(
|
||||||
|
self.hass,
|
||||||
|
f"{DOMAIN}_{self.unique_id}_poll_value",
|
||||||
|
self.async_poll_value,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return entity availability."""
|
||||||
|
return self.client.connected and bool(self.node.ready)
|
||||||
|
@ -916,7 +916,7 @@ async def test_removed_device(hass, client, multiple_devices, integration):
|
|||||||
# Check how many entities there are
|
# Check how many entities there are
|
||||||
ent_reg = er.async_get(hass)
|
ent_reg = er.async_get(hass)
|
||||||
entity_entries = er.async_entries_for_config_entry(ent_reg, integration.entry_id)
|
entity_entries = er.async_entries_for_config_entry(ent_reg, integration.entry_id)
|
||||||
assert len(entity_entries) == 24
|
assert len(entity_entries) == 26
|
||||||
|
|
||||||
# Remove a node and reload the entry
|
# Remove a node and reload the entry
|
||||||
old_node = nodes.pop(13)
|
old_node = nodes.pop(13)
|
||||||
@ -928,7 +928,7 @@ async def test_removed_device(hass, client, multiple_devices, integration):
|
|||||||
device_entries = dr.async_entries_for_config_entry(dev_reg, integration.entry_id)
|
device_entries = dr.async_entries_for_config_entry(dev_reg, integration.entry_id)
|
||||||
assert len(device_entries) == 1
|
assert len(device_entries) == 1
|
||||||
entity_entries = er.async_entries_for_config_entry(ent_reg, integration.entry_id)
|
entity_entries = er.async_entries_for_config_entry(ent_reg, integration.entry_id)
|
||||||
assert len(entity_entries) == 15
|
assert len(entity_entries) == 16
|
||||||
assert dev_reg.async_get_device({get_device_id(client, old_node)}) is None
|
assert dev_reg.async_get_device({get_device_id(client, old_node)}) is None
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
"""Test the Z-Wave JS sensor platform."""
|
"""Test the Z-Wave JS sensor platform."""
|
||||||
|
from zwave_js_server.event import Event
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
DEVICE_CLASS_HUMIDITY,
|
DEVICE_CLASS_HUMIDITY,
|
||||||
@ -85,3 +87,47 @@ async def test_config_parameter_sensor(hass, lock_id_lock_as_id150, integration)
|
|||||||
entity_entry = ent_reg.async_get(ID_LOCK_CONFIG_PARAMETER_SENSOR)
|
entity_entry = ent_reg.async_get(ID_LOCK_CONFIG_PARAMETER_SENSOR)
|
||||||
assert entity_entry
|
assert entity_entry
|
||||||
assert entity_entry.disabled
|
assert entity_entry.disabled
|
||||||
|
|
||||||
|
|
||||||
|
async def test_node_status_sensor(hass, lock_id_lock_as_id150, integration):
|
||||||
|
"""Test node status sensor is created and gets updated on node state changes."""
|
||||||
|
NODE_STATUS_ENTITY = "sensor.z_wave_module_for_id_lock_150_and_101_node_status"
|
||||||
|
node = lock_id_lock_as_id150
|
||||||
|
ent_reg = er.async_get(hass)
|
||||||
|
entity_entry = ent_reg.async_get(NODE_STATUS_ENTITY)
|
||||||
|
assert entity_entry.disabled
|
||||||
|
assert entity_entry.disabled_by == er.DISABLED_INTEGRATION
|
||||||
|
updated_entry = ent_reg.async_update_entity(
|
||||||
|
entity_entry.entity_id, **{"disabled_by": None}
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.config_entries.async_reload(integration.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert not updated_entry.disabled
|
||||||
|
assert hass.states.get(NODE_STATUS_ENTITY).state == "alive"
|
||||||
|
|
||||||
|
# Test transitions work
|
||||||
|
event = Event(
|
||||||
|
"dead", data={"source": "node", "event": "dead", "nodeId": node.node_id}
|
||||||
|
)
|
||||||
|
node.receive_event(event)
|
||||||
|
assert hass.states.get(NODE_STATUS_ENTITY).state == "dead"
|
||||||
|
|
||||||
|
event = Event(
|
||||||
|
"wake up", data={"source": "node", "event": "wake up", "nodeId": node.node_id}
|
||||||
|
)
|
||||||
|
node.receive_event(event)
|
||||||
|
assert hass.states.get(NODE_STATUS_ENTITY).state == "awake"
|
||||||
|
|
||||||
|
event = Event(
|
||||||
|
"sleep", data={"source": "node", "event": "sleep", "nodeId": node.node_id}
|
||||||
|
)
|
||||||
|
node.receive_event(event)
|
||||||
|
assert hass.states.get(NODE_STATUS_ENTITY).state == "asleep"
|
||||||
|
|
||||||
|
event = Event(
|
||||||
|
"alive", data={"source": "node", "event": "alive", "nodeId": node.node_id}
|
||||||
|
)
|
||||||
|
node.receive_event(event)
|
||||||
|
assert hass.states.get(NODE_STATUS_ENTITY).state == "alive"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user