mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 04:07:08 +00:00
Fix Z-Wave device class endpoint discovery (#142171)
* Add test fixture and test for Glass 9 shutter * Fix zwave_js device class discovery matcher * Fall back to node device class * Fix test_special_meters modifying node state * Handle value added after node ready
This commit is contained in:
parent
341d9f15f0
commit
11564e3df5
@ -1334,21 +1334,49 @@ def async_discover_single_value(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# check device_class_generic
|
# check device_class_generic
|
||||||
|
# If the value has an endpoint but it is missing on the node
|
||||||
|
# we can't match the endpoint device class to the schema device class.
|
||||||
|
# This could happen if the value is discovered after the node is ready.
|
||||||
if schema.device_class_generic and (
|
if schema.device_class_generic and (
|
||||||
not value.node.device_class
|
(
|
||||||
or not any(
|
(endpoint := value.endpoint) is None
|
||||||
value.node.device_class.generic.label == val
|
or (node_endpoint := value.node.endpoints.get(endpoint)) is None
|
||||||
for val in schema.device_class_generic
|
or (device_class := node_endpoint.device_class) is None
|
||||||
|
or not any(
|
||||||
|
device_class.generic.label == val
|
||||||
|
for val in schema.device_class_generic
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and (
|
||||||
|
(device_class := value.node.device_class) is None
|
||||||
|
or not any(
|
||||||
|
device_class.generic.label == val
|
||||||
|
for val in schema.device_class_generic
|
||||||
|
)
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# check device_class_specific
|
# check device_class_specific
|
||||||
|
# If the value has an endpoint but it is missing on the node
|
||||||
|
# we can't match the endpoint device class to the schema device class.
|
||||||
|
# This could happen if the value is discovered after the node is ready.
|
||||||
if schema.device_class_specific and (
|
if schema.device_class_specific and (
|
||||||
not value.node.device_class
|
(
|
||||||
or not any(
|
(endpoint := value.endpoint) is None
|
||||||
value.node.device_class.specific.label == val
|
or (node_endpoint := value.node.endpoints.get(endpoint)) is None
|
||||||
for val in schema.device_class_specific
|
or (device_class := node_endpoint.device_class) is None
|
||||||
|
or not any(
|
||||||
|
device_class.specific.label == val
|
||||||
|
for val in schema.device_class_specific
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and (
|
||||||
|
(device_class := value.node.device_class) is None
|
||||||
|
or not any(
|
||||||
|
device_class.specific.label == val
|
||||||
|
for val in schema.device_class_specific
|
||||||
|
)
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
@ -301,6 +301,12 @@ def shelly_europe_ltd_qnsh_001p10_state_fixture() -> dict[str, Any]:
|
|||||||
return load_json_object_fixture("shelly_europe_ltd_qnsh_001p10_state.json", DOMAIN)
|
return load_json_object_fixture("shelly_europe_ltd_qnsh_001p10_state.json", DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="touchwand_glass9_state", scope="package")
|
||||||
|
def touchwand_glass9_state_fixture() -> dict[str, Any]:
|
||||||
|
"""Load the Touchwand Glass 9 shutter node state fixture data."""
|
||||||
|
return load_json_object_fixture("touchwand_glass9_state.json", DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="merten_507801_state", scope="package")
|
@pytest.fixture(name="merten_507801_state", scope="package")
|
||||||
def merten_507801_state_fixture() -> dict[str, Any]:
|
def merten_507801_state_fixture() -> dict[str, Any]:
|
||||||
"""Load the Merten 507801 Shutter node state fixture data."""
|
"""Load the Merten 507801 Shutter node state fixture data."""
|
||||||
@ -1040,6 +1046,14 @@ def shelly_qnsh_001P10_cover_shutter_fixture(
|
|||||||
return node
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="touchwand_glass9")
|
||||||
|
def touchwand_glass9_fixture(client, touchwand_glass9_state) -> Node:
|
||||||
|
"""Mock a Touchwand glass9 node."""
|
||||||
|
node = Node(client, copy.deepcopy(touchwand_glass9_state))
|
||||||
|
client.driver.controller.nodes[node.node_id] = node
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="merten_507801")
|
@pytest.fixture(name="merten_507801")
|
||||||
def merten_507801_cover_fixture(client, merten_507801_state) -> Node:
|
def merten_507801_cover_fixture(client, merten_507801_state) -> Node:
|
||||||
"""Mock a Merten 507801 Shutter node."""
|
"""Mock a Merten 507801 Shutter node."""
|
||||||
|
File diff suppressed because it is too large
Load Diff
3467
tests/components/zwave_js/fixtures/touchwand_glass9_state.json
Normal file
3467
tests/components/zwave_js/fixtures/touchwand_glass9_state.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -56,6 +56,24 @@ async def test_iblinds_v2(hass: HomeAssistant, client, iblinds_v2, integration)
|
|||||||
assert state
|
assert state
|
||||||
|
|
||||||
|
|
||||||
|
async def test_touchwand_glass9(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
client: MagicMock,
|
||||||
|
touchwand_glass9: Node,
|
||||||
|
integration: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test a touchwand_glass9 is discovered as a cover."""
|
||||||
|
node = touchwand_glass9
|
||||||
|
node_device_class = node.device_class
|
||||||
|
assert node_device_class
|
||||||
|
assert node_device_class.specific.label == "Unused"
|
||||||
|
|
||||||
|
assert not hass.states.async_entity_ids_count("light")
|
||||||
|
assert hass.states.async_entity_ids_count("cover") == 3
|
||||||
|
state = hass.states.get("cover.gp9")
|
||||||
|
assert state
|
||||||
|
|
||||||
|
|
||||||
async def test_zvidar_state(hass: HomeAssistant, client, zvidar, integration) -> None:
|
async def test_zvidar_state(hass: HomeAssistant, client, zvidar, integration) -> None:
|
||||||
"""Test that an ZVIDAR Z-CM-V01 multilevel switch value is discovered as a cover."""
|
"""Test that an ZVIDAR Z-CM-V01 multilevel switch value is discovered as a cover."""
|
||||||
node = zvidar
|
node = zvidar
|
||||||
|
@ -27,7 +27,7 @@ from homeassistant.components.persistent_notification import async_dismiss
|
|||||||
from homeassistant.components.zwave_js import DOMAIN
|
from homeassistant.components.zwave_js import DOMAIN
|
||||||
from homeassistant.components.zwave_js.helpers import get_device_id, get_device_id_ext
|
from homeassistant.components.zwave_js.helpers import get_device_id, get_device_id_ext
|
||||||
from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState
|
||||||
from homeassistant.const import STATE_UNAVAILABLE
|
from homeassistant.const import STATE_UNAVAILABLE, Platform
|
||||||
from homeassistant.core import CoreState, HomeAssistant
|
from homeassistant.core import CoreState, HomeAssistant
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
area_registry as ar,
|
area_registry as ar,
|
||||||
@ -366,6 +366,7 @@ async def test_listen_done_after_setup(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("client")
|
@pytest.mark.usefixtures("client")
|
||||||
|
@pytest.mark.parametrize("platforms", [[Platform.SENSOR]])
|
||||||
async def test_new_entity_on_value_added(
|
async def test_new_entity_on_value_added(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
multisensor_6: Node,
|
multisensor_6: Node,
|
||||||
|
@ -655,6 +655,17 @@ async def test_special_meters(
|
|||||||
"value": 659.813,
|
"value": 659.813,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
node_data["endpoints"].append(
|
||||||
|
{
|
||||||
|
"nodeId": 102,
|
||||||
|
"index": 10,
|
||||||
|
"installerIcon": 1792,
|
||||||
|
"userIcon": 1792,
|
||||||
|
"commandClasses": [
|
||||||
|
{"id": 50, "name": "Meter", "version": 3, "isSecure": False}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
# Add an ElectricScale.KILOVOLT_AMPERE_REACTIVE value to the state so we can test that
|
# Add an ElectricScale.KILOVOLT_AMPERE_REACTIVE value to the state so we can test that
|
||||||
# it is handled differently (no device class)
|
# it is handled differently (no device class)
|
||||||
node_data["values"].append(
|
node_data["values"].append(
|
||||||
@ -678,6 +689,17 @@ async def test_special_meters(
|
|||||||
"value": 659.813,
|
"value": 659.813,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
node_data["endpoints"].append(
|
||||||
|
{
|
||||||
|
"nodeId": 102,
|
||||||
|
"index": 11,
|
||||||
|
"installerIcon": 1792,
|
||||||
|
"userIcon": 1792,
|
||||||
|
"commandClasses": [
|
||||||
|
{"id": 50, "name": "Meter", "version": 3, "isSecure": False}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
node = Node(client, node_data)
|
node = Node(client, node_data)
|
||||||
event = {"node": node}
|
event = {"node": node}
|
||||||
client.driver.controller.emit("node added", event)
|
client.driver.controller.emit("node added", event)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user