Add zwave_js device triggers (#51968)

* Initial support for zwave_js device triggers

* lint

* Add node status changed trigger

* comments

* create helper function and simplify trigger logic

* simplify code

* fix exception

* remove unused type ignore

* switch to append to make future changes easier

* make exception consistent

* Add state config schema validation

* comment

* remove 0 from falsy check

* increase test coverage

* typos

* Add central scene and scene activation value notification triggers

* reorder things for readability and enumerate node statuses

* Add support for Basic CC value notifications

* fix schemas since additional fields on triggers aren't very flexible

* pylint

* remove extra logger statement

* fix comment

* dont use get when we know key will be available in dict

* tweak text

* use better schema for required extra fields that are ints

* rename trigger types to make them easier to parse

* fix strings

* missed renaming of one trigger type

* typo

* Fix strings

* reduce complexity

* Use Al's suggestion for strings

* add additional failure test cases

* remove errant logging statement

* make CC required

* raise vol.Invalid when value ID isn't legit to prepare for next PR

* Use helper function

* fix tests

* black
This commit is contained in:
Raman Gupta 2021-07-14 14:14:36 -04:00 committed by GitHub
parent 4d711898c7
commit dd908caeba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1970 additions and 1 deletions

View File

@ -0,0 +1,371 @@
"""Provides device triggers for Z-Wave JS."""
from __future__ import annotations
import voluptuous as vol
from zwave_js_server.const import CommandClass
from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
from homeassistant.components.homeassistant.triggers import event, state
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_DOMAIN,
CONF_ENTITY_ID,
CONF_PLATFORM,
CONF_TYPE,
)
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
config_validation as cv,
device_registry,
entity_registry,
)
from homeassistant.helpers.typing import ConfigType
from .const import (
ATTR_COMMAND_CLASS,
ATTR_DATA_TYPE,
ATTR_ENDPOINT,
ATTR_EVENT,
ATTR_EVENT_LABEL,
ATTR_EVENT_TYPE,
ATTR_LABEL,
ATTR_PROPERTY,
ATTR_PROPERTY_KEY,
ATTR_TYPE,
ATTR_VALUE,
ATTR_VALUE_RAW,
DOMAIN,
ZWAVE_JS_NOTIFICATION_EVENT,
ZWAVE_JS_VALUE_NOTIFICATION_EVENT,
)
from .helpers import (
async_get_node_from_device_id,
async_get_node_status_sensor_entity_id,
get_zwave_value_from_config,
)
CONF_SUBTYPE = "subtype"
CONF_VALUE_ID = "value_id"
# Trigger types
ENTRY_CONTROL_NOTIFICATION = "event.notification.entry_control"
NOTIFICATION_NOTIFICATION = "event.notification.notification"
BASIC_VALUE_NOTIFICATION = "event.value_notification.basic"
CENTRAL_SCENE_VALUE_NOTIFICATION = "event.value_notification.central_scene"
SCENE_ACTIVATION_VALUE_NOTIFICATION = "event.value_notification.scene_activation"
NODE_STATUS = "state.node_status"
NOTIFICATION_EVENT_CC_MAPPINGS = (
(ENTRY_CONTROL_NOTIFICATION, CommandClass.ENTRY_CONTROL),
(NOTIFICATION_NOTIFICATION, CommandClass.NOTIFICATION),
)
# Event based trigger schemas
BASE_EVENT_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(ATTR_COMMAND_CLASS): vol.In([cc.value for cc in CommandClass]),
}
)
NOTIFICATION_NOTIFICATION_SCHEMA = BASE_EVENT_SCHEMA.extend(
{
vol.Required(CONF_TYPE): NOTIFICATION_NOTIFICATION,
vol.Optional(f"{ATTR_TYPE}."): vol.Coerce(int),
vol.Optional(ATTR_LABEL): cv.string,
vol.Optional(ATTR_EVENT): vol.Coerce(int),
vol.Optional(ATTR_EVENT_LABEL): cv.string,
}
)
ENTRY_CONTROL_NOTIFICATION_SCHEMA = BASE_EVENT_SCHEMA.extend(
{
vol.Required(CONF_TYPE): ENTRY_CONTROL_NOTIFICATION,
vol.Optional(ATTR_EVENT_TYPE): vol.Coerce(int),
vol.Optional(ATTR_DATA_TYPE): vol.Coerce(int),
}
)
BASE_VALUE_NOTIFICATION_EVENT_SCHEMA = BASE_EVENT_SCHEMA.extend(
{
vol.Required(ATTR_PROPERTY): vol.Any(int, str),
vol.Required(ATTR_PROPERTY_KEY): vol.Any(None, int, str),
vol.Required(ATTR_ENDPOINT): vol.Coerce(int),
vol.Required(ATTR_VALUE): vol.Coerce(int),
vol.Required(CONF_SUBTYPE): cv.string,
}
)
BASIC_VALUE_NOTIFICATION_SCHEMA = BASE_VALUE_NOTIFICATION_EVENT_SCHEMA.extend(
{
vol.Required(CONF_TYPE): BASIC_VALUE_NOTIFICATION,
}
)
CENTRAL_SCENE_VALUE_NOTIFICATION_SCHEMA = BASE_VALUE_NOTIFICATION_EVENT_SCHEMA.extend(
{
vol.Required(CONF_TYPE): CENTRAL_SCENE_VALUE_NOTIFICATION,
}
)
SCENE_ACTIVATION_VALUE_NOTIFICATION_SCHEMA = (
BASE_VALUE_NOTIFICATION_EVENT_SCHEMA.extend(
{
vol.Required(CONF_TYPE): SCENE_ACTIVATION_VALUE_NOTIFICATION,
}
)
)
# State based trigger schemas
BASE_STATE_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_ENTITY_ID): cv.entity_id,
}
)
NODE_STATUSES = ["asleep", "awake", "dead", "alive"]
NODE_STATUS_SCHEMA = BASE_STATE_SCHEMA.extend(
{
vol.Required(CONF_TYPE): NODE_STATUS,
vol.Optional(state.CONF_FROM): vol.In(NODE_STATUSES),
vol.Optional(state.CONF_TO): vol.In(NODE_STATUSES),
vol.Optional(state.CONF_FOR): cv.positive_time_period_dict,
}
)
TRIGGER_SCHEMA = vol.Any(
ENTRY_CONTROL_NOTIFICATION_SCHEMA,
NOTIFICATION_NOTIFICATION_SCHEMA,
BASIC_VALUE_NOTIFICATION_SCHEMA,
CENTRAL_SCENE_VALUE_NOTIFICATION_SCHEMA,
SCENE_ACTIVATION_VALUE_NOTIFICATION_SCHEMA,
NODE_STATUS_SCHEMA,
)
async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]:
"""List device triggers for Z-Wave JS devices."""
dev_reg = device_registry.async_get(hass)
node = async_get_node_from_device_id(hass, device_id, dev_reg)
triggers = []
base_trigger = {
CONF_PLATFORM: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
}
# We can add a node status trigger if the node status sensor is enabled
ent_reg = entity_registry.async_get(hass)
entity_id = async_get_node_status_sensor_entity_id(
hass, device_id, ent_reg, dev_reg
)
if (entity := ent_reg.async_get(entity_id)) is not None and not entity.disabled:
triggers.append(
{**base_trigger, CONF_TYPE: NODE_STATUS, CONF_ENTITY_ID: entity_id}
)
# Handle notification event triggers
triggers.extend(
[
{**base_trigger, CONF_TYPE: event_type, ATTR_COMMAND_CLASS: command_class}
for event_type, command_class in NOTIFICATION_EVENT_CC_MAPPINGS
if any(cc.id == command_class for cc in node.command_classes)
]
)
# Handle central scene value notification event triggers
triggers.extend(
[
{
**base_trigger,
CONF_TYPE: CENTRAL_SCENE_VALUE_NOTIFICATION,
ATTR_PROPERTY: value.property_,
ATTR_PROPERTY_KEY: value.property_key,
ATTR_ENDPOINT: value.endpoint,
ATTR_COMMAND_CLASS: CommandClass.CENTRAL_SCENE,
CONF_SUBTYPE: f"Endpoint {value.endpoint} Scene {value.property_key}",
}
for value in node.get_command_class_values(
CommandClass.CENTRAL_SCENE
).values()
if value.property_ == "scene"
]
)
# Handle scene activation value notification event triggers
triggers.extend(
[
{
**base_trigger,
CONF_TYPE: SCENE_ACTIVATION_VALUE_NOTIFICATION,
ATTR_PROPERTY: value.property_,
ATTR_PROPERTY_KEY: value.property_key,
ATTR_ENDPOINT: value.endpoint,
ATTR_COMMAND_CLASS: CommandClass.SCENE_ACTIVATION,
CONF_SUBTYPE: f"Endpoint {value.endpoint}",
}
for value in node.get_command_class_values(
CommandClass.SCENE_ACTIVATION
).values()
if value.property_ == "sceneId"
]
)
# Handle basic value notification event triggers
# Nodes will only send Basic CC value notifications if a compatibility flag is set
if node.device_config.compat.get("treatBasicSetAsEvent", False):
triggers.extend(
[
{
**base_trigger,
CONF_TYPE: BASIC_VALUE_NOTIFICATION,
ATTR_PROPERTY: value.property_,
ATTR_PROPERTY_KEY: value.property_key,
ATTR_ENDPOINT: value.endpoint,
ATTR_COMMAND_CLASS: CommandClass.BASIC,
CONF_SUBTYPE: f"Endpoint {value.endpoint}",
}
for value in node.get_command_class_values(CommandClass.BASIC).values()
if value.property_ == "event"
]
)
return triggers
def copy_available_params(
input_dict: dict, output_dict: dict, params: list[str]
) -> None:
"""Copy available params from input into output."""
for param in params:
if (val := input_dict.get(param)) not in ("", None):
output_dict[param] = val
async def async_attach_trigger(
hass: HomeAssistant,
config: ConfigType,
action: AutomationActionType,
automation_info: dict,
) -> CALLBACK_TYPE:
"""Attach a trigger."""
trigger_type = config[CONF_TYPE]
trigger_platform = trigger_type.split(".")[0]
event_data = {CONF_DEVICE_ID: config[CONF_DEVICE_ID]}
event_config = {
event.CONF_PLATFORM: "event",
event.CONF_EVENT_DATA: event_data,
}
if ATTR_COMMAND_CLASS in config:
event_data[ATTR_COMMAND_CLASS] = config[ATTR_COMMAND_CLASS]
# Take input data from automation trigger UI and add it to the trigger we are
# attaching to
if trigger_platform == "event":
if trigger_type == ENTRY_CONTROL_NOTIFICATION:
event_config[event.CONF_EVENT_TYPE] = ZWAVE_JS_NOTIFICATION_EVENT
copy_available_params(config, event_data, [ATTR_EVENT_TYPE, ATTR_DATA_TYPE])
elif trigger_type == NOTIFICATION_NOTIFICATION:
event_config[event.CONF_EVENT_TYPE] = ZWAVE_JS_NOTIFICATION_EVENT
copy_available_params(
config, event_data, [ATTR_LABEL, ATTR_EVENT_LABEL, ATTR_EVENT]
)
if (val := config.get(f"{ATTR_TYPE}.")) not in ("", None):
event_data[ATTR_TYPE] = val
elif trigger_type in (
BASIC_VALUE_NOTIFICATION,
CENTRAL_SCENE_VALUE_NOTIFICATION,
SCENE_ACTIVATION_VALUE_NOTIFICATION,
):
event_config[event.CONF_EVENT_TYPE] = ZWAVE_JS_VALUE_NOTIFICATION_EVENT
copy_available_params(
config, event_data, [ATTR_PROPERTY, ATTR_PROPERTY_KEY, ATTR_ENDPOINT]
)
event_data[ATTR_VALUE_RAW] = config[ATTR_VALUE]
else:
raise HomeAssistantError(f"Unhandled trigger type {trigger_type}")
event_config = event.TRIGGER_SCHEMA(event_config)
return await event.async_attach_trigger(
hass, event_config, action, automation_info, platform_type="device"
)
state_config = {state.CONF_PLATFORM: "state"}
if trigger_platform == "state" and trigger_type == NODE_STATUS:
state_config[state.CONF_ENTITY_ID] = config[CONF_ENTITY_ID]
copy_available_params(
config, state_config, [state.CONF_FOR, state.CONF_FROM, state.CONF_TO]
)
state_config = state.TRIGGER_SCHEMA(state_config)
return await state.async_attach_trigger(
hass, state_config, action, automation_info, platform_type="device"
)
raise HomeAssistantError(f"Unhandled trigger type {trigger_type}")
async def async_get_trigger_capabilities(
hass: HomeAssistant, config: ConfigType
) -> dict[str, vol.Schema]:
"""List trigger capabilities."""
node = async_get_node_from_device_id(hass, config[CONF_DEVICE_ID])
value = (
get_zwave_value_from_config(node, config) if ATTR_PROPERTY in config else None
)
# Add additional fields to the automation trigger UI
if config[CONF_TYPE] == NOTIFICATION_NOTIFICATION:
return {
"extra_fields": vol.Schema(
{
vol.Optional(f"{ATTR_TYPE}."): cv.string,
vol.Optional(ATTR_LABEL): cv.string,
vol.Optional(ATTR_EVENT): cv.string,
vol.Optional(ATTR_EVENT_LABEL): cv.string,
}
)
}
if config[CONF_TYPE] == ENTRY_CONTROL_NOTIFICATION:
return {
"extra_fields": vol.Schema(
{
vol.Optional(ATTR_EVENT_TYPE): cv.string,
vol.Optional(ATTR_DATA_TYPE): cv.string,
}
)
}
if config[CONF_TYPE] == NODE_STATUS:
return {
"extra_fields": vol.Schema(
{
vol.Optional(state.CONF_FROM): vol.In(NODE_STATUSES),
vol.Optional(state.CONF_TO): vol.In(NODE_STATUSES),
vol.Optional(state.CONF_FOR): cv.positive_time_period_dict,
}
)
}
if config[CONF_TYPE] in (
BASIC_VALUE_NOTIFICATION,
CENTRAL_SCENE_VALUE_NOTIFICATION,
SCENE_ACTIVATION_VALUE_NOTIFICATION,
):
if value.metadata.states:
value_schema = vol.In({int(k): v for k, v in value.metadata.states.items()})
else:
value_schema = vol.All(
vol.Coerce(int),
vol.Range(min=value.metadata.min, max=value.metadata.max),
)
return {"extra_fields": vol.Schema({vol.Required(ATTR_VALUE): value_schema})}
return {}

