Add zwave_js discovery schema for Vision Security ZL7432 (#49510)

* Add zwave_js discovery schema for Vision Security ZL7432

* add tests

* fix test

* add correct fixture

* Make discussed changes

* fix tests

* move event handler

* fix logic to get entity ID

* add test

* make discovery test more explicit

* remove domain from event data

* always provide entity_id key to make automations easier and translate value if possible

* formatting

* comment

* dont overwrite value
This commit is contained in:
Raman Gupta 2021-04-28 04:22:54 -04:00 committed by GitHub
parent d7247c2ace
commit 1cb907c2e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 618 additions and 6 deletions

View File

@ -13,12 +13,13 @@ from zwave_js_server.model.notification import (
EntryControlNotification,
NotificationNotification,
)
from zwave_js_server.model.value import ValueNotification
from zwave_js_server.model.value import Value, ValueNotification
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_DEVICE_ID,
ATTR_DOMAIN,
ATTR_ENTITY_ID,
CONF_URL,
EVENT_HOMEASSISTANT_STOP,
)
@ -63,9 +64,10 @@ from .const import (
LOGGER,
ZWAVE_JS_NOTIFICATION_EVENT,
ZWAVE_JS_VALUE_NOTIFICATION_EVENT,
ZWAVE_JS_VALUE_UPDATED_EVENT,
)
from .discovery import async_discover_values
from .helpers import async_enable_statistics, get_device_id
from .discovery import ZwaveDiscoveryInfo, async_discover_values
from .helpers import async_enable_statistics, get_device_id, get_unique_id
from .migrate import async_migrate_discovered_value
from .services import ZWaveServices
@ -140,6 +142,8 @@ async def async_setup_entry( # noqa: C901
if device.id not in registered_unique_ids:
registered_unique_ids[device.id] = defaultdict(set)
value_updates_disc_info = []
# run discovery on all node values and create/update entities
for disc_info in async_discover_values(node):
platform = disc_info.platform
@ -168,6 +172,21 @@ async def async_setup_entry( # noqa: C901
hass, f"{DOMAIN}_{entry.entry_id}_add_{platform}", disc_info
)
# Capture discovery info for values we want to watch for updates
if disc_info.assumed_state:
value_updates_disc_info.append(disc_info)
# add listener for value updated events if necessary
if value_updates_disc_info:
unsubscribe_callbacks.append(
node.on(
"value updated",
lambda event: async_on_value_updated(
value_updates_disc_info, event["value"]
),
)
)
# add listener for stateless node value notification events
unsubscribe_callbacks.append(
node.on(
@ -274,6 +293,52 @@ async def async_setup_entry( # noqa: C901
hass.bus.async_fire(ZWAVE_JS_NOTIFICATION_EVENT, event_data)
@callback
def async_on_value_updated(
value_updates_disc_info: list[ZwaveDiscoveryInfo], value: Value
) -> None:
"""Fire value updated event."""
# Get the discovery info for the value that was updated. If we can't
# find the discovery info, we don't need to fire an event
try:
disc_info = next(
disc_info
for disc_info in value_updates_disc_info
if disc_info.primary_value.value_id == value.value_id
)
except StopIteration:
return
device = dev_reg.async_get_device({get_device_id(client, value.node)})
unique_id = get_unique_id(
client.driver.controller.home_id, disc_info.primary_value.value_id
)
entity_id = ent_reg.async_get_entity_id(disc_info.platform, DOMAIN, unique_id)
raw_value = value_ = value.value
if value.metadata.states:
value_ = value.metadata.states.get(str(value), value_)
hass.bus.async_fire(
ZWAVE_JS_VALUE_UPDATED_EVENT,
{
ATTR_NODE_ID: value.node.node_id,
ATTR_HOME_ID: client.driver.controller.home_id,
ATTR_DEVICE_ID: device.id, # type: ignore
ATTR_ENTITY_ID: entity_id,
ATTR_COMMAND_CLASS: value.command_class,
ATTR_COMMAND_CLASS_NAME: value.command_class_name,
ATTR_ENDPOINT: value.endpoint,
ATTR_PROPERTY: value.property_,
ATTR_PROPERTY_NAME: value.property_name,
ATTR_PROPERTY_KEY: value.property_key,
ATTR_PROPERTY_KEY_NAME: value.property_key_name,
ATTR_VALUE: value_,
ATTR_VALUE_RAW: raw_value,
},
)
# connect and throw error if connection failed
try:
async with timeout(CONNECT_TIMEOUT):

View File

@ -21,6 +21,7 @@ LOGGER = logging.getLogger(__package__)
# constants for events
ZWAVE_JS_VALUE_NOTIFICATION_EVENT = f"{DOMAIN}_value_notification"
ZWAVE_JS_NOTIFICATION_EVENT = f"{DOMAIN}_notification"
ZWAVE_JS_VALUE_UPDATED_EVENT = f"{DOMAIN}_value_updated"
ATTR_NODE_ID = "node_id"
ATTR_HOME_ID = "home_id"
ATTR_ENDPOINT = "endpoint"

View File

@ -21,6 +21,8 @@ class ZwaveDiscoveryInfo:
node: ZwaveNode
# the value object itself for primary value
primary_value: ZwaveValue
# bool to specify whether state is assumed and events should be fired on value update
assumed_state: bool
# the home assistant platform for which an entity should be created
platform: str
# hint for the platform about this discovered entity
@ -87,6 +89,8 @@ class ZWaveDiscoverySchema:
absent_values: list[ZWaveValueDiscoverySchema] | None = None
# [optional] bool to specify if this primary value may be discovered by multiple platforms
allow_multi: bool = False
# [optional] bool to specify whether state is assumed and events should be fired on value update
assumed_state: bool = False
def get_config_parameter_discovery_schema(
@ -123,6 +127,10 @@ SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA = ZWaveValueDiscoverySchema(
type={"number"},
)
SWITCH_BINARY_CURRENT_VALUE_SCHEMA = ZWaveValueDiscoverySchema(
command_class={CommandClass.SWITCH_BINARY}, property={"currentValue"}
)
# For device class mapping see:
# https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/deviceClasses.json
DISCOVERY_SCHEMAS = [
@ -197,6 +205,15 @@ DISCOVERY_SCHEMAS = [
product_type={0x0003},
primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,
),
# Vision Security ZL7432 In Wall Dual Relay Switch
ZWaveDiscoverySchema(
platform="switch",
manufacturer_id={0x0109},
product_id={0x1711, 0x1717},
product_type={0x2017},
primary_value=SWITCH_BINARY_CURRENT_VALUE_SCHEMA,
assumed_state=True,
),
# ====== START OF CONFIG PARAMETER SPECIFIC MAPPING SCHEMAS =======
# Door lock mode config parameter. Functionality equivalent to Notification CC
# list sensors.
@ -365,9 +382,7 @@ DISCOVERY_SCHEMAS = [
# binary switches
ZWaveDiscoverySchema(
platform="switch",
primary_value=ZWaveValueDiscoverySchema(
command_class={CommandClass.SWITCH_BINARY}, property={"currentValue"}
),
primary_value=SWITCH_BINARY_CURRENT_VALUE_SCHEMA,
),
# binary switch
# barrier operator signaling states
@ -513,6 +528,7 @@ def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None
yield ZwaveDiscoveryInfo(
node=value.node,
primary_value=value,
assumed_state=schema.assumed_state,
platform=schema.platform,
platform_hint=schema.hint,
)

View File

@ -224,3 +224,8 @@ class ZWaveBaseEntity(Entity):
def should_poll(self) -> bool:
"""No polling needed."""
return False
@property
def assumed_state(self) -> bool:
"""Return True if unable to access real state of the entity."""
return self.info.assumed_state

View File

@ -337,6 +337,12 @@ def climate_radio_thermostat_ct100_mode_and_setpoint_on_different_endpoints_stat
)
@pytest.fixture(name="vision_security_zl7432_state", scope="session")
def vision_security_zl7432_state_fixture():
"""Load the vision security zl7432 switch node state fixture data."""
return json.loads(load_fixture("zwave_js/vision_security_zl7432_state.json"))
@pytest.fixture(name="client")
def mock_client_fixture(controller_state, version_state):
"""Mock a client."""
@ -637,3 +643,11 @@ def climate_radio_thermostat_ct100_mode_and_setpoint_on_different_endpoints_fixt
)
client.driver.controller.nodes[node.node_id] = node
return node
@pytest.fixture(name="vision_security_zl7432")
def vision_security_zl7432_fixture(client, vision_security_zl7432_state):
"""Mock a vision security zl7432 node."""
node = Node(client, copy.deepcopy(vision_security_zl7432_state))
client.driver.controller.nodes[node.node_id] = node
return node

View File

@ -35,3 +35,16 @@ async def test_inovelli_lzw36(hass, client, inovelli_lzw36, integration):
state = hass.states.get("fan.family_room_combo_2")
assert state
async def test_vision_security_zl7432(
hass, client, vision_security_zl7432, integration
):
"""Test Vision Security ZL7432 is caught by the device specific discovery."""
for entity_id in (
"switch.in_wall_dual_relay_switch",
"switch.in_wall_dual_relay_switch_2",
):
state = hass.states.get(entity_id)
assert state
assert state.attributes["assumed_state"]

View File

@ -194,3 +194,68 @@ async def test_notifications(hass, hank_binary_switch, integration, client):
assert events[1].data["event_data"] == "555"
assert events[1].data["command_class"] == CommandClass.ENTRY_CONTROL
assert events[1].data["command_class_name"] == "Entry Control"
async def test_value_updated(hass, vision_security_zl7432, integration, client):
"""Test value updated events."""
node = vision_security_zl7432
events = async_capture_events(hass, "zwave_js_value_updated")
event = Event(
type="value updated",
data={
"source": "node",
"event": "value updated",
"nodeId": 7,
"args": {
"commandClassName": "Switch Binary",
"commandClass": 37,
"endpoint": 1,
"property": "currentValue",
"newValue": 1,
"prevValue": 0,
"propertyName": "currentValue",
},
},
)
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"] == 7
assert events[0].data["entity_id"] == "switch.in_wall_dual_relay_switch"
assert events[0].data["command_class"] == CommandClass.SWITCH_BINARY
assert events[0].data["command_class_name"] == "Switch Binary"
assert events[0].data["endpoint"] == 1
assert events[0].data["property_name"] == "currentValue"
assert events[0].data["property"] == "currentValue"
assert events[0].data["value"] == 1
assert events[0].data["value_raw"] == 1
# Try a value updated event on a value we aren't watching to make sure
# no event fires
event = Event(
type="value updated",
data={
"source": "node",
"event": "value updated",
"nodeId": 7,
"args": {
"commandClassName": "Basic",
"commandClass": 32,
"endpoint": 1,
"property": "currentValue",
"newValue": 1,
"prevValue": 0,
"propertyName": "currentValue",
},
},
)
node.receive_event(event)
# wait for the event
await hass.async_block_till_done()
# We should only still have captured one event
assert len(events) == 1

View File

@ -0,0 +1,433 @@
{
"nodeId": 7,
"index": 0,
"status": 4,
"ready": true,
"isListening": true,
"isRouting": true,
"isSecure": false,
"manufacturerId": 265,
"productId": 5911,
"productType": 8215,
"firmwareVersion": "13.7",
"deviceConfig": {
"filename": "/opt/node_modules/@zwave-js/config/config/devices/0x0109/zl7432.json",
"manufacturer": "Vision Security",
"manufacturerId": 265,
"label": "ZL7432",
"description": "In Wall Dual Relay Switch",
"devices": [
{
"productType": 8215,
"productId": 5905
},
{
"productType": 8215,
"productId": 5911
}
],
"firmwareVersion": {
"min": "0.0",
"max": "255.255"
},
"associations": {}
},
"label": "ZL7432",
"neighbors": [
1,
10,
11,
12,
13,
14,
15,
17,
18,
19,
20,
21,
22,
23,
24,
26,
27,
28,
29,
3,
32,
33,
34,
4,
47,
48,
5,
50,
51,
52,
53,
56,
58,
59,
69,
8,
9
],
"endpointCountIsDynamic": false,
"endpointsHaveIdenticalCapabilities": true,
"individualEndpointCount": 2,
"interviewAttempts": 0,
"endpoints": [
{
"nodeId": 7,
"index": 0,
"deviceClass": {
"basic": {
"key": 4,
"label": "Routing Slave"
},
"generic": {
"key": 16,
"label": "Binary Switch"
},
"specific": {
"key": 1,
"label": "Binary Power Switch"
},
"mandatorySupportedCCs": [32, 37, 39],
"mandatoryControlledCCs": []
}
},
{
"nodeId": 7,
"index": 1,
"deviceClass": {
"basic": {
"key": 4,
"label": "Routing Slave"
},
"generic": {
"key": 16,
"label": "Binary Switch"
},
"specific": {
"key": 1,
"label": "Binary Power Switch"
},
"mandatorySupportedCCs": [32, 37, 39],
"mandatoryControlledCCs": []
}
},
{
"nodeId": 7,
"index": 2,
"deviceClass": {
"basic": {
"key": 4,
"label": "Routing Slave"
},
"generic": {
"key": 16,
"label": "Binary Switch"
},
"specific": {
"key": 1,
"label": "Binary Power Switch"
},
"mandatorySupportedCCs": [32, 37, 39],
"mandatoryControlledCCs": []
}
}
],
"values": [
{
"endpoint": 0,
"commandClass": 96,
"commandClassName": "Multi Channel",
"property": "endpointIndizes",
"propertyName": "endpointIndizes",
"ccVersion": 3,
"metadata": {
"type": "any",
"readable": true,
"writeable": true
},
"value": [1, 2]
},
{
"endpoint": 0,
"commandClass": 114,
"commandClassName": "Manufacturer Specific",
"property": "manufacturerId",
"propertyName": "manufacturerId",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Manufacturer ID",
"min": 0,
"max": 65535
},
"value": 265
},
{
"endpoint": 0,
"commandClass": 114,
"commandClassName": "Manufacturer Specific",
"property": "productType",
"propertyName": "productType",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Product type",
"min": 0,
"max": 65535
},
"value": 8215
},
{
"endpoint": 0,
"commandClass": 114,
"commandClassName": "Manufacturer Specific",
"property": "productId",
"propertyName": "productId",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Product ID",
"min": 0,
"max": 65535
},
"value": 5911
},
{
"endpoint": 0,
"commandClass": 134,
"commandClassName": "Version",
"property": "libraryType",
"propertyName": "libraryType",
"ccVersion": 1,
"metadata": {
"type": "any",
"readable": true,
"writeable": false,
"label": "Library type"
},
"value": 6
},
{
"endpoint": 0,
"commandClass": 134,
"commandClassName": "Version",
"property": "protocolVersion",
"propertyName": "protocolVersion",
"ccVersion": 1,
"metadata": {
"type": "any",
"readable": true,
"writeable": false,
"label": "Z-Wave protocol version"
},
"value": "3.67"
},
{
"endpoint": 0,
"commandClass": 134,
"commandClassName": "Version",
"property": "firmwareVersions",
"propertyName": "firmwareVersions",
"ccVersion": 1,
"metadata": {
"type": "any",
"readable": true,
"writeable": false,
"label": "Z-Wave chip firmware versions"
},
"value": ["13.7"]
},
{
"endpoint": 1,
"commandClass": 32,
"commandClassName": "Basic",
"property": "currentValue",
"propertyName": "currentValue",
"ccVersion": 0,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Current value",
"min": 0,
"max": 99
}
},
{
"endpoint": 1,
"commandClass": 32,
"commandClassName": "Basic",
"property": "targetValue",
"propertyName": "targetValue",
"ccVersion": 0,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"label": "Target value",
"min": 0,
"max": 99
}
},
{
"endpoint": 1,
"commandClass": 37,
"commandClassName": "Binary Switch",
"property": "currentValue",
"propertyName": "currentValue",
"ccVersion": 1,
"metadata": {
"type": "boolean",
"readable": true,
"writeable": false,
"label": "Current value"
},
"value": false
},
{
"endpoint": 1,
"commandClass": 37,
"commandClassName": "Binary Switch",
"property": "targetValue",
"propertyName": "targetValue",
"ccVersion": 1,
"metadata": {
"type": "boolean",
"readable": true,
"writeable": true,
"label": "Target value"
},
"value": false
},
{
"endpoint": 2,
"commandClass": 32,
"commandClassName": "Basic",
"property": "currentValue",
"propertyName": "currentValue",
"ccVersion": 0,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Current value",
"min": 0,
"max": 99
}
},
{
"endpoint": 2,
"commandClass": 32,
"commandClassName": "Basic",
"property": "targetValue",
"propertyName": "targetValue",
"ccVersion": 0,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"label": "Target value",
"min": 0,
"max": 99
}
},
{
"endpoint": 2,
"commandClass": 37,
"commandClassName": "Binary Switch",
"property": "currentValue",
"propertyName": "currentValue",
"ccVersion": 1,
"metadata": {
"type": "boolean",
"readable": true,
"writeable": false,
"label": "Current value"
},
"value": false
},
{
"endpoint": 2,
"commandClass": 37,
"commandClassName": "Binary Switch",
"property": "targetValue",
"propertyName": "targetValue",
"ccVersion": 1,
"metadata": {
"type": "boolean",
"readable": true,
"writeable": true,
"label": "Target value"
},
"value": false
}
],
"interviewStage": 6,
"isFrequentListening": false,
"maxDataRate": 40000,
"supportedDataRates": [40000],
"protocolVersion": 3,
"supportsBeaming": true,
"supportsSecurity": false,
"nodeType": 1,
"deviceClass": {
"basic": {
"key": 4,
"label": "Routing Slave"
},
"generic": {
"key": 16,
"label": "Binary Switch"
},
"specific": {
"key": 1,
"label": "Binary Power Switch"
},
"mandatorySupportedCCs": [32, 37, 39],
"mandatoryControlledCCs": []
},
"commandClasses": [
{
"id": 37,
"name": "Binary Switch",
"version": 1,
"isSecure": false
},
{
"id": 96,
"name": "Multi Channel",
"version": 3,
"isSecure": false
},
{
"id": 114,
"name": "Manufacturer Specific",
"version": 1,
"isSecure": false
},
{
"id": 133,
"name": "Association",
"version": 1,
"isSecure": false
},
{
"id": 134,
"name": "Version",
"version": 1,
"isSecure": false
}
]
}