2020-08-25 23:55:55 +00:00
|
|
|
"""Generic entity for the HomematicIP Cloud component."""
|
2018-07-06 21:05:34 +00:00
|
|
|
import logging
|
2019-11-25 13:17:14 +00:00
|
|
|
from typing import Any, Dict, Optional
|
2018-07-06 21:05:34 +00:00
|
|
|
|
2019-04-23 23:47:31 +00:00
|
|
|
from homematicip.aio.device import AsyncDevice
|
2019-10-08 17:52:43 +00:00
|
|
|
from homematicip.aio.group import AsyncGroup
|
2019-04-23 23:47:31 +00:00
|
|
|
|
2019-09-28 20:34:14 +00:00
|
|
|
from homeassistant.core import callback
|
|
|
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
2018-07-06 21:05:34 +00:00
|
|
|
from homeassistant.helpers.entity import Entity
|
|
|
|
|
2019-10-23 16:21:49 +00:00
|
|
|
from .const import DOMAIN as HMIPC_DOMAIN
|
2019-10-11 14:36:46 +00:00
|
|
|
from .hap import HomematicipHAP
|
|
|
|
|
2018-07-06 21:05:34 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_MODEL_TYPE = "model_type"
|
2019-10-28 00:03:26 +00:00
|
|
|
ATTR_LOW_BATTERY = "low_battery"
|
|
|
|
ATTR_CONFIG_PENDING = "config_pending"
|
2020-09-03 15:23:42 +00:00
|
|
|
ATTR_CONNECTION_TYPE = "connection_type"
|
2019-10-28 00:03:26 +00:00
|
|
|
ATTR_DUTY_CYCLE_REACHED = "duty_cycle_reached"
|
2019-08-20 17:59:01 +00:00
|
|
|
ATTR_ID = "id"
|
2019-09-14 05:12:19 +00:00
|
|
|
ATTR_IS_GROUP = "is_group"
|
2019-03-23 12:21:55 +00:00
|
|
|
# RSSI HAP -> Device
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_RSSI_DEVICE = "rssi_device"
|
2019-03-23 12:21:55 +00:00
|
|
|
# RSSI Device -> HAP
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_RSSI_PEER = "rssi_peer"
|
|
|
|
ATTR_SABOTAGE = "sabotage"
|
|
|
|
ATTR_GROUP_MEMBER_UNREACHABLE = "group_member_unreachable"
|
2019-08-21 14:31:55 +00:00
|
|
|
ATTR_DEVICE_OVERHEATED = "device_overheated"
|
|
|
|
ATTR_DEVICE_OVERLOADED = "device_overloaded"
|
|
|
|
ATTR_DEVICE_UNTERVOLTAGE = "device_undervoltage"
|
2019-10-28 00:03:26 +00:00
|
|
|
ATTR_EVENT_DELAY = "event_delay"
|
2019-08-21 14:31:55 +00:00
|
|
|
|
|
|
|
DEVICE_ATTRIBUTE_ICONS = {
|
|
|
|
"lowBat": "mdi:battery-outline",
|
2019-10-28 00:03:26 +00:00
|
|
|
"sabotage": "mdi:shield-alert",
|
|
|
|
"dutyCycle": "mdi:alert",
|
2019-08-21 14:31:55 +00:00
|
|
|
"deviceOverheated": "mdi:alert",
|
|
|
|
"deviceOverloaded": "mdi:alert",
|
|
|
|
"deviceUndervoltage": "mdi:alert",
|
2019-10-28 00:03:26 +00:00
|
|
|
"configPending": "mdi:alert-circle",
|
2019-08-21 14:31:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEVICE_ATTRIBUTES = {
|
|
|
|
"modelType": ATTR_MODEL_TYPE,
|
2020-09-03 15:23:42 +00:00
|
|
|
"connectionType": ATTR_CONNECTION_TYPE,
|
2019-08-21 14:31:55 +00:00
|
|
|
"sabotage": ATTR_SABOTAGE,
|
2019-10-28 00:03:26 +00:00
|
|
|
"dutyCycle": ATTR_DUTY_CYCLE_REACHED,
|
2019-08-21 14:31:55 +00:00
|
|
|
"rssiDeviceValue": ATTR_RSSI_DEVICE,
|
|
|
|
"rssiPeerValue": ATTR_RSSI_PEER,
|
|
|
|
"deviceOverheated": ATTR_DEVICE_OVERHEATED,
|
|
|
|
"deviceOverloaded": ATTR_DEVICE_OVERLOADED,
|
|
|
|
"deviceUndervoltage": ATTR_DEVICE_UNTERVOLTAGE,
|
2019-10-28 00:03:26 +00:00
|
|
|
"configPending": ATTR_CONFIG_PENDING,
|
|
|
|
"eventDelay": ATTR_EVENT_DELAY,
|
2019-10-08 17:52:43 +00:00
|
|
|
"id": ATTR_ID,
|
2019-08-21 14:31:55 +00:00
|
|
|
}
|
2018-07-06 21:05:34 +00:00
|
|
|
|
2019-10-28 00:03:26 +00:00
|
|
|
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,
|
|
|
|
}
|
2019-10-08 17:52:43 +00:00
|
|
|
|
2018-07-06 21:05:34 +00:00
|
|
|
|
2020-08-25 23:55:55 +00:00
|
|
|
class HomematicipGenericEntity(Entity):
|
|
|
|
"""Representation of the HomematicIP generic entity."""
|
2018-07-06 21:05:34 +00:00
|
|
|
|
2020-10-03 14:35:04 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
hap: HomematicipHAP,
|
|
|
|
device,
|
|
|
|
post: Optional[str] = None,
|
|
|
|
channel: Optional[int] = None,
|
2020-12-07 23:16:22 +00:00
|
|
|
is_multi_channel: Optional[bool] = False,
|
2020-10-03 14:35:04 +00:00
|
|
|
) -> None:
|
2020-08-25 23:55:55 +00:00
|
|
|
"""Initialize the generic entity."""
|
2019-10-11 14:36:46 +00:00
|
|
|
self._hap = hap
|
|
|
|
self._home = hap.home
|
2018-07-06 21:05:34 +00:00
|
|
|
self._device = device
|
2020-10-03 14:35:04 +00:00
|
|
|
self._post = post
|
|
|
|
self._channel = channel
|
2020-12-07 23:16:22 +00:00
|
|
|
self._is_multi_channel = is_multi_channel
|
2019-09-28 20:34:14 +00:00
|
|
|
# Marker showing that the HmIP device hase been removed.
|
|
|
|
self.hmip_device_removed = False
|
2018-08-21 19:25:16 +00:00
|
|
|
_LOGGER.info("Setting up %s (%s)", self.name, self._device.modelType)
|
2018-07-06 21:05:34 +00:00
|
|
|
|
2019-02-27 19:25:11 +00:00
|
|
|
@property
|
2019-11-25 13:17:14 +00:00
|
|
|
def device_info(self) -> Dict[str, Any]:
|
2019-02-27 19:25:11 +00:00
|
|
|
"""Return device specific attributes."""
|
|
|
|
# Only physical devices should be HA devices.
|
|
|
|
if isinstance(self._device, AsyncDevice):
|
|
|
|
return {
|
2019-07-31 19:25:30 +00:00
|
|
|
"identifiers": {
|
2019-02-27 19:25:11 +00:00
|
|
|
# Serial numbers of Homematic IP device
|
2019-10-23 16:21:49 +00:00
|
|
|
(HMIPC_DOMAIN, self._device.id)
|
2019-02-27 19:25:11 +00:00
|
|
|
},
|
2019-07-31 19:25:30 +00:00
|
|
|
"name": self._device.label,
|
|
|
|
"manufacturer": self._device.oem,
|
|
|
|
"model": self._device.modelType,
|
|
|
|
"sw_version": self._device.firmwareVersion,
|
2020-10-03 14:35:04 +00:00
|
|
|
# Link to the homematic ip access point.
|
2019-10-23 16:21:49 +00:00
|
|
|
"via_device": (HMIPC_DOMAIN, self._device.homeId),
|
2019-02-27 19:25:11 +00:00
|
|
|
}
|
|
|
|
return None
|
|
|
|
|
2019-11-25 13:17:14 +00:00
|
|
|
async def async_added_to_hass(self) -> None:
|
2018-07-06 21:05:34 +00:00
|
|
|
"""Register callbacks."""
|
2019-10-11 14:36:46 +00:00
|
|
|
self._hap.hmip_device_by_entity_id[self.entity_id] = self._device
|
2019-05-01 19:35:57 +00:00
|
|
|
self._device.on_update(self._async_device_changed)
|
2019-09-28 20:34:14 +00:00
|
|
|
self._device.on_remove(self._async_device_removed)
|
2018-07-06 21:05:34 +00:00
|
|
|
|
2019-09-28 20:34:14 +00:00
|
|
|
@callback
|
2019-11-25 13:17:14 +00:00
|
|
|
def _async_device_changed(self, *args, **kwargs) -> None:
|
2018-07-06 21:05:34 +00:00
|
|
|
"""Handle device state changes."""
|
2019-08-28 20:38:20 +00:00
|
|
|
# Don't update disabled entities
|
|
|
|
if self.enabled:
|
|
|
|
_LOGGER.debug("Event %s (%s)", self.name, self._device.modelType)
|
2020-04-01 21:19:51 +00:00
|
|
|
self.async_write_ha_state()
|
2019-08-28 20:38:20 +00:00
|
|
|
else:
|
|
|
|
_LOGGER.debug(
|
2020-07-05 21:04:19 +00:00
|
|
|
"Device Changed Event for %s (%s) not fired. Entity is disabled",
|
2019-08-28 20:38:20 +00:00
|
|
|
self.name,
|
|
|
|
self._device.modelType,
|
|
|
|
)
|
2018-07-06 21:05:34 +00:00
|
|
|
|
2019-09-28 20:34:14 +00:00
|
|
|
async def async_will_remove_from_hass(self) -> None:
|
2020-08-25 23:55:55 +00:00
|
|
|
"""Run when hmip device will be removed from hass."""
|
2019-09-28 20:34:14 +00:00
|
|
|
|
|
|
|
# Only go further if the device/entity should be removed from registries
|
|
|
|
# due to a removal of the HmIP device.
|
2020-03-29 04:01:53 +00:00
|
|
|
|
2019-09-28 20:34:14 +00:00
|
|
|
if self.hmip_device_removed:
|
2020-03-29 04:01:53 +00:00
|
|
|
try:
|
|
|
|
del self._hap.hmip_device_by_entity_id[self.entity_id]
|
|
|
|
await self.async_remove_from_registries()
|
|
|
|
except KeyError as err:
|
2020-08-25 23:55:55 +00:00
|
|
|
_LOGGER.debug("Error removing HMIP device from registry: %s", err)
|
2019-09-28 20:34:14 +00:00
|
|
|
|
|
|
|
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
|
2019-11-25 13:17:14 +00:00
|
|
|
def _async_device_removed(self, *args, **kwargs) -> None:
|
2019-09-28 20:34:14 +00:00
|
|
|
"""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())
|
|
|
|
|
2018-07-06 21:05:34 +00:00
|
|
|
@property
|
2019-04-25 22:13:07 +00:00
|
|
|
def name(self) -> str:
|
2020-08-25 23:55:55 +00:00
|
|
|
"""Return the name of the generic entity."""
|
2018-07-06 21:05:34 +00:00
|
|
|
|
2020-10-03 14:35:04 +00:00
|
|
|
name = None
|
|
|
|
# Try to get a label from a channel.
|
|
|
|
if hasattr(self._device, "functionalChannels"):
|
2020-12-07 23:16:22 +00:00
|
|
|
if self._is_multi_channel:
|
2020-10-03 14:35:04 +00:00
|
|
|
name = self._device.functionalChannels[self._channel].label
|
|
|
|
else:
|
|
|
|
if len(self._device.functionalChannels) > 1:
|
|
|
|
name = self._device.functionalChannels[1].label
|
|
|
|
|
|
|
|
# Use device label, if name is not defined by channel label.
|
|
|
|
if not name:
|
|
|
|
name = self._device.label
|
|
|
|
if self._post:
|
|
|
|
name = f"{name} {self._post}"
|
2020-12-07 23:16:22 +00:00
|
|
|
elif self._is_multi_channel:
|
2020-10-03 14:35:04 +00:00
|
|
|
name = f"{name} Channel{self._channel}"
|
|
|
|
|
|
|
|
# Add a prefix to the name if the homematic ip home has a name.
|
2020-03-29 19:38:59 +00:00
|
|
|
if name and self._home.name:
|
|
|
|
name = f"{self._home.name} {name}"
|
2020-10-03 14:35:04 +00:00
|
|
|
|
2020-03-29 19:38:59 +00:00
|
|
|
return name
|
|
|
|
|
2018-07-06 21:05:34 +00:00
|
|
|
@property
|
2019-04-25 22:13:07 +00:00
|
|
|
def should_poll(self) -> bool:
|
2018-07-06 21:05:34 +00:00
|
|
|
"""No polling needed."""
|
|
|
|
return False
|
|
|
|
|
|
|
|
@property
|
2019-04-25 22:13:07 +00:00
|
|
|
def available(self) -> bool:
|
2020-08-25 23:55:55 +00:00
|
|
|
"""Return if entity is available."""
|
2018-07-06 21:05:34 +00:00
|
|
|
return not self._device.unreach
|
|
|
|
|
2018-09-25 08:15:03 +00:00
|
|
|
@property
|
2019-04-25 22:13:07 +00:00
|
|
|
def unique_id(self) -> str:
|
2018-09-25 08:15:03 +00:00
|
|
|
"""Return a unique ID."""
|
2020-10-03 14:35:04 +00:00
|
|
|
unique_id = f"{self.__class__.__name__}_{self._device.id}"
|
2020-12-07 23:16:22 +00:00
|
|
|
if self._is_multi_channel:
|
2020-10-03 14:35:04 +00:00
|
|
|
unique_id = (
|
|
|
|
f"{self.__class__.__name__}_Channel{self._channel}_{self._device.id}"
|
|
|
|
)
|
|
|
|
|
|
|
|
return unique_id
|
2018-09-25 08:15:03 +00:00
|
|
|
|
2018-07-18 10:19:08 +00:00
|
|
|
@property
|
2019-04-25 22:13:07 +00:00
|
|
|
def icon(self) -> Optional[str]:
|
2018-07-18 10:19:08 +00:00
|
|
|
"""Return the icon."""
|
2019-08-21 14:31:55 +00:00
|
|
|
for attr, icon in DEVICE_ATTRIBUTE_ICONS.items():
|
|
|
|
if getattr(self._device, attr, None):
|
|
|
|
return icon
|
|
|
|
|
2018-07-18 10:19:08 +00:00
|
|
|
return None
|
|
|
|
|
2018-07-06 21:05:34 +00:00
|
|
|
@property
|
2019-11-25 13:17:14 +00:00
|
|
|
def device_state_attributes(self) -> Dict[str, Any]:
|
2020-08-25 23:55:55 +00:00
|
|
|
"""Return the state attributes of the generic entity."""
|
2019-08-21 14:31:55 +00:00
|
|
|
state_attr = {}
|
2019-10-08 17:52:43 +00:00
|
|
|
|
2019-08-22 16:02:35 +00:00
|
|
|
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
|
2019-08-21 14:31:55 +00:00
|
|
|
|
2019-09-14 05:12:19 +00:00
|
|
|
state_attr[ATTR_IS_GROUP] = False
|
|
|
|
|
2019-10-08 17:52:43 +00:00
|
|
|
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
|
|
|
|
|
2019-08-21 14:31:55 +00:00
|
|
|
return state_attr
|