View File

@ -8,9 +8,11 @@ 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 homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import __version__ as HA_VERSION
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import (
DeviceRegistry,
async_get as async_get_dev_reg,
@ -175,3 +177,35 @@ def get_zwave_value_from_config(node: ZwaveNode, config: ConfigType) -> ZwaveVal
if value_id not in node.values:
raise vol.Invalid(f"Value {value_id} can't be found on node {node}")
return node.values[value_id]
@callback
def async_get_node_status_sensor_entity_id(
hass: HomeAssistant,
device_id: str,
ent_reg: EntityRegistry | None = None,
dev_reg: DeviceRegistry | None = None,
) -> str:
"""Get the node status sensor entity ID for a given Z-Wave JS device."""
if not ent_reg:
ent_reg = async_get_ent_reg(hass)
if not dev_reg:
dev_reg = async_get_dev_reg(hass)
device = dev_reg.async_get(device_id)
if not device:
raise HomeAssistantError("Invalid Device ID provided")
entry_id = next(entry_id for entry_id in device.config_entries)
client = hass.data[DOMAIN][entry_id][DATA_CLIENT]
node = async_get_node_from_device_id(hass, device_id, dev_reg)
entity_id = ent_reg.async_get_entity_id(
SENSOR_DOMAIN,
DOMAIN,
f"{client.driver.controller.home_id}.{node.node_id}.node_status",
)
if not entity_id:
raise HomeAssistantError(
"Node status sensor entity not found. Device may not be a zwave_js device"
)
return entity_id

View File

@ -99,6 +99,14 @@
}
},
"device_automation": {
"trigger_type": {
"event.notification.entry_control": "Sent an Entry Control notification",
"event.notification.notification": "Sent a notification",
"event.value_notification.basic": "Basic CC event on {subtype}",
"event.value_notification.central_scene": "Central Scene action on {subtype}",
"event.value_notification.scene_activation": "Scene Activation on {subtype}",
"state.node_status": "Node status changed"
},
"condition_type": {
"node_status": "Node status",
"config_parameter": "Config parameter {subtype} value",

View File

@ -56,6 +56,14 @@
"config_parameter": "Config parameter {subtype} value",
"node_status": "Node status",
"value": "Current value of a Z-Wave Value"
},
"trigger_type": {
"event.notification.entry_control": "Sent an Entry Control notification",
"event.notification.notification": "Sent a notification",
"event.value_notification.basic": "Basic CC event on {subtype}",
"event.value_notification.central_scene": "Central Scene action on {subtype}",
"event.value_notification.scene_activation": "Scene Activation on {subtype}",
"state.node_status": "Node status changed"
}
},
"options": {

View File

@ -429,6 +429,12 @@ def wallmote_central_scene_state_fixture():
return json.loads(load_fixture("zwave_js/wallmote_central_scene_state.json"))
@pytest.fixture(name="ge_in_wall_dimmer_switch_state", scope="session")
def ge_in_wall_dimmer_switch_state_fixture():
"""Load the ge in-wall dimmer switch node state fixture data."""
return json.loads(load_fixture("zwave_js/ge_in_wall_dimmer_switch_state.json"))
@pytest.fixture(name="aeotec_zw164_siren_state", scope="session")
def aeotec_zw164_siren_state_fixture():
"""Load the aeotec zw164 siren node state fixture data."""
@ -795,6 +801,14 @@ def wallmote_central_scene_fixture(client, wallmote_central_scene_state):
return node
@pytest.fixture(name="ge_in_wall_dimmer_switch")
def ge_in_wall_dimmer_switch_fixture(client, ge_in_wall_dimmer_switch_state):
"""Mock a ge in-wall dimmer switch scene node."""
node = Node(client, copy.deepcopy(ge_in_wall_dimmer_switch_state))
client.driver.controller.nodes[node.node_id] = node
return node
@pytest.fixture(name="aeotec_zw164_siren")
def aeotec_zw164_siren_fixture(client, aeotec_zw164_siren_state):
"""Mock a wallmote central scene node."""

View File

@ -0,0 +1,831 @@
"""The tests for Z-Wave JS device triggers."""
from unittest.mock import patch
import pytest
import voluptuous_serialize
from zwave_js_server.const import CommandClass
from zwave_js_server.event import Event
from zwave_js_server.model.node import Node
from homeassistant.components import automation
from homeassistant.components.zwave_js import DOMAIN, device_trigger
from homeassistant.components.zwave_js.device_trigger import (
async_attach_trigger,
async_get_trigger_capabilities,
)
from homeassistant.components.zwave_js.helpers import (
async_get_node_status_sensor_entity_id,
)
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import (
async_entries_for_config_entry,
async_get as async_get_dev_reg,
)
from homeassistant.helpers.entity_registry import async_get as async_get_ent_reg
from homeassistant.setup import async_setup_component
from tests.common import (
assert_lists_same,
async_get_device_automations,
async_mock_service,
)
@pytest.fixture
def calls(hass):
"""Track calls to a mock service."""
return async_mock_service(hass, "test", "automation")
async def test_get_notification_notification_triggers(
hass, client, lock_schlage_be469, integration
):
"""Test we get the expected triggers from a zwave_js device with the Notification CC."""
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
expected_trigger = {
"platform": "device",
"domain": DOMAIN,
"type": "event.notification.notification",
"device_id": device.id,
"command_class": CommandClass.NOTIFICATION,
}
triggers = await async_get_device_automations(hass, "trigger", device.id)
assert expected_trigger in triggers
async def test_if_notification_notification_fires(
hass, client, lock_schlage_be469, integration, calls
):
"""Test for event.notification.notification trigger firing."""
node: Node = lock_schlage_be469
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": device.id,
"type": "event.notification.notification",
"command_class": CommandClass.NOTIFICATION.value,
"type.": 6,
"event": 5,
"label": "Access Control",
},
"action": {
"service": "test.automation",
"data_template": {
"some": (
"event.notification.notification - "
"{{ trigger.platform}} - "
"{{ trigger.event.event_type}} - "
"{{ trigger.event.data.command_class }}"
)
},
},
},
]
},
)
# Publish fake Notification CC notification
event = Event(
type="notification",
data={
"source": "node",
"event": "notification",
"nodeId": node.node_id,
"ccId": 113,
"args": {
"type": 6,
"event": 5,
"label": "Access Control",
"eventLabel": "Keypad lock operation",
"parameters": {"userId": 1},
},
},
)
node.receive_event(event)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data[
"some"
] == "event.notification.notification - device - zwave_js_notification - {}".format(
CommandClass.NOTIFICATION
)
async def test_get_trigger_capabilities_notification_notification(
hass, client, lock_schlage_be469, integration
):
"""Test we get the expected capabilities from a notification.notification trigger."""
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
capabilities = await device_trigger.async_get_trigger_capabilities(
hass,
{
"platform": "device",
"domain": DOMAIN,
"device_id": device.id,
"type": "event.notification.notification",
"command_class": CommandClass.NOTIFICATION.value,
},
)
assert capabilities and "extra_fields" in capabilities
assert_lists_same(
voluptuous_serialize.convert(
capabilities["extra_fields"], custom_serializer=cv.custom_serializer
),
[
{"name": "type.", "optional": True, "type": "string"},
{"name": "label", "optional": True, "type": "string"},
{"name": "event", "optional": True, "type": "string"},
{"name": "event_label", "optional": True, "type": "string"},
],
)
async def test_if_entry_control_notification_fires(
hass, client, lock_schlage_be469, integration, calls
):
"""Test for notification.entry_control trigger firing."""
node: Node = lock_schlage_be469
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": device.id,
"type": "event.notification.entry_control",
"command_class": CommandClass.ENTRY_CONTROL.value,
"event_type": 5,
"data_type": 2,
},
"action": {
"service": "test.automation",
"data_template": {
"some": (
"event.notification.notification - "
"{{ trigger.platform}} - "
"{{ trigger.event.event_type}} - "
"{{ trigger.event.data.command_class }}"
)
},
},
},
]
},
)
# Publish fake Entry Control CC notification
event = Event(
type="notification",
data={
"source": "node",
"event": "notification",
"nodeId": node.node_id,
"ccId": 111,
"args": {"eventType": 5, "dataType": 2, "eventData": "555"},
},
)
node.receive_event(event)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data[
"some"
] == "event.notification.notification - device - zwave_js_notification - {}".format(
CommandClass.ENTRY_CONTROL
)
async def test_get_trigger_capabilities_entry_control_notification(
hass, client, lock_schlage_be469, integration
):
"""Test we get the expected capabilities from a notification.entry_control trigger."""
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
capabilities = await device_trigger.async_get_trigger_capabilities(
hass,
{
"platform": "device",
"domain": DOMAIN,
"device_id": device.id,
"type": "event.notification.entry_control",
"command_class": CommandClass.ENTRY_CONTROL.value,
},
)
assert capabilities and "extra_fields" in capabilities
assert_lists_same(
voluptuous_serialize.convert(
capabilities["extra_fields"], custom_serializer=cv.custom_serializer
),
[
{"name": "event_type", "optional": True, "type": "string"},
{"name": "data_type", "optional": True, "type": "string"},
],
)
async def test_get_node_status_triggers(hass, client, lock_schlage_be469, integration):
"""Test we get the expected triggers from a device with node status sensor enabled."""
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
ent_reg = async_get_ent_reg(hass)
entity_id = async_get_node_status_sensor_entity_id(
hass, device.id, ent_reg, dev_reg
)
ent_reg.async_update_entity(entity_id, **{"disabled_by": None})
await hass.config_entries.async_reload(integration.entry_id)
await hass.async_block_till_done()
expected_trigger = {
"platform": "device",
"domain": DOMAIN,
"type": "state.node_status",
"device_id": device.id,
"entity_id": entity_id,
}
triggers = await async_get_device_automations(hass, "trigger", device.id)
assert expected_trigger in triggers
async def test_if_node_status_change_fires(
hass, client, lock_schlage_be469, integration, calls
):
"""Test for node_status trigger firing."""
node: Node = lock_schlage_be469
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
ent_reg = async_get_ent_reg(hass)
entity_id = async_get_node_status_sensor_entity_id(
hass, device.id, ent_reg, dev_reg
)
ent_reg.async_update_entity(entity_id, **{"disabled_by": None})
await hass.config_entries.async_reload(integration.entry_id)
await hass.async_block_till_done()
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": device.id,
"entity_id": entity_id,
"type": "state.node_status",
"from": "alive",
},
"action": {
"service": "test.automation",
"data_template": {
"some": (
"state.node_status - "
"{{ trigger.platform}} - "
"{{ trigger.from_state.state }}"
)
},
},
},
]
},
)
# Test status change
event = Event(
"dead", data={"source": "node", "event": "dead", "nodeId": node.node_id}
)
node.receive_event(event)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "state.node_status - device - alive"
async def test_get_trigger_capabilities_node_status(
hass, client, lock_schlage_be469, integration
):
"""Test we get the expected capabilities from a node_status trigger."""
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
ent_reg = async_get_ent_reg(hass)
entity_id = async_get_node_status_sensor_entity_id(
hass, device.id, ent_reg, dev_reg
)
ent_reg.async_update_entity(entity_id, **{"disabled_by": None})
await hass.config_entries.async_reload(integration.entry_id)
await hass.async_block_till_done()
capabilities = await device_trigger.async_get_trigger_capabilities(
hass,
{
"platform": "device",
"domain": DOMAIN,
"device_id": device.id,
"entity_id": entity_id,
"type": "state.node_status",
},
)
assert capabilities and "extra_fields" in capabilities
assert voluptuous_serialize.convert(
capabilities["extra_fields"], custom_serializer=cv.custom_serializer
) == [
{
"name": "from",
"optional": True,
"options": [
("asleep", "asleep"),
("awake", "awake"),
("dead", "dead"),
("alive", "alive"),
],
"type": "select",
},
{
"name": "to",
"optional": True,
"options": [
("asleep", "asleep"),
("awake", "awake"),
("dead", "dead"),
("alive", "alive"),
],
"type": "select",
},
{"name": "for", "optional": True, "type": "positive_time_period_dict"},
]
async def test_get_basic_value_notification_triggers(
hass, client, ge_in_wall_dimmer_switch, integration
):
"""Test we get the expected triggers from a zwave_js device with the Basic CC."""
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
expected_trigger = {
"platform": "device",
"domain": DOMAIN,
"type": "event.value_notification.basic",
"device_id": device.id,
"command_class": CommandClass.BASIC,
"property": "event",
"property_key": None,
"endpoint": 0,
"subtype": "Endpoint 0",
}
triggers = await async_get_device_automations(hass, "trigger", device.id)
assert expected_trigger in triggers
async def test_if_basic_value_notification_fires(
hass, client, ge_in_wall_dimmer_switch, integration, calls
):
"""Test for event.value_notification.basic trigger firing."""
node: Node = ge_in_wall_dimmer_switch
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"type": "event.value_notification.basic",
"device_id": device.id,
"command_class": CommandClass.BASIC.value,
"property": "event",
"property_key": None,
"endpoint": 0,
"subtype": "Endpoint 0",
"value": 0,
},
"action": {
"service": "test.automation",
"data_template": {
"some": (
"event.value_notification.basic - "
"{{ trigger.platform}} - "
"{{ trigger.event.event_type}} - "
"{{ trigger.event.data.command_class }}"
)
},
},
},
]
},
)
# Publish fake Basic CC value notification
event = Event(
type="value notification",
data={
"source": "node",
"event": "value notification",
"nodeId": node.node_id,
"args": {
"commandClassName": "Basic",
"commandClass": 32,
"endpoint": 0,
"property": "event",
"propertyName": "event",
"value": 0,
"metadata": {
"type": "number",
"readable": True,
"writeable": False,
"label": "Event value",
"min": 0,
"max": 255,
},
"ccVersion": 1,
},
},
)
node.receive_event(event)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data[
"some"
] == "event.value_notification.basic - device - zwave_js_value_notification - {}".format(
CommandClass.BASIC
)
async def test_get_trigger_capabilities_basic_value_notification(
hass, client, ge_in_wall_dimmer_switch, integration
):
"""Test we get the expected capabilities from a value_notification.basic trigger."""
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
capabilities = await device_trigger.async_get_trigger_capabilities(
hass,
{
"platform": "device",
"domain": DOMAIN,
"type": "event.value_notification.basic",
"device_id": device.id,
"command_class": CommandClass.BASIC.value,
"property": "event",
"property_key": None,
"endpoint": 0,
"subtype": "Endpoint 0",
},
)
assert capabilities and "extra_fields" in capabilities
assert voluptuous_serialize.convert(
capabilities["extra_fields"], custom_serializer=cv.custom_serializer
) == [
{
"name": "value",
"required": True,
"type": "integer",
"valueMin": 0,
"valueMax": 255,
}
]
async def test_get_central_scene_value_notification_triggers(
hass, client, wallmote_central_scene, integration
):
"""Test we get the expected triggers from a zwave_js device with the Central Scene CC."""
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
expected_trigger = {
"platform": "device",
"domain": DOMAIN,
"type": "event.value_notification.central_scene",
"device_id": device.id,
"command_class": CommandClass.CENTRAL_SCENE,
"property": "scene",
"property_key": "001",
"endpoint": 0,
"subtype": "Endpoint 0 Scene 001",
}
triggers = await async_get_device_automations(hass, "trigger", device.id)
assert expected_trigger in triggers
async def test_if_central_scene_value_notification_fires(
hass, client, wallmote_central_scene, integration, calls
):
"""Test for event.value_notification.central_scene trigger firing."""
node: Node = wallmote_central_scene
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": device.id,
"type": "event.value_notification.central_scene",
"command_class": CommandClass.CENTRAL_SCENE.value,
"property": "scene",
"property_key": "001",
"endpoint": 0,
"subtype": "Endpoint 0 Scene 001",
"value": 0,
},
"action": {
"service": "test.automation",
"data_template": {
"some": (
"event.value_notification.central_scene - "
"{{ trigger.platform}} - "
"{{ trigger.event.event_type}} - "
"{{ trigger.event.data.command_class }}"
)
},
},
},
]
},
)
# Publish fake Central Scene CC value notification
event = Event(
type="value notification",
data={
"source": "node",
"event": "value notification",
"nodeId": node.node_id,
"args": {
"commandClassName": "Central Scene",
"commandClass": 91,
"endpoint": 0,
"property": "scene",
"propertyName": "scene",
"propertyKey": "001",
"propertyKey": "001",
"value": 0,
"metadata": {
"type": "number",
"readable": True,
"writeable": False,
"min": 0,
"max": 255,
"label": "Scene 004",
"states": {
"0": "KeyPressed",
"1": "KeyReleased",
"2": "KeyHeldDown",
},
},
"ccVersion": 1,
},
},
)
node.receive_event(event)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data[
"some"
] == "event.value_notification.central_scene - device - zwave_js_value_notification - {}".format(
CommandClass.CENTRAL_SCENE
)
async def test_get_trigger_capabilities_central_scene_value_notification(
hass, client, wallmote_central_scene, integration
):
"""Test we get the expected capabilities from a value_notification.central_scene trigger."""
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
capabilities = await device_trigger.async_get_trigger_capabilities(
hass,
{
"platform": "device",
"domain": DOMAIN,
"type": "event.value_notification.central_scene",
"device_id": device.id,
"command_class": CommandClass.CENTRAL_SCENE.value,
"property": "scene",
"property_key": "001",
"endpoint": 0,
"subtype": "Endpoint 0 Scene 001",
},
)
assert capabilities and "extra_fields" in capabilities
assert voluptuous_serialize.convert(
capabilities["extra_fields"], custom_serializer=cv.custom_serializer
) == [
{
"name": "value",
"required": True,
"type": "select",
"options": [(0, "KeyPressed"), (1, "KeyReleased"), (2, "KeyHeldDown")],
},
]
async def test_get_scene_activation_value_notification_triggers(
hass, client, hank_binary_switch, integration
):
"""Test we get the expected triggers from a zwave_js device with the SceneActivation CC."""
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
expected_trigger = {
"platform": "device",
"domain": DOMAIN,
"type": "event.value_notification.scene_activation",
"device_id": device.id,
"command_class": CommandClass.SCENE_ACTIVATION.value,
"property": "sceneId",
"property_key": None,
"endpoint": 0,
"subtype": "Endpoint 0",
}
triggers = await async_get_device_automations(hass, "trigger", device.id)
assert expected_trigger in triggers
async def test_if_scene_activation_value_notification_fires(
hass, client, hank_binary_switch, integration, calls
):
"""Test for event.value_notification.scene_activation trigger firing."""
node: Node = hank_binary_switch
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
"platform": "device",
"domain": DOMAIN,
"device_id": device.id,
"type": "event.value_notification.scene_activation",
"command_class": CommandClass.SCENE_ACTIVATION.value,
"property": "sceneId",
"property_key": None,
"endpoint": 0,
"subtype": "Endpoint 0",
"value": 1,
},
"action": {
"service": "test.automation",
"data_template": {
"some": (
"event.value_notification.scene_activation - "
"{{ trigger.platform}} - "
"{{ trigger.event.event_type}} - "
"{{ trigger.event.data.command_class }}"
)
},
},
},
]
},
)
# Publish fake Scene Activation CC value notification
event = Event(
type="value notification",
data={
"source": "node",
"event": "value notification",
"nodeId": node.node_id,
"args": {
"commandClassName": "Scene Activation",
"commandClass": 43,
"endpoint": 0,
"property": "sceneId",
"propertyName": "sceneId",
"value": 1,
"metadata": {
"type": "number",
"readable": True,
"writeable": True,
"min": 1,
"max": 255,
"label": "Scene ID",
},
"ccVersion": 1,
},
},
)
node.receive_event(event)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data[
"some"
] == "event.value_notification.scene_activation - device - zwave_js_value_notification - {}".format(
CommandClass.SCENE_ACTIVATION
)
async def test_get_trigger_capabilities_scene_activation_value_notification(
hass, client, hank_binary_switch, integration
):
"""Test we get the expected capabilities from a value_notification.scene_activation trigger."""
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
capabilities = await device_trigger.async_get_trigger_capabilities(
hass,
{
"platform": "device",
"domain": DOMAIN,
"type": "event.value_notification.scene_activation",
"device_id": device.id,
"command_class": CommandClass.SCENE_ACTIVATION.value,
"property": "sceneId",
"property_key": None,
"endpoint": 0,
"subtype": "Endpoint 0",
},
)
assert capabilities and "extra_fields" in capabilities
assert voluptuous_serialize.convert(
capabilities["extra_fields"], custom_serializer=cv.custom_serializer
) == [
{
"name": "value",
"required": True,
"type": "integer",
"valueMin": 1,
"valueMax": 255,
}
]
async def test_failure_scenarios(hass, client, hank_binary_switch, integration):
"""Test failure scenarios."""
with pytest.raises(HomeAssistantError):
await async_attach_trigger(
hass, {"type": "failed.test", "device_id": "invalid_device_id"}, None, {}
)
with pytest.raises(HomeAssistantError):
await async_attach_trigger(
hass,
{"type": "event.failed_type", "device_id": "invalid_device_id"},
None,
{},
)
dev_reg = async_get_dev_reg(hass)
device = async_entries_for_config_entry(dev_reg, integration.entry_id)[0]
with pytest.raises(HomeAssistantError):
await async_attach_trigger(
hass, {"type": "failed.test", "device_id": device.id}, None, {}
)
with pytest.raises(HomeAssistantError):
await async_attach_trigger(
hass,
{"type": "event.failed_type", "device_id": device.id},
None,
{},
)
with patch(
"homeassistant.components.zwave_js.device_trigger.async_get_node_from_device_id",
return_value=None,
), patch(
"homeassistant.components.zwave_js.helpers.get_zwave_value_from_config",
return_value=None,
):
assert (
await async_get_trigger_capabilities(
hass, {"type": "failed.test", "device_id": "invalid_device_id"}
)
== {}
)
with pytest.raises(HomeAssistantError):
async_get_node_status_sensor_entity_id(hass, "invalid_device_id")

