mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Add zwave_js binary sensor entity category (#58703)
* Add zwave_js binary sensor entity category * Handle non idle notification state * Fix door state * Fix duplicate door state description * Add tests
This commit is contained in:
parent
4b228e3add
commit
d5fcf0b622
@ -19,15 +19,18 @@ from homeassistant.components.binary_sensor import (
|
||||
DEVICE_CLASS_LOCK,
|
||||
DEVICE_CLASS_MOISTURE,
|
||||
DEVICE_CLASS_MOTION,
|
||||
DEVICE_CLASS_PLUG,
|
||||
DEVICE_CLASS_PROBLEM,
|
||||
DEVICE_CLASS_SAFETY,
|
||||
DEVICE_CLASS_SMOKE,
|
||||
DEVICE_CLASS_SOUND,
|
||||
DEVICE_CLASS_TAMPER,
|
||||
DOMAIN as BINARY_SENSOR_DOMAIN,
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ENTITY_CATEGORY_DIAGNOSTIC
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
@ -63,6 +66,7 @@ NOTIFICATION_GAS = "18"
|
||||
class NotificationZWaveJSEntityDescription(BinarySensorEntityDescription):
|
||||
"""Represent a Z-Wave JS binary sensor entity description."""
|
||||
|
||||
off_state: str = "0"
|
||||
states: tuple[str, ...] | None = None
|
||||
|
||||
|
||||
@ -145,17 +149,12 @@ NOTIFICATION_SENSOR_MAPPINGS: tuple[NotificationZWaveJSEntityDescription, ...] =
|
||||
device_class=DEVICE_CLASS_LOCK,
|
||||
),
|
||||
NotificationZWaveJSEntityDescription(
|
||||
# NotificationType 6: Access Control - State Id 16 (door/window open)
|
||||
# NotificationType 6: Access Control - State Id 22 (door/window open)
|
||||
key=NOTIFICATION_ACCESS_CONTROL,
|
||||
states=("22",),
|
||||
off_state="23",
|
||||
states=("22", "23"),
|
||||
device_class=DEVICE_CLASS_DOOR,
|
||||
),
|
||||
NotificationZWaveJSEntityDescription(
|
||||
# NotificationType 6: Access Control - State Id 17 (door/window closed)
|
||||
key=NOTIFICATION_ACCESS_CONTROL,
|
||||
states=("23",),
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
NotificationZWaveJSEntityDescription(
|
||||
# NotificationType 7: Home Security - State Id's 1, 2 (intrusion)
|
||||
key=NOTIFICATION_HOME_SECURITY,
|
||||
@ -166,7 +165,8 @@ NOTIFICATION_SENSOR_MAPPINGS: tuple[NotificationZWaveJSEntityDescription, ...] =
|
||||
# NotificationType 7: Home Security - State Id's 3, 4, 9 (tampering)
|
||||
key=NOTIFICATION_HOME_SECURITY,
|
||||
states=("3", "4", "9"),
|
||||
device_class=DEVICE_CLASS_SAFETY,
|
||||
device_class=DEVICE_CLASS_TAMPER,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
NotificationZWaveJSEntityDescription(
|
||||
# NotificationType 7: Home Security - State Id's 5, 6 (glass breakage)
|
||||
@ -180,6 +180,23 @@ NOTIFICATION_SENSOR_MAPPINGS: tuple[NotificationZWaveJSEntityDescription, ...] =
|
||||
states=("7", "8"),
|
||||
device_class=DEVICE_CLASS_MOTION,
|
||||
),
|
||||
NotificationZWaveJSEntityDescription(
|
||||
# NotificationType 8: Power Management -
|
||||
# State Id's 2, 3 (Mains status)
|
||||
key=NOTIFICATION_POWER_MANAGEMENT,
|
||||
off_state="2",
|
||||
states=("2", "3"),
|
||||
device_class=DEVICE_CLASS_PLUG,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
NotificationZWaveJSEntityDescription(
|
||||
# NotificationType 8: Power Management -
|
||||
# State Id's 10, 11, 17 (Battery maintenance status)
|
||||
key=NOTIFICATION_POWER_MANAGEMENT,
|
||||
states=("10", "11", "17"),
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
NotificationZWaveJSEntityDescription(
|
||||
# NotificationType 9: System - State Id's 1, 2, 6, 7
|
||||
key=NOTIFICATION_SYSTEM,
|
||||
@ -228,6 +245,7 @@ BOOLEAN_SENSOR_MAPPINGS: dict[str, BinarySensorEntityDescription] = {
|
||||
CommandClass.BATTERY: BinarySensorEntityDescription(
|
||||
key=str(CommandClass.BATTERY),
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
|
||||
@ -251,16 +269,41 @@ async def async_setup_entry(
|
||||
# ignore idle key (0)
|
||||
if state_key == "0":
|
||||
continue
|
||||
|
||||
notification_description: NotificationZWaveJSEntityDescription | None = (
|
||||
None
|
||||
)
|
||||
|
||||
for description in NOTIFICATION_SENSOR_MAPPINGS:
|
||||
if (
|
||||
int(description.key)
|
||||
== info.primary_value.metadata.cc_specific[
|
||||
CC_SPECIFIC_NOTIFICATION_TYPE
|
||||
]
|
||||
) and (not description.states or state_key in description.states):
|
||||
notification_description = description
|
||||
break
|
||||
|
||||
if (
|
||||
notification_description
|
||||
and notification_description.off_state == state_key
|
||||
):
|
||||
continue
|
||||
|
||||
entities.append(
|
||||
ZWaveNotificationBinarySensor(config_entry, client, info, state_key)
|
||||
ZWaveNotificationBinarySensor(
|
||||
config_entry, client, info, state_key, notification_description
|
||||
)
|
||||
)
|
||||
elif info.platform_hint == "property" and (
|
||||
description := PROPERTY_SENSOR_MAPPINGS.get(
|
||||
property_description := PROPERTY_SENSOR_MAPPINGS.get(
|
||||
info.primary_value.property_name
|
||||
)
|
||||
):
|
||||
entities.append(
|
||||
ZWavePropertyBinarySensor(config_entry, client, info, description)
|
||||
ZWavePropertyBinarySensor(
|
||||
config_entry, client, info, property_description
|
||||
)
|
||||
)
|
||||
else:
|
||||
# boolean sensor
|
||||
@ -313,12 +356,12 @@ class ZWaveNotificationBinarySensor(ZWaveBaseEntity, BinarySensorEntity):
|
||||
client: ZwaveClient,
|
||||
info: ZwaveDiscoveryInfo,
|
||||
state_key: str,
|
||||
description: NotificationZWaveJSEntityDescription | None = None,
|
||||
) -> None:
|
||||
"""Initialize a ZWaveNotificationBinarySensor entity."""
|
||||
super().__init__(config_entry, client, info)
|
||||
self.state_key = state_key
|
||||
# check if we have a custom mapping for this value
|
||||
if description := self._get_sensor_description():
|
||||
if description:
|
||||
self.entity_description = description
|
||||
|
||||
# Entity class attributes
|
||||
@ -336,19 +379,6 @@ class ZWaveNotificationBinarySensor(ZWaveBaseEntity, BinarySensorEntity):
|
||||
return None
|
||||
return int(self.info.primary_value.value) == int(self.state_key)
|
||||
|
||||
@callback
|
||||
def _get_sensor_description(self) -> NotificationZWaveJSEntityDescription | None:
|
||||
"""Try to get a device specific mapping for this sensor."""
|
||||
for description in NOTIFICATION_SENSOR_MAPPINGS:
|
||||
if (
|
||||
int(description.key)
|
||||
== self.info.primary_value.metadata.cc_specific[
|
||||
CC_SPECIFIC_NOTIFICATION_TYPE
|
||||
]
|
||||
) and (not description.states or self.state_key in description.states):
|
||||
return description
|
||||
return None
|
||||
|
||||
|
||||
class ZWavePropertyBinarySensor(ZWaveBaseEntity, BinarySensorEntity):
|
||||
"""Representation of a Z-Wave binary_sensor from a property."""
|
||||
|
@ -1,6 +1,9 @@
|
||||
"""Provide common test tools for Z-Wave JS."""
|
||||
AIR_TEMPERATURE_SENSOR = "sensor.multisensor_6_air_temperature"
|
||||
BATTERY_SENSOR = "sensor.multisensor_6_battery_level"
|
||||
TAMPER_SENSOR = (
|
||||
"binary_sensor.multisensor_6_home_security_tampering_product_cover_removed"
|
||||
)
|
||||
HUMIDITY_SENSOR = "sensor.multisensor_6_humidity"
|
||||
POWER_SENSOR = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed"
|
||||
ENERGY_SENSOR = "sensor.smart_plug_with_two_usb_ports_value_electric_consumed_2"
|
||||
|
@ -1,11 +1,19 @@
|
||||
"""Test the Z-Wave JS binary sensor platform."""
|
||||
from zwave_js_server.event import Event
|
||||
from zwave_js_server.model.node import Node
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
DEVICE_CLASS_DOOR,
|
||||
DEVICE_CLASS_MOTION,
|
||||
DEVICE_CLASS_TAMPER,
|
||||
)
|
||||
from homeassistant.const import DEVICE_CLASS_BATTERY, STATE_OFF, STATE_ON
|
||||
from homeassistant.const import (
|
||||
DEVICE_CLASS_BATTERY,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .common import (
|
||||
@ -14,8 +22,11 @@ from .common import (
|
||||
LOW_BATTERY_BINARY_SENSOR,
|
||||
NOTIFICATION_MOTION_BINARY_SENSOR,
|
||||
PROPERTY_DOOR_STATUS_BINARY_SENSOR,
|
||||
TAMPER_SENSOR,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_low_battery_sensor(hass, multisensor_6, integration):
|
||||
"""Test boolean binary sensor of type low battery."""
|
||||
@ -25,6 +36,12 @@ async def test_low_battery_sensor(hass, multisensor_6, integration):
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes["device_class"] == DEVICE_CLASS_BATTERY
|
||||
|
||||
registry = er.async_get(hass)
|
||||
entity_entry = registry.async_get(LOW_BATTERY_BINARY_SENSOR)
|
||||
|
||||
assert entity_entry
|
||||
assert entity_entry.entity_category == ENTITY_CATEGORY_DIAGNOSTIC
|
||||
|
||||
|
||||
async def test_enabled_legacy_sensor(hass, ecolink_door_sensor, integration):
|
||||
"""Test enabled legacy boolean binary sensor."""
|
||||
@ -90,6 +107,50 @@ async def test_notification_sensor(hass, multisensor_6, integration):
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes["device_class"] == DEVICE_CLASS_MOTION
|
||||
|
||||
state = hass.states.get(TAMPER_SENSOR)
|
||||
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes["device_class"] == DEVICE_CLASS_TAMPER
|
||||
|
||||
registry = er.async_get(hass)
|
||||
entity_entry = registry.async_get(TAMPER_SENSOR)
|
||||
|
||||
assert entity_entry
|
||||
assert entity_entry.entity_category == ENTITY_CATEGORY_DIAGNOSTIC
|
||||
|
||||
|
||||
async def test_notification_off_state(
|
||||
hass: HomeAssistant,
|
||||
lock_popp_electric_strike_lock_control: Node,
|
||||
):
|
||||
"""Test the description off_state attribute of certain notification sensors."""
|
||||
node = lock_popp_electric_strike_lock_control
|
||||
# Remove all other values except the door state value.
|
||||
node.values = {
|
||||
value_id: value
|
||||
for value_id, value in node.values.items()
|
||||
if value_id == "62-113-0-Access Control-Door state"
|
||||
}
|
||||
|
||||
entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
door_states = [
|
||||
state
|
||||
for state in hass.states.async_all("binary_sensor")
|
||||
if state.attributes.get("device_class") == DEVICE_CLASS_DOOR
|
||||
]
|
||||
|
||||
# Only one entity should be created for the Door state notification states.
|
||||
assert len(door_states) == 1
|
||||
|
||||
state = door_states[0]
|
||||
assert state
|
||||
assert state.entity_id == "binary_sensor.node_62_access_control_window_door_is_open"
|
||||
|
||||
|
||||
async def test_property_sensor_door_status(hass, lock_august_pro, integration):
|
||||
"""Test property binary sensor with sensor mapping (doorStatus)."""
|
||||
|
Loading…
x
Reference in New Issue
Block a user