Fix Z-Wave rediscovery ()

This commit is contained in:
Martin Hjelmare 2024-10-01 14:42:31 +02:00 committed by GitHub
parent 44eb4e0c9e
commit bb70a0feb2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 825 additions and 8 deletions

View file

@ -100,6 +100,7 @@ from .const import (
DATA_CLIENT,
DOMAIN,
EVENT_DEVICE_ADDED_TO_REGISTRY,
EVENT_VALUE_UPDATED,
LIB_LOGGER,
LOGGER,
LR_ADDON_VERSION,
@ -623,7 +624,7 @@ class NodeEvents:
)
# add listeners to handle new values that get added later
for event in ("value added", "value updated", "metadata updated"):
for event in ("value added", EVENT_VALUE_UPDATED, "metadata updated"):
self.config_entry.async_on_unload(
node.on(
event,
@ -722,7 +723,7 @@ class NodeEvents:
# add listener for value updated events
self.config_entry.async_on_unload(
disc_info.node.on(
"value updated",
EVENT_VALUE_UPDATED,
lambda event: self.async_on_value_updated_fire_event(
value_updates_disc_info, event["value"]
),

View file

@ -42,6 +42,7 @@ DATA_CLIENT = "client"
DATA_OLD_SERVER_LOG_LEVEL = "old_server_log_level"
EVENT_DEVICE_ADDED_TO_REGISTRY = f"{DOMAIN}_device_added_to_registry"
EVENT_VALUE_UPDATED = "value updated"
LOGGER = logging.getLogger(__package__)
LIB_LOGGER = logging.getLogger("zwave_js_server")

View file

@ -1363,6 +1363,9 @@ def async_discover_single_value(
if not schema.allow_multi:
discovered_value_ids[device.id].add(value.value_id)
# prevent re-discovery of the (primary) value after all schemas have been checked
discovered_value_ids[device.id].add(value.value_id)
if value.command_class == CommandClass.CONFIGURATION:
yield from async_discover_single_configuration_value(
cast(ConfigurationValue, value)

View file

@ -22,11 +22,10 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import UNDEFINED
from .const import DOMAIN, LOGGER
from .const import DOMAIN, EVENT_VALUE_UPDATED, LOGGER
from .discovery import ZwaveDiscoveryInfo
from .helpers import get_device_id, get_unique_id, get_valueless_base_unique_id
EVENT_VALUE_UPDATED = "value updated"
EVENT_VALUE_REMOVED = "value removed"
EVENT_DEAD = "dead"
EVENT_ALIVE = "alive"

View file

@ -32,6 +32,7 @@ from ..const import (
ATTR_PROPERTY_KEY_NAME,
ATTR_PROPERTY_NAME,
DOMAIN,
EVENT_VALUE_UPDATED,
)
from ..helpers import async_get_nodes_from_targets, get_device_id
from .trigger_helpers import async_bypass_dynamic_config_validation
@ -184,7 +185,7 @@ async def async_attach_trigger(
# We need to store the current value and device for the callback
unsubs.append(
node.on(
"value updated",
EVENT_VALUE_UPDATED,
functools.partial(async_on_value_updated, value, device),
)
)

View file

@ -3,13 +3,14 @@
import asyncio
import copy
import io
from typing import Any
from unittest.mock import DEFAULT, AsyncMock, patch
from typing import Any, cast
from unittest.mock import DEFAULT, AsyncMock, MagicMock, patch
import pytest
from zwave_js_server.event import Event
from zwave_js_server.model.driver import Driver
from zwave_js_server.model.node import Node
from zwave_js_server.model.node.data_model import NodeDataType
from zwave_js_server.version import VersionInfo
from homeassistant.components.zwave_js.const import DOMAIN
@ -488,6 +489,15 @@ def window_covering_outbound_bottom_state_fixture() -> dict[str, Any]:
return load_json_object_fixture("window_covering_outbound_bottom.json", DOMAIN)
@pytest.fixture(name="siren_neo_coolcam_state")
def siren_neo_coolcam_state_state_fixture() -> NodeDataType:
"""Load node with siren_neo_coolcam_state fixture data."""
return cast(
NodeDataType,
load_json_object_fixture("siren_neo_coolcam_nas-ab01z_state.json", DOMAIN),
)
# model fixtures
@ -798,7 +808,7 @@ def nortek_thermostat_removed_event_fixture(client) -> Node:
@pytest.fixture(name="integration")
async def integration_fixture(hass: HomeAssistant, client) -> Node:
async def integration_fixture(hass: HomeAssistant, client) -> MockConfigEntry:
"""Set up the zwave_js integration."""
entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
entry.add_to_hass(hass)
@ -1192,3 +1202,13 @@ def window_covering_outbound_bottom_fixture(
node = Node(client, copy.deepcopy(window_covering_outbound_bottom_state))
client.driver.controller.nodes[node.node_id] = node
return node
@pytest.fixture(name="siren_neo_coolcam")
def siren_neo_coolcam_fixture(
client: MagicMock, siren_neo_coolcam_state: NodeDataType
) -> Node:
"""Load node for neo coolcam siren."""
node = Node(client, siren_neo_coolcam_state)
client.driver.controller.nodes[node.node_id] = node
return node

View file

@ -0,0 +1,746 @@
{
"nodeId": 36,
"index": 0,
"installerIcon": 3840,
"userIcon": 3840,
"status": 4,
"ready": true,
"isListening": false,
"isRouting": true,
"manufacturerId": 600,
"productId": 4232,
"productType": 3,
"firmwareVersion": "2.94",
"zwavePlusVersion": 1,
"deviceConfig": {
"filename": "/usr/src/app/store/.config-db/devices/0x0258/nas-ab01z.json",
"isEmbedded": true,
"manufacturer": "Shenzhen Neo Electronics Co., Ltd.",
"manufacturerId": 600,
"label": "NAS-AB01Z",
"description": "Siren Alarm",
"devices": [
{
"productType": 3,
"productId": 136
},
{
"productType": 3,
"productId": 4232
},
{
"productType": 3,
"productId": 8328
},
{
"productType": 3,
"productId": 24712
}
],
"firmwareVersion": {
"min": "0.0",
"max": "255.255"
},
"preferred": false,
"associations": {},
"paramInformation": {
"_map": {}
}
},
"label": "NAS-AB01Z",
"interviewAttempts": 0,
"isFrequentListening": "1000ms",
"maxDataRate": 100000,
"supportedDataRates": [40000, 100000],
"protocolVersion": 3,
"supportsBeaming": true,
"supportsSecurity": false,
"nodeType": 1,
"zwavePlusNodeType": 0,
"zwavePlusRoleType": 7,
"deviceClass": {
"basic": {
"key": 4,
"label": "Routing End Node"
},
"generic": {
"key": 16,
"label": "Binary Switch"
},
"specific": {
"key": 5,
"label": "Siren"
}
},
"interviewStage": "Complete",
"deviceDatabaseUrl": "https://devices.zwave-js.io/?jumpTo=0x0258:0x0003:0x1088:2.94",
"statistics": {
"commandsTX": 15,
"commandsRX": 7,
"commandsDroppedRX": 0,
"commandsDroppedTX": 0,
"timeoutResponse": 0,
"rtt": 582.5,
"lastSeen": "2024-10-01T10:22:24.457Z",
"lwr": {
"repeaters": [],
"protocolDataRate": 2
}
},
"isControllerNode": false,
"keepAwake": false,
"lastSeen": "2024-09-30T15:07:11.320Z",
"protocol": 0,
"values": [
{
"endpoint": 0,
"commandClass": 37,
"commandClassName": "Binary Switch",
"property": "currentValue",
"propertyName": "currentValue",
"ccVersion": 1,
"metadata": {
"type": "boolean",
"readable": true,
"writeable": false,
"label": "Current value",
"stateful": true,
"secret": false
},
"value": false
},
{
"endpoint": 0,
"commandClass": 37,
"commandClassName": "Binary Switch",
"property": "targetValue",
"propertyName": "targetValue",
"ccVersion": 1,
"metadata": {
"type": "boolean",
"readable": true,
"writeable": true,
"label": "Target value",
"valueChangeOptions": ["transitionDuration"],
"stateful": true,
"secret": false
},
"value": false
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 1,
"propertyName": "Alarm Volume",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"label": "Alarm Volume",
"default": 2,
"min": 1,
"max": 3,
"states": {
"1": "Low",
"2": "Middle",
"3": "High"
},
"valueSize": 1,
"format": 0,
"allowManualEntry": true,
"isFromConfig": true
},
"value": 1
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 2,
"propertyName": "Alarm Duration",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"label": "Alarm Duration",
"default": 2,
"min": 0,
"max": 255,
"states": {
"0": "Off",
"1": "30 seconds",
"2": "1 minute",
"3": "5 minutes",
"255": "Always on"
},
"valueSize": 1,
"format": 1,
"allowManualEntry": false,
"isFromConfig": true
},
"value": 1
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 3,
"propertyName": "Doorbell Duration",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"label": "Doorbell Duration",
"default": 1,
"min": 0,
"max": 255,
"states": {
"0": "Off",
"255": "Always"
},
"valueSize": 1,
"format": 1,
"allowManualEntry": true,
"isFromConfig": true
},
"value": 16
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 4,
"propertyName": "Doorbell Volume",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"label": "Doorbell Volume",
"default": 2,
"min": 1,
"max": 3,
"states": {
"1": "Low",
"2": "Middle",
"3": "High"
},
"valueSize": 1,
"format": 0,
"allowManualEntry": true,
"isFromConfig": true
},
"value": 1
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 5,
"propertyName": "Alarm Sound Selection",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"label": "Alarm Sound Selection",
"default": 10,
"min": 1,
"max": 10,
"states": {
"1": "Doorbell",
"2": "F\u00fcr Elise",
"3": "Westminster Chimes",
"4": "Ding Dong",
"5": "William Tell",
"6": "Rondo Alla Turca",
"7": "Police Siren",
"8": "Evacuation",
"9": "Beep Beep",
"10": "Beep"
},
"valueSize": 1,
"format": 0,
"allowManualEntry": false,
"isFromConfig": true
},
"value": 10
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 6,
"propertyName": "Doorbell Sound Selection",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"label": "Doorbell Sound Selection",
"default": 9,
"min": 1,
"max": 10,
"states": {
"1": "Doorbell",
"2": "F\u00fcr Elise",
"3": "Westminster Chimes",
"4": "Ding Dong",
"5": "William Tell",
"6": "Rondo Alla Turca",
"7": "Police Siren",
"8": "Evacuation",
"9": "Beep Beep",
"10": "Beep"
},
"valueSize": 1,
"format": 0,
"allowManualEntry": false,
"isFromConfig": true
},
"value": 10
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 7,
"propertyName": "Default Siren Sound",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"label": "Default Siren Sound",
"default": 1,
"min": 1,
"max": 2,
"states": {
"1": "Alarm Sound",
"2": "Doorbell Sound"
},
"valueSize": 1,
"format": 0,
"allowManualEntry": true,
"isFromConfig": true
},
"value": 2
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 8,
"propertyName": "Alarm LED",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"label": "Alarm LED",
"default": 1,
"min": 0,
"max": 1,
"states": {
"0": "Disable",
"1": "Enable"
},
"valueSize": 1,
"format": 1,
"allowManualEntry": false,
"isFromConfig": true
},
"value": 1
},
{
"endpoint": 0,
"commandClass": 112,
"commandClassName": "Configuration",
"property": 9,
"propertyName": "Doorbell LED",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"label": "Doorbell LED",
"default": 0,
"min": 0,
"max": 1,
"states": {
"0": "Disable",
"1": "Enable"
},
"valueSize": 1,
"format": 1,
"allowManualEntry": false,
"isFromConfig": true
},
"value": 0
},
{
"endpoint": 0,
"commandClass": 113,
"commandClassName": "Notification",
"property": "Siren",
"propertyKey": "Siren status",
"propertyName": "Siren",
"propertyKeyName": "Siren status",
"ccVersion": 8,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Siren status",
"ccSpecific": {
"notificationType": 14
},
"min": 0,
"max": 255,
"states": {
"0": "idle",
"1": "Siren active"
},
"stateful": true,
"secret": false
},
"value": 0
},
{
"endpoint": 0,
"commandClass": 113,
"commandClassName": "Notification",
"property": "alarmType",
"propertyName": "alarmType",
"ccVersion": 8,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Alarm Type",
"min": 0,
"max": 255,
"stateful": true,
"secret": false
},
"value": 0
},
{
"endpoint": 0,
"commandClass": 113,
"commandClassName": "Notification",
"property": "alarmLevel",
"propertyName": "alarmLevel",
"ccVersion": 8,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Alarm Level",
"min": 0,
"max": 255,
"stateful": true,
"secret": false
},
"value": 0
},
{
"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,
"stateful": true,
"secret": false
},
"value": 600
},
{
"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,
"stateful": true,
"secret": false
},
"value": 3
},
{
"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,
"stateful": true,
"secret": false
},
"value": 4232
},
{
"endpoint": 0,
"commandClass": 128,
"commandClassName": "Battery",
"property": "level",
"propertyName": "level",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Battery level",
"min": 0,
"max": 100,
"unit": "%",
"stateful": true,
"secret": false
},
"value": 89
},
{
"endpoint": 0,
"commandClass": 128,
"commandClassName": "Battery",
"property": "isLow",
"propertyName": "isLow",
"ccVersion": 1,
"metadata": {
"type": "boolean",
"readable": true,
"writeable": false,
"label": "Low battery level",
"stateful": true,
"secret": false
},
"value": false
},
{
"endpoint": 0,
"commandClass": 134,
"commandClassName": "Version",
"property": "libraryType",
"propertyName": "libraryType",
"ccVersion": 2,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Library type",
"states": {
"0": "Unknown",
"1": "Static Controller",
"2": "Controller",
"3": "Enhanced Slave",
"4": "Slave",
"5": "Installer",
"6": "Routing Slave",
"7": "Bridge Controller",
"8": "Device under Test",
"9": "N/A",
"10": "AV Remote",
"11": "AV Device"
},
"stateful": true,
"secret": false
},
"value": 6
},
{
"endpoint": 0,
"commandClass": 134,
"commandClassName": "Version",
"property": "protocolVersion",
"propertyName": "protocolVersion",
"ccVersion": 2,
"metadata": {
"type": "string",
"readable": true,
"writeable": false,
"label": "Z-Wave protocol version",
"stateful": true,
"secret": false
},
"value": "4.38"
},
{
"endpoint": 0,
"commandClass": 134,
"commandClassName": "Version",
"property": "firmwareVersions",
"propertyName": "firmwareVersions",
"ccVersion": 2,
"metadata": {
"type": "string[]",
"readable": true,
"writeable": false,
"label": "Z-Wave chip firmware versions",
"stateful": true,
"secret": false
},
"value": ["2.94"]
},
{
"endpoint": 0,
"commandClass": 134,
"commandClassName": "Version",
"property": "hardwareVersion",
"propertyName": "hardwareVersion",
"ccVersion": 2,
"metadata": {
"type": "number",
"readable": true,
"writeable": false,
"label": "Z-Wave chip hardware version",
"stateful": true,
"secret": false
},
"value": 48
},
{
"endpoint": 0,
"commandClass": 135,
"commandClassName": "Indicator",
"property": "value",
"propertyName": "value",
"ccVersion": 1,
"metadata": {
"type": "number",
"readable": true,
"writeable": true,
"label": "Indicator value",
"ccSpecific": {
"indicatorId": 0
},
"min": 0,
"max": 255,
"stateful": true,
"secret": false
},
"value": 0
}
],
"endpoints": [
{
"nodeId": 36,
"index": 0,
"installerIcon": 3840,
"userIcon": 3840,
"deviceClass": {
"basic": {
"key": 4,
"label": "Routing End Node"
},
"generic": {
"key": 16,
"label": "Binary Switch"
},
"specific": {
"key": 5,
"label": "Siren"
}
},
"commandClasses": [
{
"id": 37,
"name": "Binary Switch",
"version": 1,
"isSecure": false
},
{
"id": 133,
"name": "Association",
"version": 2,
"isSecure": false
},
{
"id": 89,
"name": "Association Group Information",
"version": 1,
"isSecure": false
},
{
"id": 128,
"name": "Battery",
"version": 1,
"isSecure": false
},
{
"id": 114,
"name": "Manufacturer Specific",
"version": 2,
"isSecure": false
},
{
"id": 115,
"name": "Powerlevel",
"version": 1,
"isSecure": false
},
{
"id": 134,
"name": "Version",
"version": 2,
"isSecure": false
},
{
"id": 94,
"name": "Z-Wave Plus Info",
"version": 2,
"isSecure": false
},
{
"id": 90,
"name": "Device Reset Locally",
"version": 1,
"isSecure": false
},
{
"id": 112,
"name": "Configuration",
"version": 1,
"isSecure": false
},
{
"id": 113,
"name": "Notification",
"version": 8,
"isSecure": false
},
{
"id": 135,
"name": "Indicator",
"version": 1,
"isSecure": false
}
]
}
]
}