View File

@ -0,0 +1,642 @@
{
"nodeId": 2,
"index": 0,
"installerIcon": 1536,
"userIcon": 1536,
"status": 4,
"ready": true,
"isListening": true,
"isRouting": true,
"isSecure": false,
"manufacturerId": 99,
"productId": 12344,
"productType": 18756,
"firmwareVersion": "5.26",
"zwavePlusVersion": 1,
"name": "LivingRoomLight",
"location": "LivingRoom",
"deviceConfig": {
"filename": "/opt/node_modules/@zwave-js/config/config/devices/0x0063/ge_14294_zw3005.json",
"manufacturer": "GE/Jasco",
"manufacturerId": 99,
"label": "14294 / ZW3005",
"description": "In-Wall Dimmer Switch",
"devices": [
{
"productType": 18756,
"productId": 12344
}
],
"firmwareVersion": {
"min": "0.0",
"max": "255.255"
},
"associations": {},
"paramInformation": {
"_map": {}
},
"compat": {
"valueIdRegex": {},
"treatBasicSetAsEvent": true
},
"isEmbedded": true
},
"label": "14294 / ZW3005",
"interviewAttempts": 0,
"endpoints": [
{
"nodeId": 2,
"index": 0,
"installerIcon": 1536,
"userIcon": 1536,
"deviceClass": {
"basic": {
"key": 4,
"label": "Routing Slave"
},
"generic": {
"key": 17,
"label": "Multilevel Switch"
},
"specific": {
"key": 1,
"label": "Multilevel Power Switch"
},
"mandatorySupportedCCs": [32, 38, 39],
"mandatoryControlledCCs": []
}
}
],
"values": [
{
"endpoint": 0,
"commandClass": 32,
"commandClassName": "Basic",
"property": "currentValue",
"propertyName": "currentValue",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Current value",
"min": 0,
"max": 99
}
},
{
"endpoint": 0,
"commandClass": 32,
"commandClassName": "Basic",
"property": "targetValue",
"propertyName": "targetValue",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"label": "Target value",
"min": 0,
"max": 99
}
},
{
"endpoint": 0,
"commandClass": 32,
"commandClassName": "Basic",
"property": "event",
"propertyName": "event",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Event value",
"min": 0,
"max": 255
}
},
{
"endpoint": 0,
"commandClass": 38,
"commandClassName": "Multilevel Switch",
"property": "targetValue",
"propertyName": "targetValue",
"ccVersion": 2,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"label": "Target value",
"min": 0,
"max": 99
},
"value": 255
},
{
"endpoint": 0,
"commandClass": 38,
"commandClassName": "Multilevel Switch",
"property": "duration",
"propertyName": "duration",
"ccVersion": 2,
"metadata": {
"type": "duration",
"readable": true,
"writeable": true,
"label": "Transition duration"
}
},
{
"endpoint": 0,
"commandClass": 38,
"commandClassName": "Multilevel Switch",
"property": "currentValue",
"propertyName": "currentValue",
"ccVersion": 2,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Current value",
"min": 0,
"max": 99
},
"value": 35
},
{
"endpoint": 0,
"commandClass": 38,
"commandClassName": "Multilevel Switch",
"property": "Up",
"propertyName": "Up",
"ccVersion": 2,
"metadata": {
"type": "boolean",
"readable": true,
"writeable": true,
"label": "Perform a level change (Up)",
"ccSpecific": {
"switchType": 2
}
}
},
{
"endpoint": 0,
"commandClass": 38,
"commandClassName": "Multilevel Switch",
"property": "Down",
"propertyName": "Down",
"ccVersion": 2,
"metadata": {
"type": "boolean",
"readable": true,
"writeable": true,
"label": "Perform a level change (Down)",
"ccSpecific": {
"switchType": 2
}
}
},
{
"endpoint": 0,
"commandClass": 43,
"commandClassName": "Scene Activation",
"property": "sceneId",
"propertyName": "sceneId",
"ccVersion": 0,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"label": "Scene ID",
"min": 1,
"max": 255
}
},
{
"endpoint": 0,
"commandClass": 43,
"commandClassName": "Scene Activation",
"property": "dimmingDuration",
"propertyName": "dimmingDuration",
"ccVersion": 0,
"metadata": {
"type": "any",
"readable": true,
"writeable": true,
"label": "Dimming duration"
}
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 3,
"propertyName": "Night Light",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"description": "Defines the behavior of the blue LED. Default is on when switch is off.",
"label": "Night Light",
"default": 0,
"min": 0,
"max": 2,
"states": {
"0": "LED on when switch is OFF",
"1": "LED on when switch is ON",
"2": "LED always off"
},
"valueSize": 1,
"format": 0,
"allowManualEntry": false,
"isFromConfig": true
},
"value": 0
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 4,
"propertyName": "Invert Switch",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"description": "Invert the ON/OFF Switch State.",
"label": "Invert Switch",
"default": 0,
"min": 0,
"max": 1,
"states": {
"0": "No",
"1": "Yes"
},
"valueSize": 1,
"format": 0,
"allowManualEntry": false,
"isFromConfig": true
},
"value": 0
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 7,
"propertyName": "Dim Rate Steps (Z-Wave Command)",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"description": "Number of steps or levels",
"label": "Dim Rate Steps (Z-Wave Command)",
"default": 1,
"min": 0,
"max": 99,
"valueSize": 1,
"format": 1,
"allowManualEntry": true,
"isFromConfig": true
},
"value": 1
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 8,
"propertyName": "Dim Rate Timing (Z-Wave)",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"description": "Timing of steps or levels",
"label": "Dim Rate Timing (Z-Wave)",
"default": 3,
"min": 1,
"max": 255,
"unit": "10ms",
"valueSize": 2,
"format": 1,
"allowManualEntry": true,
"isFromConfig": true
},
"value": 3
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 9,
"propertyName": "Dim Rate Steps (Manual)",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"description": "Number of steps or levels",
"label": "Dim Rate Steps (Manual)",
"default": 1,
"min": 1,
"max": 99,
"valueSize": 1,
"format": 1,
"allowManualEntry": true,
"isFromConfig": true
},
"value": 1
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 10,
"propertyName": "Dim Rate Timing (Manual)",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"description": "Timing of steps",
"label": "Dim Rate Timing (Manual)",
"default": 3,
"min": 1,
"max": 255,
"unit": "10ms",
"valueSize": 2,
"format": 1,
"allowManualEntry": true,
"isFromConfig": true
},
"value": 3
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 11,
"propertyName": "Dim Rate Steps (All-On/All-Off)",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"description": "Number of steps or levels",
"label": "Dim Rate Steps (All-On/All-Off)",
"default": 1,
"min": 1,
"max": 99,
"valueSize": 1,
"format": 1,
"allowManualEntry": true,
"isFromConfig": true
},
"value": 1
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 12,
"propertyName": "Dim Rate Timing (All-On/All-Off)",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"description": "Timing of steps or levels",
"label": "Dim Rate Timing (All-On/All-Off)",
"default": 3,
"min": 1,
"max": 255,
"unit": "10ms",
"valueSize": 2,
"format": 1,
"allowManualEntry": true,
"isFromConfig": true
},
"value": 3
},
{
"endpoint": 0,
"commandClass": 114,
"commandClassName": "Manufacturer Specific",
"property": "manufacturerId",
"propertyName": "manufacturerId",
"ccVersion": 2,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Manufacturer ID",
"min": 0,
"max": 65535
},
"value": 99
},
{
"endpoint": 0,
"commandClass": 114,
"commandClassName": "Manufacturer Specific",
"property": "productType",
"propertyName": "productType",
"ccVersion": 2,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Product type",
"min": 0,
"max": 65535
},
"value": 18756
},
{
"endpoint": 0,
"commandClass": 114,
"commandClassName": "Manufacturer Specific",
"property": "productId",
"propertyName": "productId",
"ccVersion": 2,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Product ID",
"min": 0,
"max": 65535
},
"value": 12344
},
{
"endpoint": 0,
"commandClass": 134,
"commandClassName": "Version",
"property": "libraryType",
"propertyName": "libraryType",
"ccVersion": 2,
"metadata": {
"type": "any",
"readable": true,
"writeable": false,
"label": "Library type"
},
"value": 3
},
{
"endpoint": 0,
"commandClass": 134,
"commandClassName": "Version",
"property": "protocolVersion",
"propertyName": "protocolVersion",
"ccVersion": 2,
"metadata": {
"type": "any",
"readable": true,
"writeable": false,
"label": "Z-Wave protocol version"
},
"value": "4.34"
},
{
"endpoint": 0,
"commandClass": 134,
"commandClassName": "Version",
"property": "firmwareVersions",
"propertyName": "firmwareVersions",
"ccVersion": 2,
"metadata": {
"type": "any",
"readable": true,
"writeable": false,
"label": "Z-Wave chip firmware versions"
},
"value": ["5.26"]
},
{
"endpoint": 0,
"commandClass": 134,
"commandClassName": "Version",
"property": "hardwareVersion",
"propertyName": "hardwareVersion",
"ccVersion": 2,
"metadata": {
"type": "any",
"readable": true,
"writeable": false,
"label": "Z-Wave chip hardware version"
}
}
],
"isFrequentListening": false,
"maxDataRate": 40000,
"supportedDataRates": [40000],
"protocolVersion": 3,
"zwavePlusNodeType": 0,
"zwavePlusRoleType": 5,
"deviceClass": {
"basic": {
"key": 4,
"label": "Routing Slave"
},
"generic": {
"key": 17,
"label": "Multilevel Switch"
},
"specific": {
"key": 1,
"label": "Multilevel Power Switch"
},
"mandatorySupportedCCs": [32, 38, 39],
"mandatoryControlledCCs": []
},
"commandClasses": [
{
"id": 32,
"name": "Basic",
"version": 1,
"isSecure": false
},
{
"id": 38,
"name": "Multilevel Switch",
"version": 2,
"isSecure": false
},
{
"id": 43,
"name": "Scene Activation",
"version": 1,
"isSecure": false
},
{
"id": 44,
"name": "Scene Actuator Configuration",
"version": 1,
"isSecure": false
},
{
"id": 86,
"name": "CRC-16 Encapsulation",
"version": 1,
"isSecure": false
},
{
"id": 89,
"name": "Association Group Information",
"version": 1,
"isSecure": false
},
{
"id": 90,
"name": "Device Reset Locally",
"version": 1,
"isSecure": false
},
{
"id": 94,
"name": "Z-Wave Plus Info",
"version": 2,
"isSecure": false
},
{
"id": 112,
"name": "Configuration",
"version": 1,
"isSecure": false
},
{
"id": 114,
"name": "Manufacturer Specific",
"version": 2,
"isSecure": false
},
{
"id": 122,
"name": "Firmware Update Meta Data",
"version": 2,
"isSecure": false
},
{
"id": 133,
"name": "Association",
"version": 2,
"isSecure": false
},
{
"id": 134,
"name": "Version",
"version": 2,
"isSecure": false
}
],
"interviewStage": "Complete",
"deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x0063:0x4944:0x3038:5.26"
}

View File

@ -50,7 +50,68 @@
"index": 0
}
],
"commandClasses": [],
"commandClasses": [
{
"id": 98,
"name": "Door Lock",
"version": 1,
"isSecure": true
},
{
"id": 99,
"name": "User Code",
"version": 1,
"isSecure": true
},
{
"id": 112,
"name": "Configuration",
"version": 1,
"isSecure": true
},
{
"id": 113,
"name": "Notification",
"version": 1,
"isSecure": true
},
{
"id": 114,
"name": "Manufacturer Specific",
"version": 1,
"isSecure": false
},
{
"id": 122,
"name": "Firmware Update Meta Data",
"version": 1,
"isSecure": false
},
{
"id": 128,
"name": "Battery",
"version": 1,
"isSecure": true
},
{
"id": 133,
"name": "Association",
"version": 1,
"isSecure": true
},
{
"id": 134,
"name": "Version",
"version": 1,
"isSecure": false
},
{
"id": 152,
"name": "Security",
"version": 1,
"isSecure": true
}
],
"values": [
{
"commandClassName": "Door Lock",