Remove ZHA device entity (#24909)
* move availability handling to device * update last_seen format * add battery sensor * fix interval * fix battery reporting now that it is a sensor * remove zha entities and add battery sensorpull/24928/head
parent
eec67d8b1a
commit
a9459c6d92
|
@ -15,8 +15,8 @@ from .core.channels.registry import populate_channel_registry
|
|||
from .core.const import (
|
||||
COMPONENTS, CONF_BAUDRATE, CONF_DATABASE, CONF_DEVICE_CONFIG,
|
||||
CONF_RADIO_TYPE, CONF_USB_PATH, DATA_ZHA, DATA_ZHA_CONFIG,
|
||||
DATA_ZHA_CORE_COMPONENT, DATA_ZHA_DISPATCHERS, DATA_ZHA_GATEWAY,
|
||||
DEFAULT_BAUDRATE, DEFAULT_RADIO_TYPE, DOMAIN, ENABLE_QUIRKS, RadioType)
|
||||
DATA_ZHA_DISPATCHERS, DATA_ZHA_GATEWAY, DEFAULT_BAUDRATE,
|
||||
DEFAULT_RADIO_TYPE, DOMAIN, ENABLE_QUIRKS, RadioType)
|
||||
from .core.registries import establish_device_mappings
|
||||
|
||||
DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema({
|
||||
|
@ -147,11 +147,5 @@ async def async_unload_entry(hass, config_entry):
|
|||
await hass.config_entries.async_forward_entry_unload(
|
||||
config_entry, component)
|
||||
|
||||
# clean up device entities
|
||||
component = hass.data[DATA_ZHA][DATA_ZHA_CORE_COMPONENT]
|
||||
entity_ids = [entity.entity_id for entity in component.entities]
|
||||
for entity_id in entity_ids:
|
||||
await component.async_remove_entity(entity_id)
|
||||
|
||||
del hass.data[DATA_ZHA]
|
||||
return True
|
||||
|
|
|
@ -22,7 +22,6 @@ from ..const import (
|
|||
)
|
||||
from ..registries import CLUSTER_REPORT_CONFIGS
|
||||
|
||||
ZIGBEE_CHANNEL_REGISTRY = {}
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
|
|
@ -11,8 +11,7 @@ from homeassistant.helpers.event import async_call_later
|
|||
from . import ZigbeeChannel, parse_and_log_command
|
||||
from ..helpers import get_attr_id_by_name
|
||||
from ..const import (
|
||||
SIGNAL_ATTR_UPDATED, SIGNAL_MOVE_LEVEL, SIGNAL_SET_LEVEL,
|
||||
SIGNAL_STATE_ATTR
|
||||
SIGNAL_ATTR_UPDATED, SIGNAL_MOVE_LEVEL, SIGNAL_SET_LEVEL
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -202,8 +201,7 @@ class PowerConfigurationChannel(ZigbeeChannel):
|
|||
if attrid == attr_id:
|
||||
async_dispatcher_send(
|
||||
self._zha_device.hass,
|
||||
"{}_{}".format(self.unique_id, SIGNAL_STATE_ATTR),
|
||||
'battery_level',
|
||||
"{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED),
|
||||
value
|
||||
)
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ DATA_ZHA = 'zha'
|
|||
DATA_ZHA_CONFIG = 'config'
|
||||
DATA_ZHA_BRIDGE_ID = 'zha_bridge_id'
|
||||
DATA_ZHA_DISPATCHERS = 'zha_dispatchers'
|
||||
DATA_ZHA_CORE_COMPONENT = 'zha_core_component'
|
||||
DATA_ZHA_CORE_EVENTS = 'zha_core_events'
|
||||
DATA_ZHA_GATEWAY = 'zha_gateway'
|
||||
ZHA_DISCOVERY_NEW = 'zha_discovery_new_{}'
|
||||
|
@ -67,6 +66,9 @@ SERVER = 'server'
|
|||
IEEE = 'ieee'
|
||||
MODEL = 'model'
|
||||
NAME = 'name'
|
||||
LQI = 'lqi'
|
||||
RSSI = 'rssi'
|
||||
LAST_SEEN = 'last_seen'
|
||||
|
||||
SENSOR_TYPE = 'sensor_type'
|
||||
HUMIDITY = 'humidity'
|
||||
|
@ -76,6 +78,7 @@ PRESSURE = 'pressure'
|
|||
METERING = 'metering'
|
||||
ELECTRICAL_MEASUREMENT = 'electrical_measurement'
|
||||
GENERIC = 'generic'
|
||||
BATTERY = 'battery'
|
||||
UNKNOWN = 'unknown'
|
||||
UNKNOWN_MANUFACTURER = 'unk_manufacturer'
|
||||
UNKNOWN_MODEL = 'unk_model'
|
||||
|
|
|
@ -5,12 +5,15 @@ For more details about this component, please refer to the documentation at
|
|||
https://home-assistant.io/components/zha/
|
||||
"""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
from enum import Enum
|
||||
import logging
|
||||
import time
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect, async_dispatcher_send)
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
|
||||
from .channels import EventRelayChannel
|
||||
from .const import (
|
||||
|
@ -19,9 +22,12 @@ from .const import (
|
|||
BATTERY_OR_UNKNOWN, CLIENT_COMMANDS, IEEE, IN, MAINS_POWERED,
|
||||
MANUFACTURER_CODE, MODEL, NAME, NWK, OUT, POWER_CONFIGURATION_CHANNEL,
|
||||
POWER_SOURCE, QUIRK_APPLIED, QUIRK_CLASS, SERVER, SERVER_COMMANDS,
|
||||
SIGNAL_AVAILABLE, UNKNOWN_MANUFACTURER, UNKNOWN_MODEL, ZDO_CHANNEL)
|
||||
SIGNAL_AVAILABLE, UNKNOWN_MANUFACTURER, UNKNOWN_MODEL, ZDO_CHANNEL,
|
||||
LQI, RSSI, LAST_SEEN)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_KEEP_ALIVE_INTERVAL = 7200
|
||||
_UPDATE_ALIVE_INTERVAL = timedelta(seconds=60)
|
||||
|
||||
|
||||
class DeviceStatus(Enum):
|
||||
|
@ -56,6 +62,11 @@ class ZHADevice:
|
|||
self._zigpy_device.__class__.__module__,
|
||||
self._zigpy_device.__class__.__name__
|
||||
)
|
||||
self._available_check = async_track_time_interval(
|
||||
self.hass,
|
||||
self._check_available,
|
||||
_UPDATE_ALIVE_INTERVAL
|
||||
)
|
||||
self.status = DeviceStatus.CREATED
|
||||
|
||||
@property
|
||||
|
@ -158,6 +169,16 @@ class ZHADevice:
|
|||
"""Set availability from restore and prevent signals."""
|
||||
self._available = available
|
||||
|
||||
def _check_available(self, *_):
|
||||
if self.last_seen is None:
|
||||
self.update_available(False)
|
||||
else:
|
||||
difference = time.time() - self.last_seen
|
||||
if difference > _KEEP_ALIVE_INTERVAL:
|
||||
self.update_available(False)
|
||||
else:
|
||||
self.update_available(True)
|
||||
|
||||
def update_available(self, available):
|
||||
"""Set sensor availability."""
|
||||
if self._available != available and available:
|
||||
|
@ -178,6 +199,8 @@ class ZHADevice:
|
|||
def device_info(self):
|
||||
"""Return a device description for device."""
|
||||
ieee = str(self.ieee)
|
||||
time_struct = time.localtime(self.last_seen)
|
||||
update_time = time.strftime("%Y-%m-%dT%H:%M:%S", time_struct)
|
||||
return {
|
||||
IEEE: ieee,
|
||||
NWK: self.nwk,
|
||||
|
@ -187,7 +210,10 @@ class ZHADevice:
|
|||
QUIRK_APPLIED: self.quirk_applied,
|
||||
QUIRK_CLASS: self.quirk_class,
|
||||
MANUFACTURER_CODE: self.manufacturer_code,
|
||||
POWER_SOURCE: self.power_source
|
||||
POWER_SOURCE: self.power_source,
|
||||
LQI: self.lqi,
|
||||
RSSI: self.rssi,
|
||||
LAST_SEEN: update_time
|
||||
}
|
||||
|
||||
def add_cluster_channel(self, cluster_channel):
|
||||
|
|
|
@ -18,7 +18,7 @@ from .channels import (
|
|||
from .channels.registry import ZIGBEE_CHANNEL_REGISTRY
|
||||
from .const import (
|
||||
CONF_DEVICE_CONFIG, COMPONENTS, ZHA_DISCOVERY_NEW, DATA_ZHA,
|
||||
SENSOR_TYPE, UNKNOWN, GENERIC, POWER_CONFIGURATION_CHANNEL
|
||||
SENSOR_TYPE, UNKNOWN, GENERIC
|
||||
)
|
||||
from .registries import (
|
||||
BINARY_SENSOR_TYPES, CHANNEL_ONLY_CLUSTERS, EVENT_RELAY_CLUSTERS,
|
||||
|
@ -26,7 +26,6 @@ from .registries import (
|
|||
SINGLE_INPUT_CLUSTER_DEVICE_CLASS, SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS,
|
||||
OUTPUT_CHANNEL_ONLY_CLUSTERS, REMOTE_DEVICE_TYPES
|
||||
)
|
||||
from ..device_entity import ZhaDeviceEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -168,9 +167,10 @@ def _async_handle_single_cluster_matches(hass, endpoint, zha_device,
|
|||
profile_clusters, device_key,
|
||||
is_new_join):
|
||||
"""Dispatch single cluster matches to HA components."""
|
||||
from zigpy.zcl.clusters.general import OnOff
|
||||
from zigpy.zcl.clusters.general import OnOff, PowerConfiguration
|
||||
cluster_matches = []
|
||||
cluster_match_results = []
|
||||
matched_power_configuration = False
|
||||
for cluster in endpoint.in_clusters.values():
|
||||
if cluster.cluster_id in CHANNEL_ONLY_CLUSTERS:
|
||||
cluster_match_results.append(
|
||||
|
@ -182,6 +182,14 @@ def _async_handle_single_cluster_matches(hass, endpoint, zha_device,
|
|||
continue
|
||||
|
||||
if cluster.cluster_id not in profile_clusters:
|
||||
# Only create one battery sensor per device
|
||||
if cluster.cluster_id == PowerConfiguration.cluster_id and \
|
||||
(zha_device.is_mains_powered or
|
||||
matched_power_configuration):
|
||||
continue
|
||||
elif cluster.cluster_id == PowerConfiguration.cluster_id and not \
|
||||
zha_device.is_mains_powered:
|
||||
matched_power_configuration = True
|
||||
cluster_match_results.append(_async_handle_single_cluster_match(
|
||||
hass,
|
||||
zha_device,
|
||||
|
@ -279,13 +287,3 @@ def _async_handle_single_cluster_match(hass, zha_device, cluster, device_key,
|
|||
})
|
||||
|
||||
return discovery_info
|
||||
|
||||
|
||||
@callback
|
||||
def async_create_device_entity(zha_device):
|
||||
"""Create ZHADeviceEntity."""
|
||||
device_entity_channels = []
|
||||
if POWER_CONFIGURATION_CHANNEL in zha_device.cluster_channels:
|
||||
channel = zha_device.cluster_channels.get(POWER_CONFIGURATION_CHANNEL)
|
||||
device_entity_channels.append(channel)
|
||||
return ZhaDeviceEntity(zha_device, device_entity_channels)
|
||||
|
|
|
@ -17,22 +17,21 @@ from homeassistant.core import callback
|
|||
from homeassistant.helpers.device_registry import (
|
||||
async_get_registry as get_dev_reg)
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
||||
from ..api import async_get_device_info
|
||||
from .const import (
|
||||
ADD_DEVICE_RELAY_LOGGERS, ATTR_MANUFACTURER, BELLOWS, CONF_BAUDRATE,
|
||||
CONF_DATABASE, CONF_RADIO_TYPE, CONF_USB_PATH, CONTROLLER, CURRENT,
|
||||
DATA_ZHA, DATA_ZHA_BRIDGE_ID, DATA_ZHA_CORE_COMPONENT, DATA_ZHA_GATEWAY,
|
||||
DEBUG_LEVELS, DEFAULT_BAUDRATE, DEFAULT_DATABASE_NAME, DEVICE_FULL_INIT,
|
||||
DATA_ZHA, DATA_ZHA_BRIDGE_ID, DATA_ZHA_GATEWAY, DEBUG_LEVELS,
|
||||
DEFAULT_BAUDRATE, DEFAULT_DATABASE_NAME, DEVICE_FULL_INIT,
|
||||
DEVICE_INFO, DEVICE_JOINED, DEVICE_REMOVED, DOMAIN, IEEE, LOG_ENTRY,
|
||||
LOG_OUTPUT, MODEL, NWK, ORIGINAL, RADIO, RADIO_DESCRIPTION, RAW_INIT,
|
||||
SIGNAL_REMOVE, SIGNATURE, TYPE, UNKNOWN_MANUFACTURER, UNKNOWN_MODEL, ZHA,
|
||||
ZHA_GW_MSG, ZIGPY, ZIGPY_DECONZ, ZIGPY_XBEE)
|
||||
from .device import DeviceStatus, ZHADevice
|
||||
from .discovery import (
|
||||
async_create_device_entity, async_dispatch_discovery_info,
|
||||
async_process_endpoint)
|
||||
async_dispatch_discovery_info, async_process_endpoint
|
||||
)
|
||||
from .patches import apply_application_controller_patch
|
||||
from .registries import INPUT_BIND_ONLY_CLUSTERS, RADIO_TYPES
|
||||
from .store import async_get_registry
|
||||
|
@ -51,13 +50,11 @@ class ZHAGateway:
|
|||
"""Initialize the gateway."""
|
||||
self._hass = hass
|
||||
self._config = config
|
||||
self._component = EntityComponent(_LOGGER, DOMAIN, hass)
|
||||
self._devices = {}
|
||||
self._device_registry = collections.defaultdict(list)
|
||||
self.zha_storage = None
|
||||
self.application_controller = None
|
||||
self.radio_description = None
|
||||
hass.data[DATA_ZHA][DATA_ZHA_CORE_COMPONENT] = self._component
|
||||
hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] = self
|
||||
self._log_levels = {
|
||||
ORIGINAL: async_capture_log_levels(),
|
||||
|
@ -324,9 +321,6 @@ class ZHAGateway:
|
|||
discovery_info
|
||||
)
|
||||
|
||||
device_entity = async_create_device_entity(zha_device)
|
||||
await self._component.async_add_entities([device_entity])
|
||||
|
||||
if is_new_join:
|
||||
device_info = async_get_device_info(self._hass, zha_device)
|
||||
async_dispatcher_send(
|
||||
|
|
|
@ -18,7 +18,7 @@ from .const import (
|
|||
OCCUPANCY, REPORT_CONFIG_IMMEDIATE, OPENING, ZONE, RADIO_DESCRIPTION,
|
||||
REPORT_CONFIG_ASAP, REPORT_CONFIG_DEFAULT, REPORT_CONFIG_MIN_INT,
|
||||
REPORT_CONFIG_MAX_INT, REPORT_CONFIG_OP, ACCELERATION, RadioType, RADIO,
|
||||
CONTROLLER
|
||||
CONTROLLER, BATTERY
|
||||
)
|
||||
|
||||
SMARTTHINGS_HUMIDITY_CLUSTER = 64581
|
||||
|
@ -110,8 +110,6 @@ def establish_device_mappings():
|
|||
EVENT_RELAY_CLUSTERS.append(zcl.clusters.general.OnOff.cluster_id)
|
||||
|
||||
CHANNEL_ONLY_CLUSTERS.append(zcl.clusters.general.Basic.cluster_id)
|
||||
CHANNEL_ONLY_CLUSTERS.append(
|
||||
zcl.clusters.general.PowerConfiguration.cluster_id)
|
||||
CHANNEL_ONLY_CLUSTERS.append(zcl.clusters.lightlink.LightLink.cluster_id)
|
||||
|
||||
OUTPUT_CHANNEL_ONLY_CLUSTERS.append(zcl.clusters.general.Scenes.cluster_id)
|
||||
|
@ -166,7 +164,8 @@ def establish_device_mappings():
|
|||
SMARTTHINGS_ACCELERATION_CLUSTER: BINARY_SENSOR,
|
||||
zcl.clusters.general.MultistateInput.cluster_id: SENSOR,
|
||||
zcl.clusters.general.AnalogInput.cluster_id: SENSOR,
|
||||
zcl.clusters.closures.DoorLock: LOCK
|
||||
zcl.clusters.closures.DoorLock: LOCK,
|
||||
zcl.clusters.general.PowerConfiguration: SENSOR
|
||||
})
|
||||
|
||||
SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS.update({
|
||||
|
@ -184,6 +183,7 @@ def establish_device_mappings():
|
|||
zcl.clusters.smartenergy.Metering.cluster_id: METERING,
|
||||
zcl.clusters.homeautomation.ElectricalMeasurement.cluster_id:
|
||||
ELECTRICAL_MEASUREMENT,
|
||||
zcl.clusters.general.PowerConfiguration.cluster_id: BATTERY
|
||||
})
|
||||
|
||||
BINARY_SENSOR_TYPES.update({
|
||||
|
|
|
@ -1,158 +0,0 @@
|
|||
"""Device entity for Zigbee Home Automation."""
|
||||
|
||||
import logging
|
||||
import numbers
|
||||
import time
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.util import slugify
|
||||
|
||||
from .core.const import POWER_CONFIGURATION_CHANNEL, SIGNAL_STATE_ATTR
|
||||
from .entity import ZhaEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
BATTERY_SIZES = {
|
||||
0: 'No battery',
|
||||
1: 'Built in',
|
||||
2: 'Other',
|
||||
3: 'AA',
|
||||
4: 'AAA',
|
||||
5: 'C',
|
||||
6: 'D',
|
||||
7: 'CR2',
|
||||
8: 'CR123A',
|
||||
9: 'CR2450',
|
||||
10: 'CR2032',
|
||||
11: 'CR1632',
|
||||
255: 'Unknown'
|
||||
}
|
||||
|
||||
STATE_ONLINE = 'online'
|
||||
STATE_OFFLINE = 'offline'
|
||||
|
||||
|
||||
class ZhaDeviceEntity(ZhaEntity):
|
||||
"""A base class for ZHA devices."""
|
||||
|
||||
def __init__(self, zha_device, channels, keepalive_interval=7200,
|
||||
**kwargs):
|
||||
"""Init ZHA endpoint entity."""
|
||||
ieee = zha_device.ieee
|
||||
ieeetail = ''.join(['%02x' % (o, ) for o in ieee[-4:]])
|
||||
unique_id = "{}_{}_{}".format(
|
||||
slugify(zha_device.manufacturer),
|
||||
slugify(zha_device.model),
|
||||
ieeetail,
|
||||
)
|
||||
|
||||
kwargs['component'] = 'zha'
|
||||
super().__init__(unique_id, zha_device, channels, skip_entity_id=True,
|
||||
**kwargs)
|
||||
|
||||
self._keepalive_interval = keepalive_interval
|
||||
self._device_state_attributes.update({
|
||||
'nwk': '0x{0:04x}'.format(zha_device.nwk),
|
||||
'ieee': str(zha_device.ieee),
|
||||
'lqi': zha_device.lqi,
|
||||
'rssi': zha_device.rssi,
|
||||
})
|
||||
self._should_poll = True
|
||||
self._battery_channel = self.cluster_channels.get(
|
||||
POWER_CONFIGURATION_CHANNEL)
|
||||
|
||||
@property
|
||||
def state(self) -> str:
|
||||
"""Return the state of the entity."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if device is available."""
|
||||
return self._zha_device.available
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return device specific state attributes."""
|
||||
update_time = None
|
||||
device = self._zha_device
|
||||
if device.last_seen is not None and not self.available:
|
||||
time_struct = time.localtime(device.last_seen)
|
||||
update_time = time.strftime("%Y-%m-%dT%H:%M:%S", time_struct)
|
||||
self._device_state_attributes['last_seen'] = update_time
|
||||
if ('last_seen' in self._device_state_attributes and
|
||||
self.available):
|
||||
del self._device_state_attributes['last_seen']
|
||||
self._device_state_attributes['lqi'] = device.lqi
|
||||
self._device_state_attributes['rssi'] = device.rssi
|
||||
return self._device_state_attributes
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Run when about to be added to hass."""
|
||||
await super().async_added_to_hass()
|
||||
await self.async_check_recently_seen()
|
||||
if self._battery_channel:
|
||||
await self.async_accept_signal(
|
||||
self._battery_channel, SIGNAL_STATE_ATTR,
|
||||
self.async_update_state_attribute)
|
||||
# only do this on add to HA because it is static
|
||||
await self._async_init_battery_values()
|
||||
|
||||
def async_update_state_attribute(self, key, value):
|
||||
"""Update a single device state attribute."""
|
||||
if key == 'battery_level':
|
||||
if not isinstance(value, numbers.Number) or value == -1:
|
||||
return
|
||||
value = value / 2
|
||||
value = int(round(value))
|
||||
self._device_state_attributes.update({
|
||||
key: value
|
||||
})
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
async def async_update(self):
|
||||
"""Handle polling."""
|
||||
if self._zha_device.last_seen is None:
|
||||
self._zha_device.update_available(False)
|
||||
else:
|
||||
difference = time.time() - self._zha_device.last_seen
|
||||
if difference > self._keepalive_interval:
|
||||
self._zha_device.update_available(False)
|
||||
else:
|
||||
self._zha_device.update_available(True)
|
||||
if self._battery_channel:
|
||||
await self.async_get_latest_battery_reading()
|
||||
|
||||
@callback
|
||||
def async_set_available(self, available):
|
||||
"""Set entity availability."""
|
||||
if available:
|
||||
self._state = STATE_ONLINE
|
||||
else:
|
||||
self._state = STATE_OFFLINE
|
||||
super().async_set_available(available)
|
||||
|
||||
async def _async_init_battery_values(self):
|
||||
"""Get initial battery level and battery info from channel cache."""
|
||||
battery_size = await self._battery_channel.get_attribute_value(
|
||||
'battery_size')
|
||||
if battery_size is not None:
|
||||
self._device_state_attributes['battery_size'] = BATTERY_SIZES.get(
|
||||
battery_size, 'Unknown')
|
||||
|
||||
battery_quantity = await self._battery_channel.get_attribute_value(
|
||||
'battery_quantity')
|
||||
if battery_quantity is not None:
|
||||
self._device_state_attributes['battery_quantity'] = \
|
||||
battery_quantity
|
||||
await self.async_get_latest_battery_reading()
|
||||
|
||||
async def async_get_latest_battery_reading(self):
|
||||
"""Get the latest battery reading from channels cache."""
|
||||
battery = await self._battery_channel.get_attribute_value(
|
||||
'battery_percentage_remaining')
|
||||
# per zcl specs battery percent is reported at 200% ¯\_(ツ)_/¯
|
||||
if battery is not None and battery != -1:
|
||||
battery = battery / 2
|
||||
battery = int(round(battery))
|
||||
self._device_state_attributes['battery_level'] = battery
|
|
@ -1,10 +1,12 @@
|
|||
"""Sensors on Zigbee Home Automation networks."""
|
||||
import logging
|
||||
import numbers
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.sensor import (
|
||||
DOMAIN, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE,
|
||||
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_POWER
|
||||
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_BATTERY
|
||||
)
|
||||
from homeassistant.const import (
|
||||
TEMP_CELSIUS, POWER_WATT, ATTR_UNIT_OF_MEASUREMENT
|
||||
|
@ -14,12 +16,29 @@ from .core.const import (
|
|||
DATA_ZHA, DATA_ZHA_DISPATCHERS, ZHA_DISCOVERY_NEW, HUMIDITY, TEMPERATURE,
|
||||
ILLUMINANCE, PRESSURE, METERING, ELECTRICAL_MEASUREMENT,
|
||||
GENERIC, SENSOR_TYPE, ATTRIBUTE_CHANNEL, ELECTRICAL_MEASUREMENT_CHANNEL,
|
||||
SIGNAL_ATTR_UPDATED, SIGNAL_STATE_ATTR, UNKNOWN)
|
||||
SIGNAL_ATTR_UPDATED, SIGNAL_STATE_ATTR, UNKNOWN, BATTERY,
|
||||
POWER_CONFIGURATION_CHANNEL)
|
||||
from .entity import ZhaEntity
|
||||
|
||||
PARALLEL_UPDATES = 5
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
BATTERY_SIZES = {
|
||||
0: 'No battery',
|
||||
1: 'Built in',
|
||||
2: 'Other',
|
||||
3: 'AA',
|
||||
4: 'AAA',
|
||||
5: 'C',
|
||||
6: 'D',
|
||||
7: 'CR2',
|
||||
8: 'CR123A',
|
||||
9: 'CR2450',
|
||||
10: 'CR2032',
|
||||
11: 'CR1632',
|
||||
255: 'Unknown'
|
||||
}
|
||||
|
||||
|
||||
# Formatter functions
|
||||
def pass_through_formatter(value):
|
||||
|
@ -63,6 +82,29 @@ def pressure_formatter(value):
|
|||
return round(float(value))
|
||||
|
||||
|
||||
def battery_percentage_remaining_formatter(value):
|
||||
"""Return the state of the entity."""
|
||||
# per zcl specs battery percent is reported at 200% ¯\_(ツ)_/¯
|
||||
if not isinstance(value, numbers.Number) or value == -1:
|
||||
return value
|
||||
value = value / 2
|
||||
value = int(round(value))
|
||||
return value
|
||||
|
||||
|
||||
async def async_battery_device_state_attr_provider(channel):
|
||||
"""Return device statr attrs for battery sensors."""
|
||||
state_attrs = {}
|
||||
battery_size = await channel.get_attribute_value('battery_size')
|
||||
if battery_size is not None:
|
||||
state_attrs['battery_size'] = BATTERY_SIZES.get(
|
||||
battery_size, 'Unknown')
|
||||
battery_quantity = await channel.get_attribute_value('battery_quantity')
|
||||
if battery_quantity is not None:
|
||||
state_attrs['battery_quantity'] = battery_quantity
|
||||
return state_attrs
|
||||
|
||||
|
||||
FORMATTER_FUNC_REGISTRY = {
|
||||
HUMIDITY: humidity_formatter,
|
||||
TEMPERATURE: temperature_formatter,
|
||||
|
@ -70,6 +112,7 @@ FORMATTER_FUNC_REGISTRY = {
|
|||
ELECTRICAL_MEASUREMENT: active_power_formatter,
|
||||
ILLUMINANCE: illuminance_formatter,
|
||||
GENERIC: pass_through_formatter,
|
||||
BATTERY: battery_percentage_remaining_formatter
|
||||
}
|
||||
|
||||
UNIT_REGISTRY = {
|
||||
|
@ -79,11 +122,13 @@ UNIT_REGISTRY = {
|
|||
ILLUMINANCE: 'lx',
|
||||
METERING: POWER_WATT,
|
||||
ELECTRICAL_MEASUREMENT: POWER_WATT,
|
||||
GENERIC: None
|
||||
GENERIC: None,
|
||||
BATTERY: '%'
|
||||
}
|
||||
|
||||
CHANNEL_REGISTRY = {
|
||||
ELECTRICAL_MEASUREMENT: ELECTRICAL_MEASUREMENT_CHANNEL,
|
||||
BATTERY: POWER_CONFIGURATION_CHANNEL
|
||||
}
|
||||
|
||||
POLLING_REGISTRY = {
|
||||
|
@ -101,7 +146,13 @@ DEVICE_CLASS_REGISTRY = {
|
|||
PRESSURE: DEVICE_CLASS_PRESSURE,
|
||||
ILLUMINANCE: DEVICE_CLASS_ILLUMINANCE,
|
||||
METERING: DEVICE_CLASS_POWER,
|
||||
ELECTRICAL_MEASUREMENT: DEVICE_CLASS_POWER
|
||||
ELECTRICAL_MEASUREMENT: DEVICE_CLASS_POWER,
|
||||
BATTERY: DEVICE_CLASS_BATTERY
|
||||
}
|
||||
|
||||
|
||||
DEVICE_STATE_ATTR_PROVIDER_REGISTRY = {
|
||||
BATTERY: async_battery_device_state_attr_provider
|
||||
}
|
||||
|
||||
|
||||
|
@ -172,10 +223,18 @@ class Sensor(ZhaEntity):
|
|||
self._sensor_type,
|
||||
None
|
||||
)
|
||||
self.state_attr_provider = DEVICE_STATE_ATTR_PROVIDER_REGISTRY.get(
|
||||
self._sensor_type,
|
||||
None
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Run when about to be added to hass."""
|
||||
await super().async_added_to_hass()
|
||||
if self.state_attr_provider is not None:
|
||||
self._device_state_attributes = await self.state_attr_provider(
|
||||
self._channel
|
||||
)
|
||||
await self.async_accept_signal(
|
||||
self._channel, SIGNAL_ATTR_UPDATED, self.async_set_state)
|
||||
await self.async_accept_signal(
|
||||
|
|
Loading…
Reference in New Issue