View file

@ -1,6 +1,8 @@
"""Test entity discovery for device-specific schemas for the Z-Wave JS integration."""
import pytest
from zwave_js_server.event import Event
from zwave_js_server.model.node import Node
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
@ -28,6 +30,8 @@ from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_UNKNOWN, Entity
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from tests.common import MockConfigEntry
async def test_aeon_smart_switch_6_state(
hass: HomeAssistant, client, aeon_smart_switch_6, integration
@ -380,3 +384,45 @@ async def test_light_device_class_is_null(
node = light_device_class_is_null
assert node.device_class is None
assert hass.states.get("light.bar_display_cases")
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_rediscovery(
hass: HomeAssistant,
siren_neo_coolcam: Node,
integration: MockConfigEntry,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test that we don't rediscover known values."""
node = siren_neo_coolcam
entity_id = "select.siren_alarm_doorbell_sound_selection"
state = hass.states.get(entity_id)
assert state
assert state.state == "Beep"
event = Event(
type="value updated",
data={
"source": "node",
"event": "value updated",
"nodeId": 36,
"args": {
"commandClassName": "Configuration",
"commandClass": 112,
"endpoint": 0,
"property": 6,
"newValue": 9,
"prevValue": 10,
"propertyName": "Doorbell Sound Selection",
},
},
)
node.receive_event(event)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state
assert state.state == "Beep Beep"
assert "Platform zwave_js does not generate unique IDs" not in caplog.text