mirror of
https://github.com/home-assistant/core.git
synced 2026-09-10 15:41:22 +01:00
Fix Z-Wave value updated trigger KeyError (#179992)
This commit is contained in:
@@ -115,19 +115,23 @@ async def async_attach_trigger(
|
||||
|
||||
@callback
|
||||
def async_on_value_updated(
|
||||
value: Value, device: dr.DeviceEntry, event: dict
|
||||
value_id: str, device: dr.DeviceEntry, event: dict
|
||||
) -> None:
|
||||
"""Handle value update."""
|
||||
event_value: Value = event["value"]
|
||||
if event_value != value:
|
||||
if event_value.value_id != value_id:
|
||||
return
|
||||
|
||||
# Get previous value and its state value if it exists
|
||||
prev_value_raw = event["args"]["prevValue"]
|
||||
prev_value = value.metadata.states.get(str(prev_value_raw), prev_value_raw)
|
||||
prev_value = event_value.metadata.states.get(
|
||||
str(prev_value_raw), prev_value_raw
|
||||
)
|
||||
# Get current value and its state value if it exists
|
||||
curr_value_raw = event["args"]["newValue"]
|
||||
curr_value = value.metadata.states.get(str(curr_value_raw), curr_value_raw)
|
||||
curr_value = event_value.metadata.states.get(
|
||||
str(curr_value_raw), curr_value_raw
|
||||
)
|
||||
# Check from and to values against previous and current values respectively
|
||||
for value_to_eval, raw_value_to_eval, match in (
|
||||
(prev_value, prev_value_raw, from_value),
|
||||
@@ -140,17 +144,17 @@ async def async_attach_trigger(
|
||||
return
|
||||
|
||||
device_name = device.name_by_user or device.name
|
||||
description = f"Z-Wave value {value.value_id} updated on {device_name}"
|
||||
description = f"Z-Wave value {event_value.value_id} updated on {device_name}"
|
||||
payload = {
|
||||
ATTR_DEVICE_ID: device.id,
|
||||
ATTR_NODE_ID: value.node.node_id,
|
||||
ATTR_COMMAND_CLASS: value.command_class,
|
||||
ATTR_COMMAND_CLASS_NAME: value.command_class_name,
|
||||
ATTR_PROPERTY: value.property_,
|
||||
ATTR_PROPERTY_NAME: value.property_name,
|
||||
ATTR_NODE_ID: event_value.node.node_id,
|
||||
ATTR_COMMAND_CLASS: event_value.command_class,
|
||||
ATTR_COMMAND_CLASS_NAME: event_value.command_class_name,
|
||||
ATTR_PROPERTY: event_value.property_,
|
||||
ATTR_PROPERTY_NAME: event_value.property_name,
|
||||
ATTR_ENDPOINT: endpoint,
|
||||
ATTR_PROPERTY_KEY: value.property_key,
|
||||
ATTR_PROPERTY_KEY_NAME: value.property_key_name,
|
||||
ATTR_PROPERTY_KEY: event_value.property_key,
|
||||
ATTR_PROPERTY_KEY_NAME: event_value.property_key_name,
|
||||
ATTR_PREVIOUS_VALUE: prev_value,
|
||||
ATTR_PREVIOUS_VALUE_RAW: prev_value_raw,
|
||||
ATTR_CURRENT_VALUE: curr_value,
|
||||
@@ -185,12 +189,10 @@ async def async_attach_trigger(
|
||||
value_id = get_value_id_str(
|
||||
node, command_class, property_, endpoint, property_key
|
||||
)
|
||||
value = node.values[value_id]
|
||||
# We need to store the current value and device for the callback
|
||||
unsubs.append(
|
||||
node.on(
|
||||
EVENT_VALUE_UPDATED,
|
||||
functools.partial(async_on_value_updated, value, device),
|
||||
functools.partial(async_on_value_updated, value_id, device),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""The tests for Z-Wave JS automation triggers."""
|
||||
|
||||
import copy
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
@@ -1322,6 +1323,105 @@ async def test_server_reconnect_value_updated(
|
||||
assert old_listener not in new_node._listeners.get(event_name, [])
|
||||
|
||||
|
||||
async def test_server_reconnect_value_updated_missing_value(
|
||||
hass: HomeAssistant,
|
||||
client,
|
||||
lock_schlage_be469,
|
||||
lock_schlage_be469_state,
|
||||
integration,
|
||||
) -> None:
|
||||
"""Test value_updated trigger re-registers when value is absent at reconnect."""
|
||||
trigger_type = f"{DOMAIN}.value_updated"
|
||||
|
||||
no_value_filter = async_capture_events(hass, "no_value_filter")
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: [
|
||||
{
|
||||
"trigger": {
|
||||
"platform": trigger_type,
|
||||
"options": {
|
||||
"entity_id": SCHLAGE_BE469_LOCK_ENTITY,
|
||||
"command_class": CommandClass.DOOR_LOCK.value,
|
||||
"property": "latchStatus",
|
||||
},
|
||||
},
|
||||
"action": {
|
||||
"event": "no_value_filter",
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
# Remove the node so we can re-add it without the target value, simulating the
|
||||
# reconnect race where node.values is not yet fully populated when
|
||||
# _create_zwave_listeners runs.
|
||||
node_removed_event = Event(
|
||||
type="node removed",
|
||||
data={
|
||||
"source": "controller",
|
||||
"event": "node removed",
|
||||
"reason": 0,
|
||||
"node": lock_schlage_be469_state,
|
||||
},
|
||||
)
|
||||
client.driver.controller.receive_event(node_removed_event)
|
||||
assert 20 not in client.driver.controller.nodes
|
||||
await hass.async_block_till_done()
|
||||
|
||||
partial_state = copy.deepcopy(lock_schlage_be469_state)
|
||||
partial_state["values"] = [
|
||||
v for v in partial_state["values"] if v.get("propertyName") != "latchStatus"
|
||||
]
|
||||
node_added_event = Event(
|
||||
type="node added",
|
||||
data={
|
||||
"source": "controller",
|
||||
"event": "node added",
|
||||
"node": partial_state,
|
||||
"result": {},
|
||||
},
|
||||
)
|
||||
client.driver.controller.receive_event(node_added_event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Reload the integration, which fires the connected_to_server signal that causes
|
||||
# _create_zwave_listeners to re-register listeners on the new node. With the old
|
||||
# code the missing latchStatus value would raise a KeyError and leave the trigger
|
||||
# without a listener.
|
||||
await hass.config_entries.async_reload(integration.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Fire a value updated event on the new node — the trigger must fire even though
|
||||
# the value was absent when _create_zwave_listeners ran.
|
||||
new_node = client.driver.controller.nodes[20]
|
||||
value_updated_event = Event(
|
||||
type="value updated",
|
||||
data={
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": new_node.node_id,
|
||||
"args": {
|
||||
"commandClassName": "Door Lock",
|
||||
"commandClass": CommandClass.DOOR_LOCK.value,
|
||||
"endpoint": 0,
|
||||
"property": "latchStatus",
|
||||
"newValue": "boo",
|
||||
"prevValue": "hiss",
|
||||
"propertyName": "latchStatus",
|
||||
},
|
||||
},
|
||||
)
|
||||
new_node.receive_event(value_updated_event)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(no_value_filter) == 1
|
||||
|
||||
|
||||
async def test_zwave_js_old_syntax(
|
||||
hass: HomeAssistant, client, lock_schlage_be469, integration
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user