core/homeassistant/components/homematicip_cloud/device.py

228 lines
7.8 KiB
Python

"""Generic device for the HomematicIP Cloud component."""
import logging
from typing import Any, Dict, Optional
from homematicip.aio.device import AsyncDevice
from homematicip.aio.group import AsyncGroup
from homeassistant.core import callback
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.entity import Entity
from .const import DOMAIN as HMIPC_DOMAIN
from .hap import HomematicipHAP
_LOGGER = logging.getLogger(__name__)
ATTR_MODEL_TYPE = "model_type"
ATTR_LOW_BATTERY = "low_battery"
ATTR_CONFIG_PENDING = "config_pending"
ATTR_DUTY_CYCLE_REACHED = "duty_cycle_reached"
ATTR_ID = "id"
ATTR_IS_GROUP = "is_group"
# RSSI HAP -> Device
ATTR_RSSI_DEVICE = "rssi_device"
# RSSI Device -> HAP
ATTR_RSSI_PEER = "rssi_peer"
ATTR_SABOTAGE = "sabotage"
ATTR_GROUP_MEMBER_UNREACHABLE = "group_member_unreachable"
ATTR_DEVICE_OVERHEATED = "device_overheated"
ATTR_DEVICE_OVERLOADED = "device_overloaded"
ATTR_DEVICE_UNTERVOLTAGE = "device_undervoltage"
ATTR_EVENT_DELAY = "event_delay"
DEVICE_ATTRIBUTE_ICONS = {
"lowBat": "mdi:battery-outline",
"sabotage": "mdi:shield-alert",
"dutyCycle": "mdi:alert",
"deviceOverheated": "mdi:alert",
"deviceOverloaded": "mdi:alert",
"deviceUndervoltage": "mdi:alert",
"configPending": "mdi:alert-circle",
}
DEVICE_ATTRIBUTES = {
"modelType": ATTR_MODEL_TYPE,
"sabotage": ATTR_SABOTAGE,
"dutyCycle": ATTR_DUTY_CYCLE_REACHED,
"rssiDeviceValue": ATTR_RSSI_DEVICE,
"rssiPeerValue": ATTR_RSSI_PEER,
"deviceOverheated": ATTR_DEVICE_OVERHEATED,
"deviceOverloaded": ATTR_DEVICE_OVERLOADED,
"deviceUndervoltage": ATTR_DEVICE_UNTERVOLTAGE,
"configPending": ATTR_CONFIG_PENDING,
"eventDelay": ATTR_EVENT_DELAY,
"id": ATTR_ID,
}
GROUP_ATTRIBUTES = {
"modelType": ATTR_MODEL_TYPE,
"lowBat": ATTR_LOW_BATTERY,
"sabotage": ATTR_SABOTAGE,
"dutyCycle": ATTR_DUTY_CYCLE_REACHED,
"configPending": ATTR_CONFIG_PENDING,
"unreach": ATTR_GROUP_MEMBER_UNREACHABLE,
}
class HomematicipGenericDevice(Entity):
"""Representation of an HomematicIP generic device."""
def __init__(self, hap: HomematicipHAP, device, post: Optional[str] = None) -> None:
"""Initialize the generic device."""
self._hap = hap
self._home = hap.home
self._device = device
self.post = post
# Marker showing that the HmIP device hase been removed.
self.hmip_device_removed = False
_LOGGER.info("Setting up %s (%s)", self.name, self._device.modelType)
@property
def device_info(self) -> Dict[str, Any]:
"""Return device specific attributes."""
# Only physical devices should be HA devices.
if isinstance(self._device, AsyncDevice):
return {
"identifiers": {
# Serial numbers of Homematic IP device
(HMIPC_DOMAIN, self._device.id)
},
"name": self._device.label,
"manufacturer": self._device.oem,
"model": self._device.modelType,
"sw_version": self._device.firmwareVersion,
"via_device": (HMIPC_DOMAIN, self._device.homeId),
}
return None
async def async_added_to_hass(self) -> None:
"""Register callbacks."""
self._hap.hmip_device_by_entity_id[self.entity_id] = self._device
self._device.on_update(self._async_device_changed)
self._device.on_remove(self._async_device_removed)
@callback
def _async_device_changed(self, *args, **kwargs) -> None:
"""Handle device state changes."""
# Don't update disabled entities
if self.enabled:
_LOGGER.debug("Event %s (%s)", self.name, self._device.modelType)
self.async_write_ha_state()
else:
_LOGGER.debug(
"Device Changed Event for %s (%s) not fired. Entity is disabled.",
self.name,
self._device.modelType,
)
async def async_will_remove_from_hass(self) -> None:
"""Run when entity will be removed from hass."""
# Only go further if the device/entity should be removed from registries
# due to a removal of the HmIP device.
if self.hmip_device_removed:
try:
del self._hap.hmip_device_by_entity_id[self.entity_id]
await self.async_remove_from_registries()
except KeyError as err:
_LOGGER.debug("Error removing HMIP entity from registry: %s", err)
async def async_remove_from_registries(self) -> None:
"""Remove entity/device from registry."""
# Remove callback from device.
self._device.remove_callback(self._async_device_changed)
self._device.remove_callback(self._async_device_removed)
if not self.registry_entry:
return
device_id = self.registry_entry.device_id
if device_id:
# Remove from device registry.
device_registry = await dr.async_get_registry(self.hass)
if device_id in device_registry.devices:
# This will also remove associated entities from entity registry.
device_registry.async_remove_device(device_id)
else:
# Remove from entity registry.
# Only relevant for entities that do not belong to a device.
entity_id = self.registry_entry.entity_id
if entity_id:
entity_registry = await er.async_get_registry(self.hass)
if entity_id in entity_registry.entities:
entity_registry.async_remove(entity_id)
@callback
def _async_device_removed(self, *args, **kwargs) -> None:
"""Handle hmip device removal."""
# Set marker showing that the HmIP device hase been removed.
self.hmip_device_removed = True
self.hass.async_create_task(self.async_remove())
@property
def name(self) -> str:
"""Return the name of the generic device."""
name = self._device.label
if name and self._home.name:
name = f"{self._home.name} {name}"
if name and self.post:
name = f"{name} {self.post}"
return name
def _get_label_by_channel(self, channel: int) -> str:
"""Return the name of the channel."""
name = self._device.functionalChannels[channel].label
if name and self._home.name:
name = f"{self._home.name} {name}"
return name
@property
def should_poll(self) -> bool:
"""No polling needed."""
return False
@property
def available(self) -> bool:
"""Device available."""
return not self._device.unreach
@property
def unique_id(self) -> str:
"""Return a unique ID."""
return f"{self.__class__.__name__}_{self._device.id}"
@property
def icon(self) -> Optional[str]:
"""Return the icon."""
for attr, icon in DEVICE_ATTRIBUTE_ICONS.items():
if getattr(self._device, attr, None):
return icon
return None
@property
def device_state_attributes(self) -> Dict[str, Any]:
"""Return the state attributes of the generic device."""
state_attr = {}
if isinstance(self._device, AsyncDevice):
for attr, attr_key in DEVICE_ATTRIBUTES.items():
attr_value = getattr(self._device, attr, None)
if attr_value:
state_attr[attr_key] = attr_value
state_attr[ATTR_IS_GROUP] = False
if isinstance(self._device, AsyncGroup):
for attr, attr_key in GROUP_ATTRIBUTES.items():
attr_value = getattr(self._device, attr, None)
if attr_value:
state_attr[attr_key] = attr_value
state_attr[ATTR_IS_GROUP] = True
return state_attr