1
mirror of https://github.com/home-assistant/core synced 2024-07-30 21:18:57 +02:00
ha-core/homeassistant/components/zwave_js/binary_sensor.py

379 lines
12 KiB
Python

"""Representation of Z-Wave binary sensors."""
from __future__ import annotations
import logging
from typing import TypedDict
from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.const import CommandClass
from zwave_js_server.const.command_class.lock import DOOR_STATUS_PROPERTY
from zwave_js_server.const.command_class.notification import (
CC_SPECIFIC_NOTIFICATION_TYPE,
)
from homeassistant.components.binary_sensor import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_DOOR,
DEVICE_CLASS_GAS,
DEVICE_CLASS_HEAT,
DEVICE_CLASS_LOCK,
DEVICE_CLASS_MOISTURE,
DEVICE_CLASS_MOTION,
DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND,
DOMAIN as BINARY_SENSOR_DOMAIN,
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_CLIENT, DOMAIN
from .discovery import ZwaveDiscoveryInfo
from .entity import ZWaveBaseEntity
LOGGER = logging.getLogger(__name__)
NOTIFICATION_SMOKE_ALARM = 1
NOTIFICATION_CARBON_MONOOXIDE = 2
NOTIFICATION_CARBON_DIOXIDE = 3
NOTIFICATION_HEAT = 4
NOTIFICATION_WATER = 5
NOTIFICATION_ACCESS_CONTROL = 6
NOTIFICATION_HOME_SECURITY = 7
NOTIFICATION_POWER_MANAGEMENT = 8
NOTIFICATION_SYSTEM = 9
NOTIFICATION_EMERGENCY = 10
NOTIFICATION_CLOCK = 11
NOTIFICATION_APPLIANCE = 12
NOTIFICATION_HOME_HEALTH = 13
NOTIFICATION_SIREN = 14
NOTIFICATION_WATER_VALVE = 15
NOTIFICATION_WEATHER = 16
NOTIFICATION_IRRIGATION = 17
NOTIFICATION_GAS = 18
class NotificationSensorMapping(TypedDict, total=False):
"""Represent a notification sensor mapping dict type."""
type: int # required
states: list[str]
device_class: str
enabled: bool
# Mappings for Notification sensors
# https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/notifications.json
NOTIFICATION_SENSOR_MAPPINGS: list[NotificationSensorMapping] = [
{
# NotificationType 1: Smoke Alarm - State Id's 1 and 2 - Smoke detected
"type": NOTIFICATION_SMOKE_ALARM,
"states": ["1", "2"],
"device_class": DEVICE_CLASS_SMOKE,
},
{
# NotificationType 1: Smoke Alarm - All other State Id's
"type": NOTIFICATION_SMOKE_ALARM,
"device_class": DEVICE_CLASS_PROBLEM,
},
{
# NotificationType 2: Carbon Monoxide - State Id's 1 and 2
"type": NOTIFICATION_CARBON_MONOOXIDE,
"states": ["1", "2"],
"device_class": DEVICE_CLASS_GAS,
},
{
# NotificationType 2: Carbon Monoxide - All other State Id's
"type": NOTIFICATION_CARBON_MONOOXIDE,
"device_class": DEVICE_CLASS_PROBLEM,
},
{
# NotificationType 3: Carbon Dioxide - State Id's 1 and 2
"type": NOTIFICATION_CARBON_DIOXIDE,
"states": ["1", "2"],
"device_class": DEVICE_CLASS_GAS,
},
{
# NotificationType 3: Carbon Dioxide - All other State Id's
"type": NOTIFICATION_CARBON_DIOXIDE,
"device_class": DEVICE_CLASS_PROBLEM,
},
{
# NotificationType 4: Heat - State Id's 1, 2, 5, 6 (heat/underheat)
"type": NOTIFICATION_HEAT,
"states": ["1", "2", "5", "6"],
"device_class": DEVICE_CLASS_HEAT,
},
{
# NotificationType 4: Heat - All other State Id's
"type": NOTIFICATION_HEAT,
"device_class": DEVICE_CLASS_PROBLEM,
},
{
# NotificationType 5: Water - State Id's 1, 2, 3, 4
"type": NOTIFICATION_WATER,
"states": ["1", "2", "3", "4"],
"device_class": DEVICE_CLASS_MOISTURE,
},
{
# NotificationType 5: Water - All other State Id's
"type": NOTIFICATION_WATER,
"device_class": DEVICE_CLASS_PROBLEM,
},
{
# NotificationType 6: Access Control - State Id's 1, 2, 3, 4 (Lock)
"type": NOTIFICATION_ACCESS_CONTROL,
"states": ["1", "2", "3", "4"],
"device_class": DEVICE_CLASS_LOCK,
},
{
# NotificationType 6: Access Control - State Id 16 (door/window open)
"type": NOTIFICATION_ACCESS_CONTROL,
"states": ["22"],
"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)
"type": NOTIFICATION_HOME_SECURITY,
"states": ["1", "2"],
"device_class": DEVICE_CLASS_SAFETY,
},
{
# NotificationType 7: Home Security - State Id's 3, 4, 9 (tampering)
"type": NOTIFICATION_HOME_SECURITY,
"states": ["3", "4", "9"],
"device_class": DEVICE_CLASS_SAFETY,
},
{
# NotificationType 7: Home Security - State Id's 5, 6 (glass breakage)
"type": NOTIFICATION_HOME_SECURITY,
"states": ["5", "6"],
"device_class": DEVICE_CLASS_SAFETY,
},
{
# NotificationType 7: Home Security - State Id's 7, 8 (motion)
"type": NOTIFICATION_HOME_SECURITY,
"states": ["7", "8"],
"device_class": DEVICE_CLASS_MOTION,
},
{
# NotificationType 9: System - State Id's 1, 2, 6, 7
"type": NOTIFICATION_SYSTEM,
"states": ["1", "2", "6", "7"],
"device_class": DEVICE_CLASS_PROBLEM,
},
{
# NotificationType 10: Emergency - State Id's 1, 2, 3
"type": NOTIFICATION_EMERGENCY,
"states": ["1", "2", "3"],
"device_class": DEVICE_CLASS_PROBLEM,
},
{
# NotificationType 14: Siren
"type": NOTIFICATION_SIREN,
"states": ["1"],
"device_class": DEVICE_CLASS_SOUND,
},
{
# NotificationType 18: Gas
"type": NOTIFICATION_GAS,
"states": ["1", "2", "3", "4"],
"device_class": DEVICE_CLASS_GAS,
},
{
# NotificationType 18: Gas
"type": NOTIFICATION_GAS,
"states": ["6"],
"device_class": DEVICE_CLASS_PROBLEM,
},
]
class PropertySensorMapping(TypedDict, total=False):
"""Represent a property sensor mapping dict type."""
property_name: str # required
on_states: list[str] # required
device_class: str
enabled: bool
# Mappings for property sensors
PROPERTY_SENSOR_MAPPINGS: list[PropertySensorMapping] = [
{
"property_name": DOOR_STATUS_PROPERTY,
"on_states": ["open"],
"device_class": DEVICE_CLASS_DOOR,
"enabled": True,
},
]
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Z-Wave binary sensor from config entry."""
client: ZwaveClient = hass.data[DOMAIN][config_entry.entry_id][DATA_CLIENT]
@callback
def async_add_binary_sensor(info: ZwaveDiscoveryInfo) -> None:
"""Add Z-Wave Binary Sensor."""
entities: list[BinarySensorEntity] = []
if info.platform_hint == "notification":
# 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":
entities.append(ZWavePropertyBinarySensor(config_entry, client, info))
else:
# boolean sensor
entities.append(ZWaveBooleanBinarySensor(config_entry, client, info))
async_add_entities(entities)
config_entry.async_on_unload(
async_dispatcher_connect(
hass,
f"{DOMAIN}_{config_entry.entry_id}_add_{BINARY_SENSOR_DOMAIN}",
async_add_binary_sensor,
)
)
class ZWaveBooleanBinarySensor(ZWaveBaseEntity, BinarySensorEntity):
"""Representation of a Z-Wave binary_sensor."""
def __init__(
self,
config_entry: ConfigEntry,
client: ZwaveClient,
info: ZwaveDiscoveryInfo,
) -> None:
"""Initialize a ZWaveBooleanBinarySensor entity."""
super().__init__(config_entry, client, info)
# Entity class attributes
self._attr_name = self.generate_name(include_value_name=True)
self._attr_device_class = (
DEVICE_CLASS_BATTERY
if self.info.primary_value.command_class == CommandClass.BATTERY
else None
)
@property
def is_on(self) -> bool | None:
"""Return if the sensor is on or off."""
if self.info.primary_value.value is None:
return None
return bool(self.info.primary_value.value)
class ZWaveNotificationBinarySensor(ZWaveBaseEntity, BinarySensorEntity):
"""Representation of a Z-Wave binary_sensor from Notification CommandClass."""
def __init__(
self,
config_entry: ConfigEntry,
client: ZwaveClient,
info: ZwaveDiscoveryInfo,
state_key: str,
) -> 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
self._mapping_info = self._get_sensor_mapping()
# Entity class attributes
self._attr_name = self.generate_name(
include_value_name=True,
alternate_value_name=self.info.primary_value.property_name,
additional_info=[self.info.primary_value.metadata.states[self.state_key]],
)
self._attr_device_class = self._mapping_info.get("device_class")
self._attr_unique_id = f"{self._attr_unique_id}.{self.state_key}"
self._attr_entity_registry_enabled_default = (
True if not self._mapping_info else self._mapping_info.get("enabled", True)
)
@property
def is_on(self) -> bool | None:
"""Return if the sensor is on or off."""
if self.info.primary_value.value is None:
return None
return int(self.info.primary_value.value) == int(self.state_key)
@callback
def _get_sensor_mapping(self) -> NotificationSensorMapping:
"""Try to get a device specific mapping for this sensor."""
for mapping in NOTIFICATION_SENSOR_MAPPINGS:
if (
mapping["type"]
!= self.info.primary_value.metadata.cc_specific[
CC_SPECIFIC_NOTIFICATION_TYPE
]
):
continue
if not mapping.get("states") or self.state_key in mapping["states"]:
# match found
return mapping
return {}
class ZWavePropertyBinarySensor(ZWaveBaseEntity, BinarySensorEntity):
"""Representation of a Z-Wave binary_sensor from a property."""
def __init__(
self, config_entry: ConfigEntry, client: ZwaveClient, info: ZwaveDiscoveryInfo
) -> None:
"""Initialize a ZWavePropertyBinarySensor entity."""
super().__init__(config_entry, client, info)
# check if we have a custom mapping for this value
self._mapping_info = self._get_sensor_mapping()
# Entity class attributes
self._attr_name = self.generate_name(include_value_name=True)
self._attr_device_class = self._mapping_info.get("device_class")
# We hide some more advanced sensors by default to not overwhelm users
# unless explicitly stated in a mapping, assume deisabled by default
self._attr_entity_registry_enabled_default = self._mapping_info.get(
"enabled", False
)
@property
def is_on(self) -> bool | None:
"""Return if the sensor is on or off."""
if self.info.primary_value.value is None:
return None
return self.info.primary_value.value in self._mapping_info["on_states"]
@callback
def _get_sensor_mapping(self) -> PropertySensorMapping:
"""Try to get a device specific mapping for this sensor."""
mapping_info = PropertySensorMapping()
for mapping in PROPERTY_SENSOR_MAPPINGS:
if mapping["property_name"] == self.info.primary_value.property_name:
mapping_info = mapping.copy()
break
return mapping_info