mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 10:47:10 +00:00
Fix sensor discovery for zwave_js integration (#45834)
Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com>
This commit is contained in:
parent
8d9b66e23d
commit
0feda9ce63
@ -14,7 +14,6 @@ from homeassistant.components.binary_sensor import (
|
|||||||
DEVICE_CLASS_LOCK,
|
DEVICE_CLASS_LOCK,
|
||||||
DEVICE_CLASS_MOISTURE,
|
DEVICE_CLASS_MOISTURE,
|
||||||
DEVICE_CLASS_MOTION,
|
DEVICE_CLASS_MOTION,
|
||||||
DEVICE_CLASS_POWER,
|
|
||||||
DEVICE_CLASS_PROBLEM,
|
DEVICE_CLASS_PROBLEM,
|
||||||
DEVICE_CLASS_SAFETY,
|
DEVICE_CLASS_SAFETY,
|
||||||
DEVICE_CLASS_SMOKE,
|
DEVICE_CLASS_SMOKE,
|
||||||
@ -57,201 +56,144 @@ class NotificationSensorMapping(TypedDict, total=False):
|
|||||||
"""Represent a notification sensor mapping dict type."""
|
"""Represent a notification sensor mapping dict type."""
|
||||||
|
|
||||||
type: int # required
|
type: int # required
|
||||||
states: List[int] # required
|
states: List[str]
|
||||||
device_class: str
|
device_class: str
|
||||||
enabled: bool
|
enabled: bool
|
||||||
|
|
||||||
|
|
||||||
# Mappings for Notification sensors
|
# Mappings for Notification sensors
|
||||||
|
# https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/notifications.json
|
||||||
NOTIFICATION_SENSOR_MAPPINGS: List[NotificationSensorMapping] = [
|
NOTIFICATION_SENSOR_MAPPINGS: List[NotificationSensorMapping] = [
|
||||||
{
|
{
|
||||||
# NotificationType 1: Smoke Alarm - State Id's 1 and 2
|
# NotificationType 1: Smoke Alarm - State Id's 1 and 2 - Smoke detected
|
||||||
# Assuming here that Value 1 and 2 are not present at the same time
|
|
||||||
"type": NOTIFICATION_SMOKE_ALARM,
|
"type": NOTIFICATION_SMOKE_ALARM,
|
||||||
"states": [1, 2],
|
"states": ["1", "2"],
|
||||||
"device_class": DEVICE_CLASS_SMOKE,
|
"device_class": DEVICE_CLASS_SMOKE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 1: Smoke Alarm - All other State Id's
|
# NotificationType 1: Smoke Alarm - All other State Id's
|
||||||
# Create as disabled sensors
|
|
||||||
"type": NOTIFICATION_SMOKE_ALARM,
|
"type": NOTIFICATION_SMOKE_ALARM,
|
||||||
"states": [3, 4, 5, 6, 7, 8],
|
"device_class": DEVICE_CLASS_PROBLEM,
|
||||||
"device_class": DEVICE_CLASS_SMOKE,
|
|
||||||
"enabled": False,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 2: Carbon Monoxide - State Id's 1 and 2
|
# NotificationType 2: Carbon Monoxide - State Id's 1 and 2
|
||||||
"type": NOTIFICATION_CARBON_MONOOXIDE,
|
"type": NOTIFICATION_CARBON_MONOOXIDE,
|
||||||
"states": [1, 2],
|
"states": ["1", "2"],
|
||||||
"device_class": DEVICE_CLASS_GAS,
|
"device_class": DEVICE_CLASS_GAS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 2: Carbon Monoxide - All other State Id's
|
# NotificationType 2: Carbon Monoxide - All other State Id's
|
||||||
"type": NOTIFICATION_CARBON_MONOOXIDE,
|
"type": NOTIFICATION_CARBON_MONOOXIDE,
|
||||||
"states": [4, 5, 7],
|
"device_class": DEVICE_CLASS_PROBLEM,
|
||||||
"device_class": DEVICE_CLASS_GAS,
|
|
||||||
"enabled": False,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 3: Carbon Dioxide - State Id's 1 and 2
|
# NotificationType 3: Carbon Dioxide - State Id's 1 and 2
|
||||||
"type": NOTIFICATION_CARBON_DIOXIDE,
|
"type": NOTIFICATION_CARBON_DIOXIDE,
|
||||||
"states": [1, 2],
|
"states": ["1", "2"],
|
||||||
"device_class": DEVICE_CLASS_GAS,
|
"device_class": DEVICE_CLASS_GAS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 3: Carbon Dioxide - All other State Id's
|
# NotificationType 3: Carbon Dioxide - All other State Id's
|
||||||
"type": NOTIFICATION_CARBON_DIOXIDE,
|
"type": NOTIFICATION_CARBON_DIOXIDE,
|
||||||
"states": [4, 5, 7],
|
"device_class": DEVICE_CLASS_PROBLEM,
|
||||||
"device_class": DEVICE_CLASS_GAS,
|
|
||||||
"enabled": False,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 4: Heat - State Id's 1, 2, 5, 6 (heat/underheat)
|
# NotificationType 4: Heat - State Id's 1, 2, 5, 6 (heat/underheat)
|
||||||
"type": NOTIFICATION_HEAT,
|
"type": NOTIFICATION_HEAT,
|
||||||
"states": [1, 2, 5, 6],
|
"states": ["1", "2", "5", "6"],
|
||||||
"device_class": DEVICE_CLASS_HEAT,
|
"device_class": DEVICE_CLASS_HEAT,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 4: Heat - All other State Id's
|
# NotificationType 4: Heat - All other State Id's
|
||||||
"type": NOTIFICATION_HEAT,
|
"type": NOTIFICATION_HEAT,
|
||||||
"states": [3, 4, 8, 10, 11],
|
"device_class": DEVICE_CLASS_PROBLEM,
|
||||||
"device_class": DEVICE_CLASS_HEAT,
|
|
||||||
"enabled": False,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 5: Water - State Id's 1, 2, 3, 4
|
# NotificationType 5: Water - State Id's 1, 2, 3, 4
|
||||||
"type": NOTIFICATION_WATER,
|
"type": NOTIFICATION_WATER,
|
||||||
"states": [1, 2, 3, 4],
|
"states": ["1", "2", "3", "4"],
|
||||||
"device_class": DEVICE_CLASS_MOISTURE,
|
"device_class": DEVICE_CLASS_MOISTURE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 5: Water - All other State Id's
|
# NotificationType 5: Water - All other State Id's
|
||||||
"type": NOTIFICATION_WATER,
|
"type": NOTIFICATION_WATER,
|
||||||
"states": [5],
|
"device_class": DEVICE_CLASS_PROBLEM,
|
||||||
"device_class": DEVICE_CLASS_MOISTURE,
|
|
||||||
"enabled": False,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 6: Access Control - State Id's 1, 2, 3, 4 (Lock)
|
# NotificationType 6: Access Control - State Id's 1, 2, 3, 4 (Lock)
|
||||||
"type": NOTIFICATION_ACCESS_CONTROL,
|
"type": NOTIFICATION_ACCESS_CONTROL,
|
||||||
"states": [1, 2, 3, 4],
|
"states": ["1", "2", "3", "4"],
|
||||||
"device_class": DEVICE_CLASS_LOCK,
|
"device_class": DEVICE_CLASS_LOCK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 6: Access Control - State Id 22 (door/window open)
|
# NotificationType 6: Access Control - State Id 16 (door/window open)
|
||||||
"type": NOTIFICATION_ACCESS_CONTROL,
|
"type": NOTIFICATION_ACCESS_CONTROL,
|
||||||
"states": [22],
|
"states": ["22"],
|
||||||
"device_class": DEVICE_CLASS_DOOR,
|
"device_class": DEVICE_CLASS_DOOR,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
# NotificationType 6: Access Control - State Id 17 (door/window closed)
|
||||||
|
"type": NOTIFICATION_ACCESS_CONTROL,
|
||||||
|
"states": ["23"],
|
||||||
|
"enabled": False,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 7: Home Security - State Id's 1, 2 (intrusion)
|
# NotificationType 7: Home Security - State Id's 1, 2 (intrusion)
|
||||||
# Assuming that value 1 and 2 are not present at the same time
|
|
||||||
"type": NOTIFICATION_HOME_SECURITY,
|
"type": NOTIFICATION_HOME_SECURITY,
|
||||||
"states": [1, 2],
|
"states": ["1", "2"],
|
||||||
"device_class": DEVICE_CLASS_SAFETY,
|
"device_class": DEVICE_CLASS_SAFETY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 7: Home Security - State Id's 3, 4, 9 (tampering)
|
# NotificationType 7: Home Security - State Id's 3, 4, 9 (tampering)
|
||||||
"type": NOTIFICATION_HOME_SECURITY,
|
"type": NOTIFICATION_HOME_SECURITY,
|
||||||
"states": [3, 4, 9],
|
"states": ["3", "4", "9"],
|
||||||
"device_class": DEVICE_CLASS_SAFETY,
|
"device_class": DEVICE_CLASS_SAFETY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 7: Home Security - State Id's 5, 6 (glass breakage)
|
# NotificationType 7: Home Security - State Id's 5, 6 (glass breakage)
|
||||||
# Assuming that value 5 and 6 are not present at the same time
|
|
||||||
"type": NOTIFICATION_HOME_SECURITY,
|
"type": NOTIFICATION_HOME_SECURITY,
|
||||||
"states": [5, 6],
|
"states": ["5", "6"],
|
||||||
"device_class": DEVICE_CLASS_SAFETY,
|
"device_class": DEVICE_CLASS_SAFETY,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 7: Home Security - State Id's 7, 8 (motion)
|
# NotificationType 7: Home Security - State Id's 7, 8 (motion)
|
||||||
"type": NOTIFICATION_HOME_SECURITY,
|
"type": NOTIFICATION_HOME_SECURITY,
|
||||||
"states": [7, 8],
|
"states": ["7", "8"],
|
||||||
"device_class": DEVICE_CLASS_MOTION,
|
"device_class": DEVICE_CLASS_MOTION,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
# NotificationType 8: Power management - Values 1...9
|
|
||||||
"type": NOTIFICATION_POWER_MANAGEMENT,
|
|
||||||
"states": [1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
||||||
"device_class": DEVICE_CLASS_POWER,
|
|
||||||
"enabled": False,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
# NotificationType 8: Power management - Values 10...15
|
|
||||||
# Battery values (mutually exclusive)
|
|
||||||
"type": NOTIFICATION_POWER_MANAGEMENT,
|
|
||||||
"states": [10, 11, 12, 13, 14, 15],
|
|
||||||
"device_class": DEVICE_CLASS_BATTERY,
|
|
||||||
"enabled": False,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
# NotificationType 9: System - State Id's 1, 2, 6, 7
|
# NotificationType 9: System - State Id's 1, 2, 6, 7
|
||||||
"type": NOTIFICATION_SYSTEM,
|
"type": NOTIFICATION_SYSTEM,
|
||||||
"states": [1, 2, 6, 7],
|
"states": ["1", "2", "6", "7"],
|
||||||
"device_class": DEVICE_CLASS_PROBLEM,
|
"device_class": DEVICE_CLASS_PROBLEM,
|
||||||
"enabled": False,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 10: Emergency - State Id's 1, 2, 3
|
# NotificationType 10: Emergency - State Id's 1, 2, 3
|
||||||
"type": NOTIFICATION_EMERGENCY,
|
"type": NOTIFICATION_EMERGENCY,
|
||||||
"states": [1, 2, 3],
|
"states": ["1", "2", "3"],
|
||||||
"device_class": DEVICE_CLASS_PROBLEM,
|
"device_class": DEVICE_CLASS_PROBLEM,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
# NotificationType 11: Clock - State Id's 1, 2
|
|
||||||
"type": NOTIFICATION_CLOCK,
|
|
||||||
"states": [1, 2],
|
|
||||||
"enabled": False,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
# NotificationType 12: Appliance - All State Id's
|
|
||||||
"type": NOTIFICATION_APPLIANCE,
|
|
||||||
"states": list(range(1, 22)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
# NotificationType 13: Home Health - State Id's 1,2,3,4,5
|
|
||||||
"type": NOTIFICATION_APPLIANCE,
|
|
||||||
"states": [1, 2, 3, 4, 5],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
# NotificationType 14: Siren
|
# NotificationType 14: Siren
|
||||||
"type": NOTIFICATION_SIREN,
|
"type": NOTIFICATION_SIREN,
|
||||||
"states": [1],
|
"states": ["1"],
|
||||||
"device_class": DEVICE_CLASS_SOUND,
|
"device_class": DEVICE_CLASS_SOUND,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
# NotificationType 15: Water valve
|
|
||||||
# ignore non-boolean values
|
|
||||||
"type": NOTIFICATION_WATER_VALVE,
|
|
||||||
"states": [3, 4],
|
|
||||||
"device_class": DEVICE_CLASS_PROBLEM,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
# NotificationType 16: Weather
|
|
||||||
"type": NOTIFICATION_WEATHER,
|
|
||||||
"states": [1, 2],
|
|
||||||
"device_class": DEVICE_CLASS_PROBLEM,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
# NotificationType 17: Irrigation
|
|
||||||
# ignore non-boolean values
|
|
||||||
"type": NOTIFICATION_IRRIGATION,
|
|
||||||
"states": [1, 2, 3, 4, 5],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
# NotificationType 18: Gas
|
# NotificationType 18: Gas
|
||||||
"type": NOTIFICATION_GAS,
|
"type": NOTIFICATION_GAS,
|
||||||
"states": [1, 2, 3, 4],
|
"states": ["1", "2", "3", "4"],
|
||||||
"device_class": DEVICE_CLASS_GAS,
|
"device_class": DEVICE_CLASS_GAS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# NotificationType 18: Gas
|
# NotificationType 18: Gas
|
||||||
"type": NOTIFICATION_GAS,
|
"type": NOTIFICATION_GAS,
|
||||||
"states": [6],
|
"states": ["6"],
|
||||||
"device_class": DEVICE_CLASS_PROBLEM,
|
"device_class": DEVICE_CLASS_PROBLEM,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
PROPERTY_DOOR_STATUS = "doorStatus"
|
PROPERTY_DOOR_STATUS = "doorStatus"
|
||||||
|
|
||||||
|
|
||||||
@ -284,10 +226,17 @@ async def async_setup_entry(
|
|||||||
@callback
|
@callback
|
||||||
def async_add_binary_sensor(info: ZwaveDiscoveryInfo) -> None:
|
def async_add_binary_sensor(info: ZwaveDiscoveryInfo) -> None:
|
||||||
"""Add Z-Wave Binary Sensor."""
|
"""Add Z-Wave Binary Sensor."""
|
||||||
entities: List[ZWaveBaseEntity] = []
|
entities: List[BinarySensorEntity] = []
|
||||||
|
|
||||||
if info.platform_hint == "notification":
|
if info.platform_hint == "notification":
|
||||||
entities.append(ZWaveNotificationBinarySensor(config_entry, client, info))
|
# 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
|
||||||
|
entities.append(
|
||||||
|
ZWaveNotificationBinarySensor(config_entry, client, info, state_key)
|
||||||
|
)
|
||||||
elif info.platform_hint == "property":
|
elif info.platform_hint == "property":
|
||||||
entities.append(ZWavePropertyBinarySensor(config_entry, client, info))
|
entities.append(ZWavePropertyBinarySensor(config_entry, client, info))
|
||||||
else:
|
else:
|
||||||
@ -335,58 +284,60 @@ class ZWaveNotificationBinarySensor(ZWaveBaseEntity, BinarySensorEntity):
|
|||||||
"""Representation of a Z-Wave binary_sensor from Notification CommandClass."""
|
"""Representation of a Z-Wave binary_sensor from Notification CommandClass."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo
|
self,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
client: ZwaveClient,
|
||||||
|
info: ZwaveDiscoveryInfo,
|
||||||
|
state_key: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize a ZWaveNotificationBinarySensor entity."""
|
"""Initialize a ZWaveNotificationBinarySensor entity."""
|
||||||
super().__init__(config_entry, client, info)
|
super().__init__(config_entry, client, info)
|
||||||
|
self.state_key = state_key
|
||||||
# check if we have a custom mapping for this value
|
# check if we have a custom mapping for this value
|
||||||
self._mapping_info = self._get_sensor_mapping()
|
self._mapping_info = self._get_sensor_mapping()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return if the sensor is on or off."""
|
"""Return if the sensor is on or off."""
|
||||||
if self._mapping_info:
|
return int(self.info.primary_value.value) == int(self.state_key)
|
||||||
return self.info.primary_value.value in self._mapping_info["states"]
|
|
||||||
return bool(self.info.primary_value.value != 0)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Return default name from device name and value name combination."""
|
"""Return default name from device name and value name combination."""
|
||||||
node_name = self.info.node.name or self.info.node.device_config.description
|
node_name = self.info.node.name or self.info.node.device_config.description
|
||||||
property_name = self.info.primary_value.property_name
|
value_name = self.info.primary_value.property_name
|
||||||
property_key_name = self.info.primary_value.property_key_name
|
state_label = self.info.primary_value.metadata.states[self.state_key]
|
||||||
return f"{node_name}: {property_name}: {property_key_name}"
|
return f"{node_name}: {value_name} - {state_label}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self) -> Optional[str]:
|
def device_class(self) -> Optional[str]:
|
||||||
"""Return device class."""
|
"""Return device class."""
|
||||||
return self._mapping_info.get("device_class")
|
return self._mapping_info.get("device_class")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self) -> str:
|
||||||
|
"""Return unique id for this entity."""
|
||||||
|
return f"{super().unique_id}.{self.state_key}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def entity_registry_enabled_default(self) -> bool:
|
def entity_registry_enabled_default(self) -> bool:
|
||||||
"""Return if the entity should be enabled when first added to the entity registry."""
|
"""Return if the entity should be enabled when first added to the entity registry."""
|
||||||
# We hide some more advanced sensors by default to not overwhelm users
|
|
||||||
if not self._mapping_info:
|
if not self._mapping_info:
|
||||||
# consider value for which we do not have a mapping as advanced.
|
return True
|
||||||
return False
|
|
||||||
return self._mapping_info.get("enabled", True)
|
return self._mapping_info.get("enabled", True)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _get_sensor_mapping(self) -> NotificationSensorMapping:
|
def _get_sensor_mapping(self) -> NotificationSensorMapping:
|
||||||
"""Try to get a device specific mapping for this sensor."""
|
"""Try to get a device specific mapping for this sensor."""
|
||||||
for mapping in NOTIFICATION_SENSOR_MAPPINGS:
|
for mapping in NOTIFICATION_SENSOR_MAPPINGS:
|
||||||
if mapping["type"] != int(
|
if (
|
||||||
self.info.primary_value.metadata.cc_specific["notificationType"]
|
mapping["type"]
|
||||||
|
!= self.info.primary_value.metadata.cc_specific["notificationType"]
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
for state_key in self.info.primary_value.metadata.states:
|
if not mapping.get("states") or self.state_key in mapping["states"]:
|
||||||
# make sure the key is int
|
|
||||||
state_key = int(state_key)
|
|
||||||
if state_key not in mapping["states"]:
|
|
||||||
continue
|
|
||||||
# match found
|
# match found
|
||||||
mapping_info = mapping.copy()
|
return mapping
|
||||||
return mapping_info
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
@ -143,10 +143,8 @@ DISCOVERY_SCHEMAS = [
|
|||||||
platform="sensor",
|
platform="sensor",
|
||||||
hint="string_sensor",
|
hint="string_sensor",
|
||||||
command_class={
|
command_class={
|
||||||
CommandClass.ALARM,
|
|
||||||
CommandClass.SENSOR_ALARM,
|
CommandClass.SENSOR_ALARM,
|
||||||
CommandClass.INDICATOR,
|
CommandClass.INDICATOR,
|
||||||
CommandClass.NOTIFICATION,
|
|
||||||
},
|
},
|
||||||
type={"string"},
|
type={"string"},
|
||||||
),
|
),
|
||||||
@ -157,14 +155,30 @@ DISCOVERY_SCHEMAS = [
|
|||||||
command_class={
|
command_class={
|
||||||
CommandClass.SENSOR_MULTILEVEL,
|
CommandClass.SENSOR_MULTILEVEL,
|
||||||
CommandClass.METER,
|
CommandClass.METER,
|
||||||
CommandClass.ALARM,
|
|
||||||
CommandClass.SENSOR_ALARM,
|
CommandClass.SENSOR_ALARM,
|
||||||
CommandClass.INDICATOR,
|
CommandClass.INDICATOR,
|
||||||
CommandClass.BATTERY,
|
CommandClass.BATTERY,
|
||||||
|
},
|
||||||
|
type={"number"},
|
||||||
|
),
|
||||||
|
# special list sensors (Notification CC)
|
||||||
|
ZWaveDiscoverySchema(
|
||||||
|
platform="sensor",
|
||||||
|
hint="list_sensor",
|
||||||
|
command_class={
|
||||||
CommandClass.NOTIFICATION,
|
CommandClass.NOTIFICATION,
|
||||||
|
},
|
||||||
|
type={"number"},
|
||||||
|
),
|
||||||
|
# sensor for basic CC
|
||||||
|
ZWaveDiscoverySchema(
|
||||||
|
platform="sensor",
|
||||||
|
hint="numeric_sensor",
|
||||||
|
command_class={
|
||||||
CommandClass.BASIC,
|
CommandClass.BASIC,
|
||||||
},
|
},
|
||||||
type={"number"},
|
type={"number"},
|
||||||
|
property={"currentValue"},
|
||||||
),
|
),
|
||||||
# binary switches
|
# binary switches
|
||||||
ZWaveDiscoverySchema(
|
ZWaveDiscoverySchema(
|
||||||
@ -204,54 +218,44 @@ DISCOVERY_SCHEMAS = [
|
|||||||
def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None, None]:
|
def async_discover_values(node: ZwaveNode) -> Generator[ZwaveDiscoveryInfo, None, None]:
|
||||||
"""Run discovery on ZWave node and return matching (primary) values."""
|
"""Run discovery on ZWave node and return matching (primary) values."""
|
||||||
for value in node.values.values():
|
for value in node.values.values():
|
||||||
disc_val = async_discover_value(value)
|
for schema in DISCOVERY_SCHEMAS:
|
||||||
if disc_val:
|
# check device_class_basic
|
||||||
yield disc_val
|
if (
|
||||||
|
schema.device_class_basic is not None
|
||||||
|
and value.node.device_class.basic not in schema.device_class_basic
|
||||||
@callback
|
):
|
||||||
def async_discover_value(value: ZwaveValue) -> Optional[ZwaveDiscoveryInfo]:
|
continue
|
||||||
"""Run discovery on Z-Wave value and return ZwaveDiscoveryInfo if match found."""
|
# check device_class_generic
|
||||||
for schema in DISCOVERY_SCHEMAS:
|
if (
|
||||||
# check device_class_basic
|
schema.device_class_generic is not None
|
||||||
if (
|
and value.node.device_class.generic not in schema.device_class_generic
|
||||||
schema.device_class_basic is not None
|
):
|
||||||
and value.node.device_class.basic not in schema.device_class_basic
|
continue
|
||||||
):
|
# check device_class_specific
|
||||||
continue
|
if (
|
||||||
# check device_class_generic
|
schema.device_class_specific is not None
|
||||||
if (
|
and value.node.device_class.specific not in schema.device_class_specific
|
||||||
schema.device_class_generic is not None
|
):
|
||||||
and value.node.device_class.generic not in schema.device_class_generic
|
continue
|
||||||
):
|
# check command_class
|
||||||
continue
|
if (
|
||||||
# check device_class_specific
|
schema.command_class is not None
|
||||||
if (
|
and value.command_class not in schema.command_class
|
||||||
schema.device_class_specific is not None
|
):
|
||||||
and value.node.device_class.specific not in schema.device_class_specific
|
continue
|
||||||
):
|
# check endpoint
|
||||||
continue
|
if schema.endpoint is not None and value.endpoint not in schema.endpoint:
|
||||||
# check command_class
|
continue
|
||||||
if (
|
# check property
|
||||||
schema.command_class is not None
|
if schema.property is not None and value.property_ not in schema.property:
|
||||||
and value.command_class not in schema.command_class
|
continue
|
||||||
):
|
# check metadata_type
|
||||||
continue
|
if schema.type is not None and value.metadata.type not in schema.type:
|
||||||
# check endpoint
|
continue
|
||||||
if schema.endpoint is not None and value.endpoint not in schema.endpoint:
|
# all checks passed, this value belongs to an entity
|
||||||
continue
|
yield ZwaveDiscoveryInfo(
|
||||||
# check property
|
node=value.node,
|
||||||
if schema.property is not None and value.property_ not in schema.property:
|
primary_value=value,
|
||||||
continue
|
platform=schema.platform,
|
||||||
# check metadata_type
|
platform_hint=schema.hint,
|
||||||
if schema.type is not None and value.metadata.type not in schema.type:
|
)
|
||||||
continue
|
|
||||||
# all checks passed, this value belongs to an entity
|
|
||||||
return ZwaveDiscoveryInfo(
|
|
||||||
node=value.node,
|
|
||||||
primary_value=value,
|
|
||||||
platform=schema.platform,
|
|
||||||
platform_hint=schema.hint,
|
|
||||||
)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
@ -79,6 +79,9 @@ class ZWaveBaseEntity(Entity):
|
|||||||
or self.info.primary_value.property_key_name
|
or self.info.primary_value.property_key_name
|
||||||
or self.info.primary_value.property_name
|
or self.info.primary_value.property_name
|
||||||
)
|
)
|
||||||
|
# append endpoint if > 1
|
||||||
|
if self.info.primary_value.endpoint > 1:
|
||||||
|
value_name += f" ({self.info.primary_value.endpoint})"
|
||||||
return f"{node_name}: {value_name}"
|
return f"{node_name}: {value_name}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -9,6 +9,7 @@ from zwave_js_server.const import CommandClass
|
|||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
DEVICE_CLASS_BATTERY,
|
DEVICE_CLASS_BATTERY,
|
||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
|
DEVICE_CLASS_ILLUMINANCE,
|
||||||
DEVICE_CLASS_POWER,
|
DEVICE_CLASS_POWER,
|
||||||
DOMAIN as SENSOR_DOMAIN,
|
DOMAIN as SENSOR_DOMAIN,
|
||||||
)
|
)
|
||||||
@ -39,6 +40,8 @@ async def async_setup_entry(
|
|||||||
entities.append(ZWaveStringSensor(config_entry, client, info))
|
entities.append(ZWaveStringSensor(config_entry, client, info))
|
||||||
elif info.platform_hint == "numeric_sensor":
|
elif info.platform_hint == "numeric_sensor":
|
||||||
entities.append(ZWaveNumericSensor(config_entry, client, info))
|
entities.append(ZWaveNumericSensor(config_entry, client, info))
|
||||||
|
elif info.platform_hint == "list_sensor":
|
||||||
|
entities.append(ZWaveListSensor(config_entry, client, info))
|
||||||
else:
|
else:
|
||||||
LOGGER.warning(
|
LOGGER.warning(
|
||||||
"Sensor not implemented for %s/%s",
|
"Sensor not implemented for %s/%s",
|
||||||
@ -67,11 +70,15 @@ class ZwaveSensorBase(ZWaveBaseEntity):
|
|||||||
if self.info.primary_value.command_class == CommandClass.BATTERY:
|
if self.info.primary_value.command_class == CommandClass.BATTERY:
|
||||||
return DEVICE_CLASS_BATTERY
|
return DEVICE_CLASS_BATTERY
|
||||||
if self.info.primary_value.command_class == CommandClass.METER:
|
if self.info.primary_value.command_class == CommandClass.METER:
|
||||||
if self.info.primary_value.property_key_name == "kWh_Consumed":
|
if self.info.primary_value.metadata.unit == "kWh":
|
||||||
return DEVICE_CLASS_ENERGY
|
return DEVICE_CLASS_ENERGY
|
||||||
return DEVICE_CLASS_POWER
|
return DEVICE_CLASS_POWER
|
||||||
if self.info.primary_value.property_ == "Air temperature":
|
if "temperature" in self.info.primary_value.property_.lower():
|
||||||
return DEVICE_CLASS_TEMPERATURE
|
return DEVICE_CLASS_TEMPERATURE
|
||||||
|
if self.info.primary_value.metadata.unit == "W":
|
||||||
|
return DEVICE_CLASS_POWER
|
||||||
|
if self.info.primary_value.metadata.unit == "Lux":
|
||||||
|
return DEVICE_CLASS_ILLUMINANCE
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -133,17 +140,42 @@ class ZWaveNumericSensor(ZwaveSensorBase):
|
|||||||
return str(self.info.primary_value.metadata.unit)
|
return str(self.info.primary_value.metadata.unit)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self) -> Optional[Dict[str, str]]:
|
def name(self) -> str:
|
||||||
"""Return the device specific state attributes."""
|
"""Return default name from device name and value name combination."""
|
||||||
|
if self.info.primary_value.command_class == CommandClass.BASIC:
|
||||||
|
node_name = self.info.node.name or self.info.node.device_config.description
|
||||||
|
label = self.info.primary_value.command_class_name
|
||||||
|
return f"{node_name}: {label}"
|
||||||
|
return super().name
|
||||||
|
|
||||||
|
|
||||||
|
class ZWaveListSensor(ZwaveSensorBase):
|
||||||
|
"""Representation of a Z-Wave Numeric sensor with multiple states."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self) -> Optional[str]:
|
||||||
|
"""Return state of the sensor."""
|
||||||
|
if self.info.primary_value.value is None:
|
||||||
|
return None
|
||||||
if (
|
if (
|
||||||
self.info.primary_value.value is None
|
not str(self.info.primary_value.value)
|
||||||
or not self.info.primary_value.metadata.states
|
in self.info.primary_value.metadata.states
|
||||||
):
|
):
|
||||||
return None
|
return None
|
||||||
# add the value's label as property for multi-value (list) items
|
return str(
|
||||||
label = self.info.primary_value.metadata.states.get(
|
self.info.primary_value.metadata.states[str(self.info.primary_value.value)]
|
||||||
self.info.primary_value.value
|
|
||||||
) or self.info.primary_value.metadata.states.get(
|
|
||||||
str(self.info.primary_value.value)
|
|
||||||
)
|
)
|
||||||
return {"label": label}
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self) -> Optional[Dict[str, str]]:
|
||||||
|
"""Return the device specific state attributes."""
|
||||||
|
# add the value's int value as property for multi-value (list) items
|
||||||
|
return {"value": self.info.primary_value.value}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Return default name from device name and value name combination."""
|
||||||
|
node_name = self.info.node.name or self.info.node.device_config.description
|
||||||
|
prop_name = self.info.primary_value.property_name
|
||||||
|
prop_key_name = self.info.primary_value.property_key_name
|
||||||
|
return f"{node_name}: {prop_name} - {prop_key_name}"
|
||||||
|
@ -7,8 +7,9 @@ LOW_BATTERY_BINARY_SENSOR = "binary_sensor.multisensor_6_low_battery_level"
|
|||||||
ENABLED_LEGACY_BINARY_SENSOR = "binary_sensor.z_wave_door_window_sensor_any"
|
ENABLED_LEGACY_BINARY_SENSOR = "binary_sensor.z_wave_door_window_sensor_any"
|
||||||
DISABLED_LEGACY_BINARY_SENSOR = "binary_sensor.multisensor_6_any"
|
DISABLED_LEGACY_BINARY_SENSOR = "binary_sensor.multisensor_6_any"
|
||||||
NOTIFICATION_MOTION_BINARY_SENSOR = (
|
NOTIFICATION_MOTION_BINARY_SENSOR = (
|
||||||
"binary_sensor.multisensor_6_home_security_motion_sensor_status"
|
"binary_sensor.multisensor_6_home_security_motion_detection"
|
||||||
)
|
)
|
||||||
|
NOTIFICATION_MOTION_SENSOR = "sensor.multisensor_6_home_security_motion_sensor_status"
|
||||||
PROPERTY_DOOR_STATUS_BINARY_SENSOR = (
|
PROPERTY_DOOR_STATUS_BINARY_SENSOR = (
|
||||||
"binary_sensor.august_smart_lock_pro_3rd_gen_the_current_status_of_the_door"
|
"binary_sensor.august_smart_lock_pro_3rd_gen_the_current_status_of_the_door"
|
||||||
)
|
)
|
||||||
|
@ -7,8 +7,17 @@ from homeassistant.const import (
|
|||||||
POWER_WATT,
|
POWER_WATT,
|
||||||
TEMP_CELSIUS,
|
TEMP_CELSIUS,
|
||||||
)
|
)
|
||||||
|
from homeassistant.helpers.entity_registry import (
|
||||||
|
DISABLED_INTEGRATION,
|
||||||
|
async_get_registry,
|
||||||
|
)
|
||||||
|
|
||||||
from .common import AIR_TEMPERATURE_SENSOR, ENERGY_SENSOR, POWER_SENSOR
|
from .common import (
|
||||||
|
AIR_TEMPERATURE_SENSOR,
|
||||||
|
ENERGY_SENSOR,
|
||||||
|
NOTIFICATION_MOTION_SENSOR,
|
||||||
|
POWER_SENSOR,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_numeric_sensor(hass, multisensor_6, integration):
|
async def test_numeric_sensor(hass, multisensor_6, integration):
|
||||||
@ -36,3 +45,28 @@ async def test_energy_sensors(hass, hank_binary_switch, integration):
|
|||||||
assert state.state == "0.16"
|
assert state.state == "0.16"
|
||||||
assert state.attributes["unit_of_measurement"] == ENERGY_KILO_WATT_HOUR
|
assert state.attributes["unit_of_measurement"] == ENERGY_KILO_WATT_HOUR
|
||||||
assert state.attributes["device_class"] == DEVICE_CLASS_ENERGY
|
assert state.attributes["device_class"] == DEVICE_CLASS_ENERGY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_disabled_notification_sensor(hass, multisensor_6, integration):
|
||||||
|
"""Test sensor is created from Notification CC and is disabled."""
|
||||||
|
ent_reg = await async_get_registry(hass)
|
||||||
|
entity_entry = ent_reg.async_get(NOTIFICATION_MOTION_SENSOR)
|
||||||
|
|
||||||
|
assert entity_entry
|
||||||
|
assert entity_entry.disabled
|
||||||
|
assert entity_entry.disabled_by == DISABLED_INTEGRATION
|
||||||
|
|
||||||
|
# Test enabling entity
|
||||||
|
updated_entry = ent_reg.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["value"] == 8
|
||||||
|
Loading…
x
Reference in New Issue
Block a user