Enable Zwave notification sensors by default (#125326)
* Enable Zwave notification sensors by default * Enable Zwave notification sensors by default * Enable Zwave notification sensors by default * Enable Zwave notification sensors by default * Enable Zwave notification sensors by default * Enable Zwave notification sensors by default * Enable Zwave notification sensors by default * Enable Zwave notification sensors by default * Fix the check to (dis)allow discovering a value multiple times * Prevent discovery of duplicate Notification CC sensors * alarm sensors disabled by default * one more fix * Update diagnostics tests --------- Co-authored-by: Marcel van der Veldt <m.vanderveldt@outlook.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>pull/126725/head
parent
771575cfc5
commit
bebd1dc235
|
@ -248,6 +248,16 @@ BOOLEAN_SENSOR_MAPPINGS: dict[int, BinarySensorEntityDescription] = {
|
|||
}
|
||||
|
||||
|
||||
@callback
|
||||
def is_valid_notification_binary_sensor(
|
||||
info: ZwaveDiscoveryInfo,
|
||||
) -> bool | NotificationZWaveJSEntityDescription:
|
||||
"""Return if the notification CC Value is valid as binary sensor."""
|
||||
if not info.primary_value.metadata.states:
|
||||
return False
|
||||
return len(info.primary_value.metadata.states) > 1
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
|
@ -264,16 +274,18 @@ async def async_setup_entry(
|
|||
entities: list[BinarySensorEntity] = []
|
||||
|
||||
if info.platform_hint == "notification":
|
||||
# ensure the notification CC Value is valid as binary sensor
|
||||
if not is_valid_notification_binary_sensor(info):
|
||||
return
|
||||
# Get all sensors from Notification CC states
|
||||
for state_key in info.primary_value.metadata.states:
|
||||
# ignore idle key (0)
|
||||
if state_key == "0":
|
||||
continue
|
||||
|
||||
# get (optional) description for this state
|
||||
notification_description: (
|
||||
NotificationZWaveJSEntityDescription | None
|
||||
) = None
|
||||
|
||||
for description in NOTIFICATION_SENSOR_MAPPINGS:
|
||||
if (
|
||||
int(description.key)
|
||||
|
@ -289,7 +301,6 @@ async def async_setup_entry(
|
|||
and notification_description.off_state == state_key
|
||||
):
|
||||
continue
|
||||
|
||||
entities.append(
|
||||
ZWaveNotificationBinarySensor(
|
||||
config_entry, driver, info, state_key, notification_description
|
||||
|
|
|
@ -80,7 +80,7 @@ def get_device_entities(
|
|||
er.async_get(hass), device.id, include_disabled_entities=True
|
||||
)
|
||||
entities = []
|
||||
for entry in entity_entries:
|
||||
for entry in sorted(entity_entries):
|
||||
# Skip entities that are not part of this integration
|
||||
if entry.config_entry_id != config_entry.entry_id:
|
||||
continue
|
||||
|
|
|
@ -885,17 +885,6 @@ DISCOVERY_SCHEMAS = [
|
|||
type={ValueType.BOOLEAN},
|
||||
),
|
||||
),
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
hint="notification",
|
||||
primary_value=ZWaveValueDiscoverySchema(
|
||||
command_class={
|
||||
CommandClass.NOTIFICATION,
|
||||
},
|
||||
type={ValueType.NUMBER},
|
||||
),
|
||||
allow_multi=True,
|
||||
),
|
||||
# binary sensor for Indicator CC
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
|
@ -957,19 +946,6 @@ DISCOVERY_SCHEMAS = [
|
|||
),
|
||||
data_template=NumericSensorDataTemplate(),
|
||||
),
|
||||
# special list sensors (Notification CC)
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
hint="list_sensor",
|
||||
primary_value=ZWaveValueDiscoverySchema(
|
||||
command_class={
|
||||
CommandClass.NOTIFICATION,
|
||||
},
|
||||
type={ValueType.NUMBER},
|
||||
),
|
||||
allow_multi=True,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
# number for Indicator CC (exclude property keys 3-5)
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.NUMBER,
|
||||
|
@ -1196,6 +1172,7 @@ DISCOVERY_SCHEMAS = [
|
|||
type={ValueType.NUMBER},
|
||||
any_available_states={(0, "idle")},
|
||||
),
|
||||
allow_multi=True,
|
||||
),
|
||||
# event
|
||||
# stateful = False
|
||||
|
@ -1218,6 +1195,43 @@ DISCOVERY_SCHEMAS = [
|
|||
),
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
hint="notification",
|
||||
primary_value=ZWaveValueDiscoverySchema(
|
||||
command_class={
|
||||
CommandClass.NOTIFICATION,
|
||||
},
|
||||
type={ValueType.NUMBER},
|
||||
),
|
||||
# set allow-multi to true because some of the notification sensors
|
||||
# can not be mapped to a binary sensor and must be handled as a regular sensor
|
||||
allow_multi=True,
|
||||
),
|
||||
# alarmType, alarmLevel (Notification CC)
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
hint="notification_alarm",
|
||||
primary_value=ZWaveValueDiscoverySchema(
|
||||
command_class={
|
||||
CommandClass.NOTIFICATION,
|
||||
},
|
||||
property={"alarmType", "alarmLevel"},
|
||||
type={ValueType.NUMBER},
|
||||
),
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
# fallback sensors within Notification CC
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
hint="notification",
|
||||
primary_value=ZWaveValueDiscoverySchema(
|
||||
command_class={
|
||||
CommandClass.NOTIFICATION,
|
||||
},
|
||||
type={ValueType.NUMBER},
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
@ -1237,8 +1251,11 @@ def async_discover_single_value(
|
|||
value: ZwaveValue, device: DeviceEntry, discovered_value_ids: dict[str, set[str]]
|
||||
) -> Generator[ZwaveDiscoveryInfo]:
|
||||
"""Run discovery on a single ZWave value and return matching schema info."""
|
||||
discovered_value_ids[device.id].add(value.value_id)
|
||||
for schema in DISCOVERY_SCHEMAS:
|
||||
# abort if attribute(s) already discovered
|
||||
if value.value_id in discovered_value_ids[device.id]:
|
||||
continue
|
||||
|
||||
# check manufacturer_id, product_id, product_type
|
||||
if (
|
||||
(
|
||||
|
@ -1342,10 +1359,9 @@ def async_discover_single_value(
|
|||
entity_category=schema.entity_category,
|
||||
)
|
||||
|
||||
# prevent re-discovery of the (primary) value if not allowed
|
||||
if not schema.allow_multi:
|
||||
# return early since this value may not be discovered
|
||||
# by other schemas/platforms
|
||||
return
|
||||
discovered_value_ids[device.id].add(value.value_id)
|
||||
|
||||
if value.command_class == CommandClass.CONFIGURATION:
|
||||
yield from async_discover_single_configuration_value(
|
||||
|
|
|
@ -51,6 +51,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import UNDEFINED, StateType
|
||||
|
||||
from .binary_sensor import is_valid_notification_binary_sensor
|
||||
from .const import (
|
||||
ATTR_METER_TYPE,
|
||||
ATTR_METER_TYPE_NAME,
|
||||
|
@ -580,7 +581,10 @@ async def async_setup_entry(
|
|||
data.unit_of_measurement,
|
||||
)
|
||||
)
|
||||
elif info.platform_hint == "list_sensor":
|
||||
elif info.platform_hint == "notification":
|
||||
# prevent duplicate entities for values that are already represented as binary sensors
|
||||
if is_valid_notification_binary_sensor(info):
|
||||
return
|
||||
entities.append(
|
||||
ZWaveListSensor(config_entry, driver, info, entity_description)
|
||||
)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,9 +1,11 @@
|
|||
"""Test the Z-Wave JS diagnostics."""
|
||||
|
||||
import copy
|
||||
from typing import Any, cast
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
from zwave_js_server.const import CommandClass
|
||||
from zwave_js_server.event import Event
|
||||
from zwave_js_server.model.node import Node
|
||||
|
@ -13,7 +15,6 @@ from homeassistant.components.zwave_js.diagnostics import (
|
|||
ZwaveValueMatcher,
|
||||
async_get_device_diagnostics,
|
||||
)
|
||||
from homeassistant.components.zwave_js.discovery import async_discover_node_values
|
||||
from homeassistant.components.zwave_js.helpers import (
|
||||
get_device_id,
|
||||
get_value_id_from_unique_id,
|
||||
|
@ -58,6 +59,7 @@ async def test_device_diagnostics(
|
|||
integration,
|
||||
hass_client: ClientSessionGenerator,
|
||||
version_state,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test the device level diagnostics data dump."""
|
||||
device = device_registry.async_get_device(
|
||||
|
@ -113,18 +115,18 @@ async def test_device_diagnostics(
|
|||
# Entities that are created outside of discovery (e.g. node status sensor and
|
||||
# ping button) as well as helper entities created from other integrations should
|
||||
# not be in dump.
|
||||
assert len(diagnostics_data["entities"]) == len(
|
||||
list(async_discover_node_values(multisensor_6, device, {device.id: set()}))
|
||||
)
|
||||
assert diagnostics_data == snapshot
|
||||
|
||||
assert any(
|
||||
entity.entity_id == "test.unrelated_entity"
|
||||
for entity in er.async_entries_for_device(entity_registry, device.id)
|
||||
entity_entry.entity_id == "test.unrelated_entity"
|
||||
for entity_entry in er.async_entries_for_device(entity_registry, device.id)
|
||||
)
|
||||
# Explicitly check that the entity that is not part of this config entry is not
|
||||
# in the dump.
|
||||
diagnostics_entities = cast(list[dict[str, Any]], diagnostics_data["entities"])
|
||||
assert not any(
|
||||
entity["entity_id"] == "test.unrelated_entity"
|
||||
for entity in diagnostics_data["entities"]
|
||||
for entity in diagnostics_entities
|
||||
)
|
||||
assert diagnostics_data["state"] == {
|
||||
**multisensor_6.data,
|
||||
|
@ -171,6 +173,7 @@ async def test_device_diagnostics_missing_primary_value(
|
|||
|
||||
entity_id = "sensor.multisensor_6_air_temperature"
|
||||
entry = entity_registry.async_get(entity_id)
|
||||
assert entry
|
||||
|
||||
# check that the primary value for the entity exists in the diagnostics
|
||||
diagnostics_data = await get_diagnostics_for_device(
|
||||
|
@ -180,9 +183,8 @@ async def test_device_diagnostics_missing_primary_value(
|
|||
value = multisensor_6.values.get(get_value_id_from_unique_id(entry.unique_id))
|
||||
assert value
|
||||
|
||||
air_entity = next(
|
||||
x for x in diagnostics_data["entities"] if x["entity_id"] == entity_id
|
||||
)
|
||||
diagnostics_entities = cast(list[dict[str, Any]], diagnostics_data["entities"])
|
||||
air_entity = next(x for x in diagnostics_entities if x["entity_id"] == entity_id)
|
||||
|
||||
assert air_entity["value_id"] == value.value_id
|
||||
assert air_entity["primary_value"] == {
|
||||
|
@ -218,9 +220,8 @@ async def test_device_diagnostics_missing_primary_value(
|
|||
hass, hass_client, integration, device
|
||||
)
|
||||
|
||||
air_entity = next(
|
||||
x for x in diagnostics_data["entities"] if x["entity_id"] == entity_id
|
||||
)
|
||||
diagnostics_entities = cast(list[dict[str, Any]], diagnostics_data["entities"])
|
||||
air_entity = next(x for x in diagnostics_entities if x["entity_id"] == entity_id)
|
||||
|
||||
assert air_entity["value_id"] == value.value_id
|
||||
assert air_entity["primary_value"] is None
|
||||
|
@ -266,5 +267,6 @@ async def test_device_diagnostics_secret_value(
|
|||
diagnostics_data = await get_diagnostics_for_device(
|
||||
hass, hass_client, integration, device
|
||||
)
|
||||
test_value = _find_ultraviolet_val(diagnostics_data["state"])
|
||||
diagnostics_node_state = cast(dict[str, Any], diagnostics_data["state"])
|
||||
test_value = _find_ultraviolet_val(diagnostics_node_state)
|
||||
assert test_value["value"] == REDACTED
|
||||
|
|
|
@ -1574,13 +1574,9 @@ async def test_disabled_entity_on_value_removed(
|
|||
hass: HomeAssistant, entity_registry: er.EntityRegistry, zp3111, client, integration
|
||||
) -> None:
|
||||
"""Test that when entity primary values are removed the entity is removed."""
|
||||
# re-enable this default-disabled entity
|
||||
sensor_cover_entity = "sensor.4_in_1_sensor_home_security_cover_status"
|
||||
idle_cover_status_button_entity = (
|
||||
"button.4_in_1_sensor_idle_home_security_cover_status"
|
||||
)
|
||||
entity_registry.async_update_entity(entity_id=sensor_cover_entity, disabled_by=None)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# must reload the integration when enabling an entity
|
||||
await hass.config_entries.async_unload(integration.entry_id)
|
||||
|
@ -1591,10 +1587,6 @@ async def test_disabled_entity_on_value_removed(
|
|||
await hass.async_block_till_done()
|
||||
assert integration.state is ConfigEntryState.LOADED
|
||||
|
||||
state = hass.states.get(sensor_cover_entity)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
state = hass.states.get(idle_cover_status_button_entity)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
@ -1688,10 +1680,6 @@ async def test_disabled_entity_on_value_removed(
|
|||
assert state
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
state = hass.states.get(sensor_cover_entity)
|
||||
assert state
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
state = hass.states.get(idle_cover_status_button_entity)
|
||||
assert state
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
@ -1707,7 +1695,6 @@ async def test_disabled_entity_on_value_removed(
|
|||
| {
|
||||
battery_level_entity,
|
||||
binary_cover_entity,
|
||||
sensor_cover_entity,
|
||||
idle_cover_status_button_entity,
|
||||
}
|
||||
== new_unavailable_entities
|
||||
|
|
|
@ -9,7 +9,6 @@ from zwave_js_server.exceptions import FailedZWaveCommand
|
|||
from zwave_js_server.model.node import Node
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
ATTR_OPTIONS,
|
||||
ATTR_STATE_CLASS,
|
||||
SensorDeviceClass,
|
||||
SensorStateClass,
|
||||
|
@ -54,7 +53,6 @@ from .common import (
|
|||
ENERGY_SENSOR,
|
||||
HUMIDITY_SENSOR,
|
||||
METER_ENERGY_SENSOR,
|
||||
NOTIFICATION_MOTION_SENSOR,
|
||||
POWER_SENSOR,
|
||||
VOLTAGE_SENSOR,
|
||||
)
|
||||
|
@ -227,60 +225,6 @@ async def test_basic_cc_sensor(
|
|||
assert state.state == "255.0"
|
||||
|
||||
|
||||
async def test_disabled_notification_sensor(
|
||||
hass: HomeAssistant, entity_registry: er.EntityRegistry, multisensor_6, integration
|
||||
) -> None:
|
||||
"""Test sensor is created from Notification CC and is disabled."""
|
||||
entity_entry = entity_registry.async_get(NOTIFICATION_MOTION_SENSOR)
|
||||
|
||||
assert entity_entry
|
||||
assert entity_entry.disabled
|
||||
assert entity_entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
|
||||
|
||||
# Test enabling entity
|
||||
updated_entry = entity_registry.async_update_entity(
|
||||
entity_entry.entity_id, disabled_by=None
|
||||
)
|
||||
assert updated_entry != entity_entry
|
||||
assert updated_entry.disabled is False
|
||||
|
||||
# reload integration and check if entity is correctly there
|
||||
await hass.config_entries.async_reload(integration.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(NOTIFICATION_MOTION_SENSOR)
|
||||
assert state.state == "Motion detection"
|
||||
assert state.attributes[ATTR_VALUE] == 8
|
||||
assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.ENUM
|
||||
assert state.attributes[ATTR_OPTIONS] == ["idle", "Motion detection"]
|
||||
|
||||
event = Event(
|
||||
"value updated",
|
||||
{
|
||||
"source": "node",
|
||||
"event": "value updated",
|
||||
"nodeId": multisensor_6.node_id,
|
||||
"args": {
|
||||
"commandClassName": "Notification",
|
||||
"commandClass": 113,
|
||||
"endpoint": 0,
|
||||
"property": "Home Security",
|
||||
"propertyKey": "Motion sensor status",
|
||||
"newValue": None,
|
||||
"prevValue": 0,
|
||||
"propertyName": "Home Security",
|
||||
"propertyKeyName": "Motion sensor status",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
multisensor_6.receive_event(event)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(NOTIFICATION_MOTION_SENSOR)
|
||||
assert state
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
|
||||
async def test_config_parameter_sensor(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
|
|
Loading…
Reference in New Issue