Add value notification events to zwave_js integration (#45814)

This commit is contained in:
Marcel van der Veldt 2021-02-01 23:47:58 +01:00 committed by GitHub
parent 8222eb5e3e
commit b4559a172c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 199 additions and 14 deletions

View file

@ -1,11 +1,11 @@
"""The Z-Wave JS integration.""" """The Z-Wave JS integration."""
import asyncio import asyncio
import logging import logging
from typing import Tuple
from async_timeout import timeout from async_timeout import timeout
from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.node import Node as ZwaveNode
from zwave_js_server.model.value import ValueNotification
from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.components.hassio.handler import HassioAPIError
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -18,14 +18,28 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send
from .api import async_register_api from .api import async_register_api
from .const import ( from .const import (
ATTR_COMMAND_CLASS,
ATTR_COMMAND_CLASS_NAME,
ATTR_DEVICE_ID,
ATTR_DOMAIN,
ATTR_ENDPOINT,
ATTR_HOME_ID,
ATTR_LABEL,
ATTR_NODE_ID,
ATTR_PROPERTY_KEY_NAME,
ATTR_PROPERTY_NAME,
ATTR_TYPE,
ATTR_VALUE,
CONF_INTEGRATION_CREATED_ADDON, CONF_INTEGRATION_CREATED_ADDON,
DATA_CLIENT, DATA_CLIENT,
DATA_UNSUBSCRIBE, DATA_UNSUBSCRIBE,
DOMAIN, DOMAIN,
EVENT_DEVICE_ADDED_TO_REGISTRY, EVENT_DEVICE_ADDED_TO_REGISTRY,
PLATFORMS, PLATFORMS,
ZWAVE_JS_EVENT,
) )
from .discovery import async_discover_values from .discovery import async_discover_values
from .entity import get_device_id
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
CONNECT_TIMEOUT = 10 CONNECT_TIMEOUT = 10
@ -37,12 +51,6 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool:
return True return True
@callback
def get_device_id(client: ZwaveClient, node: ZwaveNode) -> Tuple[str, str]:
"""Get device registry identifier for Z-Wave node."""
return (DOMAIN, f"{client.driver.controller.home_id}-{node.node_id}")
@callback @callback
def register_node_in_dev_reg( def register_node_in_dev_reg(
hass: HomeAssistant, hass: HomeAssistant,
@ -106,6 +114,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async_dispatcher_send( async_dispatcher_send(
hass, f"{DOMAIN}_{entry.entry_id}_add_{disc_info.platform}", disc_info hass, f"{DOMAIN}_{entry.entry_id}_add_{disc_info.platform}", disc_info
) )
# add listener for stateless node events (value notification)
node.on(
"value notification",
lambda event: async_on_value_notification(event["value_notification"]),
)
@callback @callback
def async_on_node_added(node: ZwaveNode) -> None: def async_on_node_added(node: ZwaveNode) -> None:
@ -134,6 +147,31 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# note: removal of entity registry is handled by core # note: removal of entity registry is handled by core
dev_reg.async_remove_device(device.id) dev_reg.async_remove_device(device.id)
@callback
def async_on_value_notification(notification: ValueNotification) -> None:
"""Relay stateless value notification events from Z-Wave nodes to hass."""
device = dev_reg.async_get_device({get_device_id(client, notification.node)})
value = notification.value
if notification.metadata.states:
value = notification.metadata.states.get(str(value), value)
hass.bus.async_fire(
ZWAVE_JS_EVENT,
{
ATTR_TYPE: "value_notification",
ATTR_DOMAIN: DOMAIN,
ATTR_NODE_ID: notification.node.node_id,
ATTR_HOME_ID: client.driver.controller.home_id,
ATTR_ENDPOINT: notification.endpoint,
ATTR_DEVICE_ID: device.id,
ATTR_COMMAND_CLASS: notification.command_class,
ATTR_COMMAND_CLASS_NAME: notification.command_class_name,
ATTR_LABEL: notification.metadata.label,
ATTR_PROPERTY_NAME: notification.property_name,
ATTR_PROPERTY_KEY_NAME: notification.property_key_name,
ATTR_VALUE: value,
},
)
async def handle_ha_shutdown(event: Event) -> None: async def handle_ha_shutdown(event: Event) -> None:
"""Handle HA shutdown.""" """Handle HA shutdown."""
await client.disconnect() await client.disconnect()

View file

@ -17,3 +17,18 @@ DATA_CLIENT = "client"
DATA_UNSUBSCRIBE = "unsubs" DATA_UNSUBSCRIBE = "unsubs"
EVENT_DEVICE_ADDED_TO_REGISTRY = f"{DOMAIN}_device_added_to_registry" EVENT_DEVICE_ADDED_TO_REGISTRY = f"{DOMAIN}_device_added_to_registry"
# constants for events
ZWAVE_JS_EVENT = f"{DOMAIN}_event"
ATTR_NODE_ID = "node_id"
ATTR_HOME_ID = "home_id"
ATTR_ENDPOINT = "endpoint"
ATTR_LABEL = "label"
ATTR_VALUE = "value"
ATTR_COMMAND_CLASS = "command_class"
ATTR_COMMAND_CLASS_NAME = "command_class_name"
ATTR_TYPE = "type"
ATTR_DOMAIN = "domain"
ATTR_DEVICE_ID = "device_id"
ATTR_PROPERTY_NAME = "property_name"
ATTR_PROPERTY_KEY_NAME = "property_key_name"

View file

@ -1,9 +1,10 @@
"""Generic Z-Wave Entity Class.""" """Generic Z-Wave Entity Class."""
import logging import logging
from typing import Optional, Union from typing import Optional, Tuple, Union
from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.model.node import Node as ZwaveNode
from zwave_js_server.model.value import Value as ZwaveValue, get_value_id from zwave_js_server.model.value import Value as ZwaveValue, get_value_id
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -19,6 +20,12 @@ LOGGER = logging.getLogger(__name__)
EVENT_VALUE_UPDATED = "value updated" EVENT_VALUE_UPDATED = "value updated"
@callback
def get_device_id(client: ZwaveClient, node: ZwaveNode) -> Tuple[str, str]:
"""Get device registry identifier for Z-Wave node."""
return (DOMAIN, f"{client.driver.controller.home_id}-{node.node_id}")
class ZWaveBaseEntity(Entity): class ZWaveBaseEntity(Entity):
"""Generic Entity Class for a Z-Wave Device.""" """Generic Entity Class for a Z-Wave Device."""
@ -60,12 +67,7 @@ class ZWaveBaseEntity(Entity):
"""Return device information for the device registry.""" """Return device information for the device registry."""
# device is precreated in main handler # device is precreated in main handler
return { return {
"identifiers": { "identifiers": {get_device_id(self.client, self.info.node)},
(
DOMAIN,
f"{self.client.driver.controller.home_id}-{self.info.node.node_id}",
)
},
} }
@property @property

View file

@ -0,0 +1,130 @@
"""Test Z-Wave JS (value notification) events."""
from zwave_js_server.event import Event
from tests.common import async_capture_events
async def test_scenes(hass, hank_binary_switch, integration, client):
"""Test scene events."""
# just pick a random node to fake the value notification events
node = hank_binary_switch
events = async_capture_events(hass, "zwave_js_event")
# Publish fake Basic Set value notification
event = Event(
type="value notification",
data={
"source": "node",
"event": "value notification",
"nodeId": 32,
"args": {
"commandClassName": "Basic",
"commandClass": 32,
"endpoint": 0,
"property": "event",
"propertyName": "event",
"value": 255,
"metadata": {
"type": "number",
"readable": True,
"writeable": False,
"min": 0,
"max": 255,
"label": "Event value",
},
"ccVersion": 1,
},
},
)
node.receive_event(event)
# wait for the event
await hass.async_block_till_done()
assert len(events) == 1
assert events[0].data["home_id"] == client.driver.controller.home_id
assert events[0].data["node_id"] == 32
assert events[0].data["endpoint"] == 0
assert events[0].data["command_class"] == 32
assert events[0].data["command_class_name"] == "Basic"
assert events[0].data["label"] == "Event value"
assert events[0].data["value"] == 255
# Publish fake Scene Activation value notification
event = Event(
type="value notification",
data={
"source": "node",
"event": "value notification",
"nodeId": 32,
"args": {
"commandClassName": "Scene Activation",
"commandClass": 43,
"endpoint": 0,
"property": "SceneID",
"propertyName": "SceneID",
"value": 16,
"metadata": {
"type": "number",
"readable": True,
"writeable": False,
"min": 0,
"max": 255,
"label": "Scene ID",
},
"ccVersion": 3,
},
},
)
node.receive_event(event)
# wait for the event
await hass.async_block_till_done()
assert len(events) == 2
assert events[1].data["command_class"] == 43
assert events[1].data["command_class_name"] == "Scene Activation"
assert events[1].data["label"] == "Scene ID"
assert events[1].data["value"] == 16
# Publish fake Central Scene value notification
event = Event(
type="value notification",
data={
"source": "node",
"event": "value notification",
"nodeId": 32,
"args": {
"commandClassName": "Central Scene",
"commandClass": 91,
"endpoint": 0,
"property": "scene",
"propertyKey": "001",
"propertyName": "scene",
"propertyKeyName": "001",
"value": 4,
"metadata": {
"type": "number",
"readable": True,
"writeable": False,
"min": 0,
"max": 255,
"label": "Scene 001",
"states": {
"0": "KeyPressed",
"1": "KeyReleased",
"2": "KeyHeldDown",
"3": "KeyPressed2x",
"4": "KeyPressed3x",
"5": "KeyPressed4x",
"6": "KeyPressed5x",
},
},
"ccVersion": 3,
},
},
)
node.receive_event(event)
# wait for the event
await hass.async_block_till_done()
assert len(events) == 3
assert events[2].data["command_class"] == 91
assert events[2].data["command_class_name"] == "Central Scene"
assert events[2].data["label"] == "Scene 001"
assert events[2].data["value"] == "KeyPressed3x"