ZHA log helper (#25543)

* Logging helper.
* Use log helper for ZHA entities.
* Use log helper for ZHA core device.
* Log helper for ZHA core channels.
* Lint
* ZHA fixture fix.
This commit is contained in:
Alexei Chetroi 2019-07-30 15:19:24 -04:00 committed by GitHub
parent 2c144bc412
commit 5aa35b52cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 106 additions and 105 deletions

View file

@ -15,7 +15,7 @@ from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_send
from ..helpers import (
configure_reporting, construct_unique_id,
safe_read, get_attr_id_by_name, bind_cluster)
safe_read, get_attr_id_by_name, bind_cluster, LogMixin)
from ..const import (
REPORT_CONFIG_DEFAULT, SIGNAL_ATTR_UPDATED, ATTRIBUTE_CHANNEL,
EVENT_RELAY_CHANNEL, ZDO_CHANNEL
@ -25,15 +25,14 @@ from ..registries import CLUSTER_REPORT_CONFIGS
_LOGGER = logging.getLogger(__name__)
def parse_and_log_command(unique_id, cluster, tsn, command_id, args):
def parse_and_log_command(channel, tsn, command_id, args):
"""Parse and log a zigbee cluster command."""
cmd = cluster.server_commands.get(command_id, [command_id])[0]
_LOGGER.debug(
"%s: received '%s' command with %s args on cluster_id '%s' tsn '%s'",
unique_id,
cmd = channel.cluster.server_commands.get(command_id, [command_id])[0]
channel.debug(
"received '%s' command with %s args on cluster_id '%s' tsn '%s'",
cmd,
args,
cluster.cluster_id,
channel.cluster.cluster_id,
tsn
)
return cmd
@ -46,8 +45,7 @@ def decorate_command(channel, command):
from zigpy.exceptions import DeliveryError
try:
result = await command(*args, **kwds)
_LOGGER.debug("%s: executed command: %s %s %s %s",
channel.unique_id,
channel.debug("executed command: %s %s %s %s",
command.__name__,
"{}: {}".format("with args", args),
"{}: {}".format("with kwargs", kwds),
@ -55,9 +53,8 @@ def decorate_command(channel, command):
return result
except (DeliveryError, Timeout) as ex:
_LOGGER.debug(
"%s: command failed: %s exception: %s",
channel.unique_id,
channel.debug(
"command failed: %s exception: %s",
command.__name__,
str(ex)
)
@ -73,7 +70,7 @@ class ChannelStatus(Enum):
INITIALIZED = 3
class ZigbeeChannel:
class ZigbeeChannel(LogMixin):
"""Base channel for a Zigbee cluster."""
CHANNEL_NAME = None
@ -151,19 +148,12 @@ class ZigbeeChannel:
)
await asyncio.sleep(uniform(0.1, 0.5))
_LOGGER.debug(
"%s: finished channel configuration",
self._unique_id
)
self.debug("finished channel configuration")
self._status = ChannelStatus.CONFIGURED
async def async_initialize(self, from_cache):
"""Initialize channel."""
_LOGGER.debug(
'initializing channel: %s from_cache: %s',
self._channel_name,
from_cache
)
self.debug('initializing channel: from_cache: %s', from_cache)
self._status = ChannelStatus.INITIALIZED
@callback
@ -213,6 +203,11 @@ class ZigbeeChannel:
)
return result.get(attribute)
def log(self, level, msg, *args):
msg = '[%s]: ' + msg
args = (self.unique_id, ) + args
_LOGGER.log(level, msg, *args)
def __getattr__(self, name):
"""Get attribute or a decorated cluster command."""
if hasattr(self._cluster, name) and callable(
@ -257,7 +252,7 @@ class AttributeListeningChannel(ZigbeeChannel):
await super().async_initialize(from_cache)
class ZDOChannel:
class ZDOChannel(LogMixin):
"""Channel for ZDO events."""
def __init__(self, cluster, device):
@ -298,13 +293,18 @@ class ZDOChannel:
"""Initialize channel."""
entry = self._zha_device.gateway.zha_storage.async_get_or_create(
self._zha_device)
_LOGGER.debug("entry loaded from storage: %s", entry)
self.debug("entry loaded from storage: %s", entry)
self._status = ChannelStatus.INITIALIZED
async def async_configure(self):
"""Configure channel."""
self._status = ChannelStatus.CONFIGURED
def log(self, level, msg, *args):
msg = '[%s:ZDO](%s): ' + msg
args = (self._zha_device.nwk, self._zha_device.model, ) + args
_LOGGER.log(level, msg, *args)
class EventRelayChannel(ZigbeeChannel):
"""Event relay that can be attached to zigbee clusters."""

View file

@ -32,8 +32,8 @@ class DoorLockChannel(ZigbeeChannel):
def attribute_updated(self, attrid, value):
"""Handle attribute update from lock cluster."""
attr_name = self.cluster.attributes.get(attrid, [attrid])[0]
_LOGGER.debug("%s: Attribute report '%s'[%s] = %s",
self.unique_id, self.cluster.name, attr_name, value)
self.debug("Attribute report '%s'[%s] = %s",
self.cluster.name, attr_name, value)
if attrid == self._value_attribute:
async_dispatcher_send(
self._zha_device.hass,

View file

@ -31,13 +31,7 @@ class OnOffChannel(ZigbeeChannel):
@callback
def cluster_command(self, tsn, command_id, args):
"""Handle commands received to this cluster."""
cmd = parse_and_log_command(
self.unique_id,
self._cluster,
tsn,
command_id,
args
)
cmd = parse_and_log_command(self, tsn, command_id, args)
if cmd in ('off', 'off_with_effect'):
self.attribute_updated(self.ON_OFF, False)
@ -87,11 +81,8 @@ class OnOffChannel(ZigbeeChannel):
async def async_update(self):
"""Initialize channel."""
from_cache = not self.device.is_mains_powered
_LOGGER.debug(
"%s is attempting to update onoff state - from cache: %s",
self._unique_id,
from_cache
)
self.debug("attempting to update onoff state - from cache: %s",
from_cache)
self._state = bool(
await self.get_attribute_value(self.ON_OFF, from_cache=from_cache))
await super().async_update()
@ -105,13 +96,7 @@ class LevelControlChannel(ZigbeeChannel):
@callback
def cluster_command(self, tsn, command_id, args):
"""Handle commands received to this cluster."""
cmd = parse_and_log_command(
self.unique_id,
self._cluster,
tsn,
command_id,
args
)
cmd = parse_and_log_command(self, tsn, command_id, args)
if cmd in ('move_to_level', 'move_to_level_with_on_off'):
self.dispatch_level_change(SIGNAL_SET_LEVEL, args[0])
@ -130,8 +115,8 @@ class LevelControlChannel(ZigbeeChannel):
@callback
def attribute_updated(self, attrid, value):
"""Handle attribute updates on this cluster."""
_LOGGER.debug("%s: received attribute: %s update with value: %i",
self.unique_id, attrid, value)
self.debug("received attribute: %s update with value: %s",
attrid, value)
if attrid == self.CURRENT_LEVEL:
self.dispatch_level_change(SIGNAL_SET_LEVEL, value)

View file

@ -19,7 +19,7 @@ class ElectricalMeasurementChannel(AttributeListeningChannel):
async def async_update(self):
"""Retrieve latest state."""
_LOGGER.debug("%s async_update", self.unique_id)
self.debug("async_update")
# This is a polling channel. Don't allow cache.
result = await self.get_attribute_value('active_power',

View file

@ -24,7 +24,7 @@ class FanChannel(ZigbeeChannel):
try:
await self.cluster.write_attributes({'fan_mode': value})
except DeliveryError as ex:
_LOGGER.error("%s: Could not set speed: %s", self.unique_id, ex)
self.error("Could not set speed: %s", ex)
return
async def async_update(self):
@ -41,8 +41,8 @@ class FanChannel(ZigbeeChannel):
def attribute_updated(self, attrid, value):
"""Handle attribute update from fan cluster."""
attr_name = self.cluster.attributes.get(attrid, [attrid])[0]
_LOGGER.debug("%s: Attribute report '%s'[%s] = %s",
self.unique_id, self.cluster.name, attr_name, value)
self.debug("Attribute report '%s'[%s] = %s",
self.cluster.name, attr_name, value)
if attrid == self._value_attribute:
async_dispatcher_send(
self._zha_device.hass,

View file

@ -27,9 +27,9 @@ class IASZoneChannel(ZigbeeChannel):
"{}_{}".format(self.unique_id, SIGNAL_ATTR_UPDATED),
state
)
_LOGGER.debug("Updated alarm state: %s", state)
self.debug("Updated alarm state: %s", state)
elif command_id == 1:
_LOGGER.debug("Enroll requested")
self.debug("Enroll requested")
res = self._cluster.enroll_response(0, 0)
self._zha_device.hass.async_create_task(res)
@ -39,26 +39,24 @@ class IASZoneChannel(ZigbeeChannel):
if self._zha_device.manufacturer == 'LUMI':
return
from zigpy.exceptions import DeliveryError
_LOGGER.debug("%s: started IASZoneChannel configuration",
self._unique_id)
self.debug("started IASZoneChannel configuration")
await bind_cluster(self.unique_id, self._cluster)
ieee = self._cluster.endpoint.device.application.ieee
try:
res = await self._cluster.write_attributes({'cie_addr': ieee})
_LOGGER.debug(
"%s: wrote cie_addr: %s to '%s' cluster: %s",
self.unique_id, str(ieee), self._cluster.ep_attribute,
self.debug(
"wrote cie_addr: %s to '%s' cluster: %s",
str(ieee), self._cluster.ep_attribute,
res[0]
)
except DeliveryError as ex:
_LOGGER.debug(
"%s: Failed to write cie_addr: %s to '%s' cluster: %s",
self.unique_id, str(ieee), self._cluster.ep_attribute, str(ex)
self.debug(
"Failed to write cie_addr: %s to '%s' cluster: %s",
str(ieee), self._cluster.ep_attribute, str(ex)
)
_LOGGER.debug("%s: finished IASZoneChannel configuration",
self._unique_id)
self.debug("%s: finished IASZoneChannel configuration")
await self.get_attribute_value('zone_type', from_cache=False)

View file

@ -24,6 +24,7 @@ from .const import (
POWER_SOURCE, QUIRK_APPLIED, QUIRK_CLASS, SERVER, SERVER_COMMANDS,
SIGNAL_AVAILABLE, UNKNOWN_MANUFACTURER, UNKNOWN_MODEL, ZDO_CHANNEL,
LQI, RSSI, LAST_SEEN, ATTR_AVAILABLE)
from .helpers import LogMixin
_LOGGER = logging.getLogger(__name__)
_KEEP_ALIVE_INTERVAL = 7200
@ -37,7 +38,7 @@ class DeviceStatus(Enum):
INITIALIZED = 2
class ZHADevice:
class ZHADevice(LogMixin):
"""ZHA Zigbee device object."""
def __init__(self, hass, zigpy_device, zha_gateway):
@ -272,25 +273,21 @@ class ZHADevice:
async def async_configure(self):
"""Configure the device."""
_LOGGER.debug('%s: started configuration', self.name)
self.debug('started configuration')
await self._execute_channel_tasks(
self.get_channels_to_configure(), 'async_configure')
_LOGGER.debug('%s: completed configuration', self.name)
self.debug('completed configuration')
entry = self.gateway.zha_storage.async_create_or_update(self)
_LOGGER.debug('%s: stored in registry: %s', self.name, entry)
self.debug('stored in registry: %s', entry)
async def async_initialize(self, from_cache=False):
"""Initialize channels."""
_LOGGER.debug('%s: started initialization', self.name)
self.debug('started initialization')
await self._execute_channel_tasks(
self.all_channels, 'async_initialize', from_cache)
_LOGGER.debug(
'%s: power source: %s',
self.name,
self.power_source
)
self.debug('power source: %s', self.power_source)
self.status = DeviceStatus.INITIALIZED
_LOGGER.debug('%s: completed initialization', self.name)
self.debug('completed initialization')
async def _execute_channel_tasks(self, channels, task_name, *args):
"""Gather and execute a set of CHANNEL tasks."""
@ -316,19 +313,9 @@ class ZHADevice:
try:
async with semaphore:
await getattr(channel, func_name)(*args)
_LOGGER.debug('%s: channel: %s %s stage succeeded',
self.name,
"{}-{}".format(
channel.name, channel.unique_id),
func_name)
channel.debug("channel: '%s' stage succeeded", func_name)
except Exception as ex: # pylint: disable=broad-except
_LOGGER.warning(
'%s channel: %s %s stage failed ex: %s',
self.name,
"{}-{}".format(channel.name, channel.unique_id),
func_name,
ex
)
channel.warning("channel: '%s' stage failed ex: %s", func_name, ex)
@callback
def async_unsub_dispatcher(self):
@ -409,7 +396,7 @@ class ZHADevice:
{attribute: value},
manufacturer=manufacturer
)
_LOGGER.debug(
self.debug(
'set: %s for attr: %s to cluster: %s for ept: %s - res: %s',
value,
attribute,
@ -419,7 +406,7 @@ class ZHADevice:
)
return response
except DeliveryError as exc:
_LOGGER.debug(
self.debug(
'failed to set attribute: %s %s %s %s %s',
'{}: {}'.format(ATTR_VALUE, value),
'{}: {}'.format(ATTR_ATTRIBUTE, attribute),
@ -444,7 +431,7 @@ class ZHADevice:
else:
response = await cluster.client_command(command, *args)
_LOGGER.debug(
self.debug(
'Issued cluster command: %s %s %s %s %s %s %s',
'{}: {}'.format(ATTR_CLUSTER_ID, cluster_id),
'{}: {}'.format(ATTR_COMMAND, command),
@ -455,3 +442,8 @@ class ZHADevice:
'{}: {}'.format(ATTR_ENDPOINT_ID, endpoint_id)
)
return response
def log(self, level, msg, *args):
msg = '[%s](%s): ' + msg
args = (self.nwk, self.model, ) + args
_LOGGER.log(level, msg, *args)

View file

@ -207,3 +207,27 @@ def async_is_bindable_target(source_zha_device, target_zha_device):
if any(bindable in bindables for bindable in matches):
return True
return False
class LogMixin:
"""Log helper."""
def log(self, level, msg, *args):
"""Log with level."""
raise NotImplementedError
def debug(self, msg, *args):
"""Debug level log."""
return self.log(logging.DEBUG, msg, *args)
def info(self, msg, *args):
"""Info level log."""
return self.log(logging.INFO, msg, *args)
def warning(self, msg, *args):
"""Warning method log."""
return self.log(logging.WARNING, msg, *args)
def error(self, msg, *args):
"""Error level log."""
return self.log(logging.ERROR, msg, *args)

View file

@ -91,7 +91,7 @@ class ZHADeviceScannerEntity(ScannerEntity, ZhaEntity):
@callback
def async_battery_percentage_remaining_updated(self, value):
"""Handle tracking."""
_LOGGER.debug('battery_percentage_remaining updated: %s', value)
self.debug('battery_percentage_remaining updated: %s', value)
self._connected = True
self._battery_level = battery_percentage_remaining_formatter(value)
self.async_schedule_update_ha_state()

View file

@ -14,6 +14,7 @@ from homeassistant.util import slugify
from .core.const import (
ATTR_MANUFACTURER, DATA_ZHA, DATA_ZHA_BRIDGE_ID, DOMAIN, MODEL, NAME,
SIGNAL_REMOVE)
from .core.helpers import LogMixin
_LOGGER = logging.getLogger(__name__)
@ -21,7 +22,7 @@ ENTITY_SUFFIX = 'entity_suffix'
RESTART_GRACE_PERIOD = 7200 # 2 hours
class ZhaEntity(RestoreEntity, entity.Entity):
class ZhaEntity(RestoreEntity, LogMixin, entity.Entity):
"""A base class for ZHA entities."""
_domain = None # Must be overridden by subclasses
@ -186,3 +187,8 @@ class ZhaEntity(RestoreEntity, entity.Entity):
func
)
self._unsubs.append(unsub)
def log(self, level, msg, *args):
msg = '%s: ' + msg
args = (self.entity_id, ) + args
_LOGGER.log(level, msg, *args)

View file

@ -273,7 +273,3 @@ class Light(ZhaEntity, light.Light):
async def refresh(self, time):
"""Call async_get_state at an interval."""
await self.async_get_state(from_cache=False)
def debug(self, msg, *args):
"""Log debug message."""
_LOGGER.debug('%s: ' + msg, self.entity_id, *args)

View file

@ -95,7 +95,7 @@ class ZhaDoorLock(ZhaEntity, LockDevice):
"""Lock the lock."""
result = await self._doorlock_channel.lock_door()
if not isinstance(result, list) or result[0] is not Status.SUCCESS:
_LOGGER.error("Error with lock_door: %s", result)
self.error("Error with lock_door: %s", result)
return
self.async_schedule_update_ha_state()
@ -103,7 +103,7 @@ class ZhaDoorLock(ZhaEntity, LockDevice):
"""Unlock the lock."""
result = await self._doorlock_channel.unlock_door()
if not isinstance(result, list) or result[0] is not Status.SUCCESS:
_LOGGER.error("Error with unlock_door: %s", result)
self.error("Error with unlock_door: %s", result)
return
self.async_schedule_update_ha_state()
@ -128,7 +128,3 @@ class ZhaDoorLock(ZhaEntity, LockDevice):
async def refresh(self, time):
"""Call async_get_state at an interval."""
await self.async_get_state(from_cache=False)
def debug(self, msg, *args):
"""Log debug message."""
_LOGGER.debug('%s: ' + msg, self.entity_id, *args)

View file

@ -1,11 +1,14 @@
"""Common test objects."""
import time
from unittest.mock import patch, Mock
from homeassistant.components.zha.core.helpers import convert_ieee
from unittest.mock import Mock, patch
from asynctest import CoroutineMock
from homeassistant.components.zha.core.const import (
DATA_ZHA, DATA_ZHA_CONFIG, DATA_ZHA_DISPATCHERS, DATA_ZHA_BRIDGE_ID
)
DATA_ZHA, DATA_ZHA_BRIDGE_ID, DATA_ZHA_CONFIG, DATA_ZHA_DISPATCHERS)
from homeassistant.components.zha.core.helpers import convert_ieee
from homeassistant.util import slugify
from tests.common import mock_coro
@ -57,6 +60,7 @@ class FakeEndpoint:
def patch_cluster(cluster):
"""Patch a cluster for testing."""
cluster.bind = CoroutineMock(return_value=[0])
cluster.deserialize = Mock()
cluster.handle_cluster_request = Mock()
cluster.handle_cluster_general_request = Mock()