1 """Helper functions for the ZHA integration."""
3 from __future__
import annotations
7 from collections.abc
import Awaitable, Callable, Coroutine, Mapping
16 from types
import MappingProxyType
17 from typing
import TYPE_CHECKING, Any, Concatenate, NamedTuple, cast
18 from zoneinfo
import ZoneInfo
20 import voluptuous
as vol
21 from zha.application.const
import (
28 CONF_DEFAULT_CONSIDER_UNAVAILABLE_BATTERY,
29 CONF_DEFAULT_CONSIDER_UNAVAILABLE_MAINS,
32 ZHA_CLUSTER_HANDLER_CFG_DONE,
33 ZHA_CLUSTER_HANDLER_MSG,
34 ZHA_CLUSTER_HANDLER_MSG_BIND,
35 ZHA_CLUSTER_HANDLER_MSG_CFG_RPT,
36 ZHA_CLUSTER_HANDLER_MSG_DATA,
39 ZHA_GW_MSG_DEVICE_FULL_INIT,
40 ZHA_GW_MSG_DEVICE_INFO,
41 ZHA_GW_MSG_DEVICE_JOINED,
42 ZHA_GW_MSG_DEVICE_REMOVED,
43 ZHA_GW_MSG_GROUP_ADDED,
44 ZHA_GW_MSG_GROUP_INFO,
45 ZHA_GW_MSG_GROUP_MEMBER_ADDED,
46 ZHA_GW_MSG_GROUP_MEMBER_REMOVED,
47 ZHA_GW_MSG_GROUP_REMOVED,
51 from zha.application.gateway
import (
59 RawDeviceInitializedEvent,
61 from zha.application.helpers
import (
62 AlarmControlPanelOptions,
63 CoordinatorConfiguration,
65 DeviceOverridesConfiguration,
71 from zha.application.platforms
import GroupEntity, PlatformEntity
72 from zha.event
import EventBase
73 from zha.exceptions
import ZHAException
74 from zha.mixins
import LogMixin
75 from zha.zigbee.cluster_handlers
import ClusterBindEvent, ClusterConfigureReportingEvent
76 from zha.zigbee.device
import ClusterHandlerConfigurationComplete, Device, ZHAEvent
77 from zha.zigbee.group
import Group, GroupInfo, GroupMember
78 from zigpy.config
import (
85 import zigpy.exceptions
86 from zigpy.profiles
import PROFILES
88 from zigpy.types
import EUI64
91 from zigpy.zcl.foundation
import CommandSchema
93 from homeassistant
import __path__
as HOMEASSISTANT_PATH
110 config_validation
as cv,
111 device_registry
as dr,
112 entity_registry
as er,
119 ATTR_ACTIVE_COORDINATOR,
129 ATTR_MANUFACTURER_CODE,
140 CONF_ALARM_ARM_REQUIRES_CODE,
141 CONF_ALARM_FAILED_TRIES,
142 CONF_ALARM_MASTER_CODE,
144 CONF_CONSIDER_UNAVAILABLE_BATTERY,
145 CONF_CONSIDER_UNAVAILABLE_MAINS,
146 CONF_CUSTOM_QUIRKS_PATH,
147 CONF_DEFAULT_LIGHT_TRANSITION,
149 CONF_ENABLE_ENHANCED_LIGHT_TRANSITION,
150 CONF_ENABLE_IDENTIFY_ON_JOIN,
151 CONF_ENABLE_LIGHT_TRANSITIONING_FLAG,
152 CONF_ENABLE_MAINS_STARTUP_POLLING,
155 CONF_GROUP_MEMBERS_ASSUME_STATE,
158 CUSTOM_CONFIGURATION,
160 DEFAULT_DATABASE_NAME,
161 DEVICE_PAIRING_STATUS,
168 from logging
import Filter, LogRecord
170 from .entity
import ZHAEntity
171 from .update
import ZHAFirmwareUpdateCoordinator
173 type _LogFilterType = Filter | Callable[[LogRecord], bool]
175 _LOGGER = logging.getLogger(__name__)
177 DEBUG_COMP_BELLOWS =
"bellows"
178 DEBUG_COMP_ZHA =
"homeassistant.components.zha"
179 DEBUG_LIB_ZHA =
"zha"
180 DEBUG_COMP_ZIGPY =
"zigpy"
181 DEBUG_COMP_ZIGPY_ZNP =
"zigpy_znp"
182 DEBUG_COMP_ZIGPY_DECONZ =
"zigpy_deconz"
183 DEBUG_COMP_ZIGPY_XBEE =
"zigpy_xbee"
184 DEBUG_COMP_ZIGPY_ZIGATE =
"zigpy_zigate"
185 DEBUG_LEVEL_CURRENT =
"current"
186 DEBUG_LEVEL_ORIGINAL =
"original"
188 DEBUG_COMP_BELLOWS: logging.DEBUG,
189 DEBUG_COMP_ZHA: logging.DEBUG,
190 DEBUG_COMP_ZIGPY: logging.DEBUG,
191 DEBUG_COMP_ZIGPY_ZNP: logging.DEBUG,
192 DEBUG_COMP_ZIGPY_DECONZ: logging.DEBUG,
193 DEBUG_COMP_ZIGPY_XBEE: logging.DEBUG,
194 DEBUG_COMP_ZIGPY_ZIGATE: logging.DEBUG,
195 DEBUG_LIB_ZHA: logging.DEBUG,
197 DEBUG_RELAY_LOGGERS = [DEBUG_COMP_ZHA, DEBUG_COMP_ZIGPY, DEBUG_LIB_ZHA]
198 ZHA_GW_MSG_LOG_ENTRY =
"log_entry"
199 ZHA_GW_MSG_LOG_OUTPUT =
"log_output"
200 SIGNAL_REMOVE_ENTITIES =
"zha_remove_entities"
201 GROUP_ENTITY_DOMAINS = [Platform.LIGHT, Platform.SWITCH, Platform.FAN]
202 SIGNAL_ADD_ENTITIES =
"zha_add_entities"
203 ENTITIES =
"entities"
205 RX_ON_WHEN_IDLE =
"rx_on_when_idle"
206 RELATIONSHIP =
"relationship"
207 EXTENDED_PAN_ID =
"extended_pan_id"
208 PERMIT_JOINING =
"permit_joining"
211 DEST_NWK =
"dest_nwk"
212 ROUTE_STATUS =
"route_status"
213 MEMORY_CONSTRAINED =
"memory_constrained"
214 MANY_TO_ONE =
"many_to_one"
215 ROUTE_RECORD_REQUIRED =
"route_record_required"
216 NEXT_HOP =
"next_hop"
218 USER_GIVEN_NAME =
"user_given_name"
219 DEVICE_REG_ID =
"device_reg_id"
223 """Reference to a group entity."""
226 original_name: str |
None
231 """Proxy class to interact with the ZHA group instances."""
233 def __init__(self, group: Group, gateway_proxy: ZHAGatewayProxy) ->
None:
234 """Initialize the gateway proxy."""
235 self.group: Group = group
236 self.gateway_proxy: ZHAGatewayProxy = gateway_proxy
240 """Return a group description for group."""
242 "name": self.group.name,
243 "group_id": self.group.group_id,
246 "endpoint_id": member.endpoint_id,
247 "device": self.gateway_proxy.device_proxies[
252 for member
in self.group.members
257 """Return the list of entities that were derived from this endpoint."""
258 entity_registry = er.async_get(self.gateway_proxy.hass)
259 entity_refs: collections.defaultdict[EUI64, list[EntityReference]] = (
260 self.gateway_proxy.ha_entity_refs
265 for entity_ref
in entity_refs.get(member.device.ieee):
266 if not entity_ref.entity_data.is_group_entity:
268 entity = entity_registry.async_get(entity_ref.ha_entity_id)
272 or entity_ref.entity_data.group_proxy
is None
273 or entity_ref.entity_data.group_proxy.group.group_id
274 != member.group.group_id
281 original_name=entity.original_name,
282 entity_id=entity_ref.ha_entity_id,
288 def log(self, level: int, msg: str, *args: Any, **kwargs) ->
None:
290 msg = f
"[%s](%s): {msg}"
292 f
"0x{self.group.group_id:04x}",
293 self.group.endpoint.endpoint_id,
296 _LOGGER.log(level, msg, *args, **kwargs)
300 """Proxy class to interact with the ZHA device instances."""
304 def __init__(self, device: Device, gateway_proxy: ZHAGatewayProxy) ->
None:
305 """Initialize the gateway proxy."""
309 self._unsubs: list[Callable[[],
None]] = []
310 self._unsubs.append(self.
devicedevice.on_all_events(self._handle_event_protocol))
314 """Return the HA device registry device id."""
319 """Set the HA device registry device id."""
324 """Return a device description for device."""
326 time_struct = time.localtime(self.
devicedevice.last_seen)
327 update_time = time.strftime(
"%Y-%m-%dT%H:%M:%S", time_struct)
330 ATTR_NWK: self.
devicedevice.nwk,
331 ATTR_MANUFACTURER: self.
devicedevice.manufacturer,
332 ATTR_MODEL: self.
devicedevice.model,
333 ATTR_NAME: self.
devicedevice.name
or ieee,
334 ATTR_QUIRK_APPLIED: self.
devicedevice.quirk_applied,
335 ATTR_QUIRK_CLASS: self.
devicedevice.quirk_class,
336 ATTR_QUIRK_ID: self.
devicedevice.quirk_id,
337 ATTR_MANUFACTURER_CODE: self.
devicedevice.manufacturer_code,
338 ATTR_POWER_SOURCE: self.
devicedevice.power_source,
339 ATTR_LQI: self.
devicedevice.lqi,
340 ATTR_RSSI: self.
devicedevice.rssi,
341 ATTR_LAST_SEEN: update_time,
342 ATTR_AVAILABLE: self.
devicedevice.available,
343 ATTR_DEVICE_TYPE: self.
devicedevice.device_type,
344 ATTR_SIGNATURE: self.
devicedevice.zigbee_signature,
349 """Get ZHA device information."""
350 device_info: dict[str, Any] = {}
352 device_info[ATTR_ACTIVE_COORDINATOR] = self.
devicedevice.is_active_coordinator
353 device_info[ENTITIES] = [
355 ATTR_ENTITY_ID: entity_ref.ha_entity_id,
356 ATTR_NAME: entity_ref.ha_device_info[ATTR_NAME],
361 topology = self.
gateway_proxygateway_proxy.gateway.application_controller.topology
362 device_info[ATTR_NEIGHBORS] = [
364 ATTR_DEVICE_TYPE: neighbor.device_type.name,
365 RX_ON_WHEN_IDLE: neighbor.rx_on_when_idle.name,
366 RELATIONSHIP: neighbor.relationship.name,
367 EXTENDED_PAN_ID:
str(neighbor.extended_pan_id),
368 ATTR_IEEE:
str(neighbor.ieee),
369 ATTR_NWK:
str(neighbor.nwk),
370 PERMIT_JOINING: neighbor.permit_joining.name,
371 DEPTH:
str(neighbor.depth),
372 ATTR_LQI:
str(neighbor.lqi),
374 for neighbor
in topology.neighbors[self.
devicedevice.ieee]
377 device_info[ATTR_ROUTES] = [
379 DEST_NWK:
str(route.DstNWK),
380 ROUTE_STATUS:
str(route.RouteStatus.name),
381 MEMORY_CONSTRAINED: bool(route.MemoryConstrained),
382 MANY_TO_ONE: bool(route.ManyToOne),
383 ROUTE_RECORD_REQUIRED: bool(route.RouteRecordRequired),
384 NEXT_HOP:
str(route.NextHop),
386 for route
in topology.routes[self.
devicedevice.ieee]
390 names: list[dict[str, str]] = []
392 ep
for epid, ep
in self.
devicedevice.device.endpoints.items()
if epid
394 profile = PROFILES.get(endpoint.profile_id)
395 if profile
and endpoint.device_type
is not None:
397 names.append({ATTR_NAME: profile.DeviceType(endpoint.device_type).name})
402 f
"unknown {endpoint.device_type} device_type "
403 f
"of 0x{(endpoint.profile_id or 0xFFFF):04x} profile id"
407 device_info[ATTR_ENDPOINT_NAMES] = names
409 device_registry = dr.async_get(self.
gateway_proxygateway_proxy.hass)
411 if reg_device
is not None:
412 device_info[USER_GIVEN_NAME] = reg_device.name_by_user
413 device_info[DEVICE_REG_ID] = reg_device.id
414 device_info[ATTR_AREA_ID] = reg_device.area_id
419 """Handle a ZHA event."""
423 ATTR_DEVICE_IEEE:
str(zha_event.device_ieee),
424 ATTR_UNIQUE_ID: zha_event.unique_id,
432 self, event: ClusterConfigureReportingEvent
434 """Handle a ZHA cluster configure reporting event."""
437 ZHA_CLUSTER_HANDLER_MSG,
439 ATTR_TYPE: ZHA_CLUSTER_HANDLER_MSG_CFG_RPT,
440 ZHA_CLUSTER_HANDLER_MSG_DATA: {
441 ATTR_CLUSTER_NAME: event.cluster_name,
442 ATTR_CLUSTER_ID: event.cluster_id,
443 ATTR_ATTRIBUTES: event.attributes,
450 self, event: ClusterHandlerConfigurationComplete
452 """Handle a ZHA cluster configure reporting event."""
455 ZHA_CLUSTER_HANDLER_MSG,
457 ATTR_TYPE: ZHA_CLUSTER_HANDLER_CFG_DONE,
463 """Handle a ZHA cluster bind event."""
466 ZHA_CLUSTER_HANDLER_MSG,
468 ATTR_TYPE: ZHA_CLUSTER_HANDLER_MSG_BIND,
469 ZHA_CLUSTER_HANDLER_MSG_DATA: {
470 ATTR_CLUSTER_NAME: event.cluster_name,
471 ATTR_CLUSTER_ID: event.cluster_id,
472 ATTR_SUCCESS: event.success,
479 """Describes an entity reference."""
482 entity_data: EntityData
483 ha_device_info: dr.DeviceInfo
484 remove_future: asyncio.Future[Any]
488 """Proxy class to interact with the ZHA gateway."""
491 self, hass: HomeAssistant, config_entry: ConfigEntry, gateway: Gateway
493 """Initialize the gateway proxy."""
498 self.device_proxies: dict[EUI64, ZHADeviceProxy] = {}
499 self.group_proxies: dict[int, ZHAGroupProxy] = {}
500 self._ha_entity_refs: collections.defaultdict[EUI64, list[EntityReference]] = (
501 collections.defaultdict(list)
503 self._log_levels: dict[str, dict[str, int]] = {
509 self._unsubs: list[Callable[[],
None]] = []
510 self._unsubs.append(self.
gatewaygateway.on_all_events(self._handle_event_protocol))
511 self.
_reload_task_reload_task: asyncio.Task |
None =
None
512 config_entry.async_on_unload(
513 self.
hasshass.bus.async_listen(
514 er.EVENT_ENTITY_REGISTRY_UPDATED,
520 def ha_entity_refs(self) -> collections.defaultdict[EUI64, list[EntityReference]]:
521 """Return entities by ieee."""
522 return self._ha_entity_refs
527 entity_data: EntityData,
528 ha_device_info: dr.DeviceInfo,
529 remove_future: asyncio.Future[Any],
531 """Record the creation of a hass entity associated with ieee."""
532 self._ha_entity_refs[entity_data.device_proxy.device.ieee].append(
534 ha_entity_id=ha_entity_id,
535 entity_data=entity_data,
536 ha_device_info=ha_device_info,
537 remove_future=remove_future,
542 self, event: Event[er.EventEntityRegistryUpdatedData]
544 """Handle when entity registry updated."""
545 entity_id = event.data[
"entity_id"]
546 entity_entry: er.RegistryEntry |
None = er.async_get(self.
hasshass).
async_get(
551 or entity_entry.config_entry_id != self.
config_entryconfig_entry.entry_id
552 or entity_entry.device_id
is None
555 device_entry: dr.DeviceEntry |
None = dr.async_get(self.
hasshass).
async_get(
556 entity_entry.device_id
562 for domain, identifier
in device_entry.identifiers
567 ieee = EUI64.convert(ieee_address)
569 assert ieee
in self.device_proxies
571 zha_device_proxy = self.device_proxies[ieee]
572 entity_key = (entity_entry.domain, entity_entry.unique_id)
573 if entity_key
not in zha_device_proxy.device.platform_entities:
575 platform_entity = zha_device_proxy.device.platform_entities[entity_key]
576 if entity_entry.disabled:
577 platform_entity.disable()
579 platform_entity.enable()
582 """Initialize devices and entities."""
583 for device
in self.
gatewaygateway.devices.values():
586 for group
in self.
gatewaygateway.groups.values():
594 """Handle a connection lost event."""
596 _LOGGER.debug(
"Connection to the radio was lost: %r", event)
600 _LOGGER.debug(
"Ignoring reset, one is already running")
604 self.
hasshass.config_entries.async_reload(self.
config_entryconfig_entry.entry_id),
609 """Handle a device joined event."""
614 ATTR_TYPE: ZHA_GW_MSG_DEVICE_JOINED,
615 ZHA_GW_MSG_DEVICE_INFO: {
616 ATTR_NWK: event.device_info.nwk,
617 ATTR_IEEE:
str(event.device_info.ieee),
618 DEVICE_PAIRING_STATUS: event.device_info.pairing_status.name,
625 """Handle a device removed event."""
626 zha_device_proxy = self.device_proxies.pop(event.device_info.ieee,
None)
627 entity_refs = self._ha_entity_refs.pop(event.device_info.ieee,
None)
628 if zha_device_proxy
is not None:
629 device_info = zha_device_proxy.zha_device_info
633 f
"{SIGNAL_REMOVE_ENTITIES}_{zha_device_proxy.device.ieee!s}",
635 self.
hasshass.async_create_task(
637 "ZHAGateway._async_remove_device",
639 if device_info
is not None:
644 ATTR_TYPE: ZHA_GW_MSG_DEVICE_REMOVED,
645 ZHA_GW_MSG_DEVICE_INFO: device_info,
651 """Handle a device left event."""
655 """Handle a raw device initialized event."""
656 manuf = event.device_info.manufacturer
661 ATTR_TYPE: ZHA_GW_MSG_RAW_INIT,
662 ZHA_GW_MSG_DEVICE_INFO: {
663 ATTR_NWK:
str(event.device_info.nwk),
664 ATTR_IEEE:
str(event.device_info.ieee),
665 DEVICE_PAIRING_STATUS: event.device_info.pairing_status.name,
667 event.device_info.model
668 if event.device_info.model
671 ATTR_MANUFACTURER: manuf
if manuf
else UNKNOWN_MANUFACTURER,
672 ATTR_SIGNATURE: event.device_info.signature,
679 """Handle a device fully initialized event."""
683 device_info = zha_device_proxy.zha_device_info
684 device_info[DEVICE_PAIRING_STATUS] = event.device_info.pairing_status.name
692 ATTR_TYPE: ZHA_GW_MSG_DEVICE_FULL_INIT,
693 ZHA_GW_MSG_DEVICE_INFO: device_info,
699 """Handle a group member removed event."""
701 zha_group_proxy.info(
"group_member_removed - group_info: %s", event.group_info)
704 zha_group_proxy, ZHA_GW_MSG_GROUP_MEMBER_REMOVED
709 """Handle a group member added event."""
711 zha_group_proxy.info(
"group_member_added - group_info: %s", event.group_info)
717 """Handle a group added event."""
719 zha_group_proxy.info(
"group_added")
725 """Handle a group removed event."""
726 zha_group_proxy = self.group_proxies.pop(event.group_info.group_id)
728 zha_group_proxy.info(
"group_removed")
733 """Enable debug mode for ZHA."""
739 self._log_relay_handler.addFilter(filterer)
741 for logger_name
in DEBUG_RELAY_LOGGERS:
742 logging.getLogger(logger_name).addHandler(self._log_relay_handler)
748 """Disable debug mode for ZHA."""
751 for logger_name
in DEBUG_RELAY_LOGGERS:
752 logging.getLogger(logger_name).removeHandler(self._log_relay_handler)
754 self._log_relay_handler.removeFilter(filterer)
758 """Shutdown the gateway proxy."""
759 for unsub
in self._unsubs:
764 """Return ZHADevice for given ieee."""
765 return self.device_proxies.
get(ieee)
768 """Return Group for given group id."""
769 if isinstance(group_id, str):
770 for group_proxy
in self.group_proxies.values():
771 if group_proxy.group.name == group_id:
774 return self.group_proxies.
get(group_id)
777 """Return entity reference for given entity_id if found."""
778 for entity_reference
in itertools.chain.from_iterable(
781 if entity_id == entity_reference.ha_entity_id:
782 return entity_reference
786 """Remove entity reference for given entity_id if found."""
792 if e.ha_entity_id != entity.entity_id
796 """Get or create a ZHA device."""
797 if (zha_device_proxy := self.device_proxies.
get(zha_device.ieee))
is None:
799 self.device_proxies[zha_device_proxy.device.ieee] = zha_device_proxy
801 device_registry = dr.async_get(self.
hasshass)
802 device_registry_device = device_registry.async_get_or_create(
804 connections={(dr.CONNECTION_ZIGBEE,
str(zha_device.ieee))},
805 identifiers={(DOMAIN,
str(zha_device.ieee))},
806 name=zha_device.name,
807 manufacturer=zha_device.manufacturer,
808 model=zha_device.model,
810 zha_device_proxy.device_id = device_registry_device.id
811 return zha_device_proxy
814 """Get or create a ZHA group."""
815 zha_group_proxy = self.group_proxies.
get(group_info.group_id)
816 if zha_group_proxy
is None:
818 self.
gatewaygateway.groups[group_info.group_id], self
820 self.group_proxies[group_info.group_id] = zha_group_proxy
821 return zha_group_proxy
824 self, proxy_object: ZHADeviceProxy | ZHAGroupProxy
826 """Create HA entity metadata."""
828 coordinator_proxy = self.device_proxies[
829 self.
gatewaygateway.coordinator_zha_device.ieee
832 if isinstance(proxy_object, ZHADeviceProxy):
833 for entity
in proxy_object.device.platform_entities.values():
834 ha_zha_data.platforms[
Platform(entity.PLATFORM)].append(
836 entity=entity, device_proxy=proxy_object, group_proxy=
None
840 for entity
in proxy_object.group.group_entities.values():
841 ha_zha_data.platforms[
Platform(entity.PLATFORM)].append(
844 device_proxy=coordinator_proxy,
845 group_proxy=proxy_object,
850 self, zha_group_proxy: ZHAGroupProxy
852 """Remove entity registry entries for group entities when the groups are removed from HA."""
854 possible_entity_unique_ids = [
855 f
"{domain}_zha_group_0x{zha_group_proxy.group.group_id:04x}"
856 for domain
in GROUP_ENTITY_DOMAINS
860 entity_registry = er.async_get(self.
hasshass)
861 assert self.
gatewaygateway.coordinator_zha_device
862 coordinator_proxy = self.device_proxies[
863 self.
gatewaygateway.coordinator_zha_device.ieee
865 all_group_entity_entries = er.async_entries_for_device(
867 coordinator_proxy.device_id,
868 include_disabled_entities=
True,
873 entries_to_remove = [
875 for entry
in all_group_entity_entries
876 if entry.unique_id
in possible_entity_unique_ids
880 for entry
in entries_to_remove:
882 "cleaning up entity registry entry for entity: %s", entry.entity_id
884 entity_registry.async_remove(entry.entity_id)
887 """Update group entities when a group event is received."""
890 f
"{SIGNAL_REMOVE_ENTITIES}_group_{group_event.group_info.group_id}",
893 self.group_proxies[group_event.group_info.group_id]
898 self, zha_group_proxy: ZHAGroupProxy, gateway_message_type: str
900 """Send the gateway event for a zigpy group event."""
905 ATTR_TYPE: gateway_message_type,
906 ZHA_GW_MSG_GROUP_INFO: zha_group_proxy.group_info,
911 self, device: ZHADeviceProxy, entity_refs: list[EntityReference] |
None
913 if entity_refs
is not None:
914 remove_tasks: list[asyncio.Future[Any]] = [
915 entity_ref.remove_future
for entity_ref
in entity_refs
918 await asyncio.wait(remove_tasks)
920 device_registry = dr.async_get(self.
hasshass)
921 reg_device = device_registry.async_get(device.device_id)
922 if reg_device
is not None:
923 device_registry.async_remove_device(reg_device.id)
928 """Capture current logger levels for ZHA."""
930 DEBUG_COMP_BELLOWS: logging.getLogger(DEBUG_COMP_BELLOWS).getEffectiveLevel(),
931 DEBUG_COMP_ZHA: logging.getLogger(DEBUG_COMP_ZHA).getEffectiveLevel(),
932 DEBUG_COMP_ZIGPY: logging.getLogger(DEBUG_COMP_ZIGPY).getEffectiveLevel(),
933 DEBUG_COMP_ZIGPY_ZNP: logging.getLogger(
935 ).getEffectiveLevel(),
936 DEBUG_COMP_ZIGPY_DECONZ: logging.getLogger(
937 DEBUG_COMP_ZIGPY_DECONZ
938 ).getEffectiveLevel(),
939 DEBUG_COMP_ZIGPY_XBEE: logging.getLogger(
940 DEBUG_COMP_ZIGPY_XBEE
941 ).getEffectiveLevel(),
942 DEBUG_COMP_ZIGPY_ZIGATE: logging.getLogger(
943 DEBUG_COMP_ZIGPY_ZIGATE
944 ).getEffectiveLevel(),
945 DEBUG_LIB_ZHA: logging.getLogger(DEBUG_LIB_ZHA).getEffectiveLevel(),
951 """Set logger levels for ZHA."""
952 logging.getLogger(DEBUG_COMP_BELLOWS).setLevel(levels[DEBUG_COMP_BELLOWS])
953 logging.getLogger(DEBUG_COMP_ZHA).setLevel(levels[DEBUG_COMP_ZHA])
954 logging.getLogger(DEBUG_COMP_ZIGPY).setLevel(levels[DEBUG_COMP_ZIGPY])
955 logging.getLogger(DEBUG_COMP_ZIGPY_ZNP).setLevel(levels[DEBUG_COMP_ZIGPY_ZNP])
956 logging.getLogger(DEBUG_COMP_ZIGPY_DECONZ).setLevel(levels[DEBUG_COMP_ZIGPY_DECONZ])
957 logging.getLogger(DEBUG_COMP_ZIGPY_XBEE).setLevel(levels[DEBUG_COMP_ZIGPY_XBEE])
958 logging.getLogger(DEBUG_COMP_ZIGPY_ZIGATE).setLevel(levels[DEBUG_COMP_ZIGPY_ZIGATE])
959 logging.getLogger(DEBUG_LIB_ZHA).setLevel(levels[DEBUG_LIB_ZHA])
963 """Log handler for error messages."""
965 def __init__(self, hass: HomeAssistant, gateway: ZHAGatewayProxy) ->
None:
966 """Initialize a new LogErrorHandler."""
970 hass_path: str = HOMEASSISTANT_PATH[0]
971 config_dir = self.
hasshass.config.config_dir
973 rf
"(?:{re.escape(hass_path)}|{re.escape(config_dir)})/(.*)"
976 def emit(self, record: LogRecord) ->
None:
977 """Relay log message via dispatcher."""
979 record, self.
paths_repaths_re, figure_out_source=record.levelno >= logging.WARNING
984 {ATTR_TYPE: ZHA_GW_MSG_LOG_OUTPUT, ZHA_GW_MSG_LOG_ENTRY: entry.to_dict()},
988 @dataclasses.dataclass(kw_only=True, slots=True)
990 """ZHA data stored in `hass.data`."""
992 yaml_config: ConfigType = dataclasses.field(default_factory=dict)
993 config_entry: ConfigEntry |
None = dataclasses.field(default=
None)
994 device_trigger_cache: dict[str, tuple[str, dict]] = dataclasses.field(
997 gateway_proxy: ZHAGatewayProxy |
None = dataclasses.field(default=
None)
998 platforms: collections.defaultdict[Platform, list] = dataclasses.field(
999 default_factory=
lambda: collections.defaultdict(list)
1001 update_coordinator: ZHAFirmwareUpdateCoordinator |
None = dataclasses.field(
1006 @dataclasses.dataclass(kw_only=True, slots=True)
1008 """ZHA entity data."""
1010 entity: PlatformEntity | GroupEntity
1011 device_proxy: ZHADeviceProxy
1012 group_proxy: ZHAGroupProxy |
None = dataclasses.field(default=
None)
1016 """Return if this is a group entity."""
1017 return self.group_proxy
is not None and isinstance(self.entity, GroupEntity)
1021 """Get the global ZHA data object."""
1022 if DATA_ZHA
not in hass.data:
1025 return hass.data[DATA_ZHA]
1029 """Get the ZHA gateway object."""
1030 if (gateway_proxy :=
get_zha_data(hass).gateway_proxy)
is None:
1031 raise ValueError(
"No gateway object exists")
1033 return gateway_proxy.gateway
1037 """Get the ZHA gateway object."""
1038 if (gateway_proxy :=
get_zha_data(hass).gateway_proxy)
is None:
1039 raise ValueError(
"No gateway object exists")
1041 return gateway_proxy
1045 """Get the ZHA gateway object."""
1046 if (gateway_proxy :=
get_zha_data(hass).gateway_proxy)
is None:
1047 raise ValueError(
"No gateway object exists to retrieve the config entry from.")
1049 return gateway_proxy.config_entry
1054 """Get a ZHA device for the given device registry id."""
1055 device_registry = dr.async_get(hass)
1056 registry_device = device_registry.async_get(device_id)
1057 if not registry_device:
1058 _LOGGER.error(
"Device id `%s` not found in registry", device_id)
1059 raise KeyError(f
"Device id `{device_id}` not found in registry.")
1061 ieee_address = next(
1063 for domain, identifier
in registry_device.identifiers
1066 ieee = EUI64.convert(ieee_address)
1067 return zha_gateway_proxy.device_proxies[ieee]
1071 """Convert a cluster command schema to a voluptuous schema."""
1075 vol.Optional(field.name)
if field.optional
else vol.Required(field.name)
1077 for field
in schema.fields
1083 """Convert a schema type to a voluptuous type."""
1084 if issubclass(field_type, enum.Flag)
and field_type.__members__:
1085 return cv.multi_select(
1086 [key.replace(
"_",
" ")
for key
in field_type.__members__]
1088 if issubclass(field_type, enum.Enum)
and field_type.__members__:
1089 return vol.In([key.replace(
"_",
" ")
for key
in field_type.__members__])
1091 issubclass(field_type, zigpy.types.FixedIntType)
1092 or issubclass(field_type, enum.Flag)
1093 or issubclass(field_type, enum.Enum)
1096 vol.Coerce(int), vol.Range(field_type.min_value, field_type.max_value)
1102 fields: dict[str, Any], schema: CommandSchema
1103 ) -> dict[str, Any]:
1104 """Convert user input to ZCL values."""
1105 converted_fields: dict[str, Any] = {}
1106 for field
in schema.fields:
1107 if field.name
not in fields:
1109 value = fields[field.name]
1110 if issubclass(field.type, enum.Flag)
and isinstance(value, list):
1114 if isinstance(flag, str):
1115 new_value |= field.type[flag.replace(
" ",
"_")]
1119 value = field.type(new_value)
1120 elif issubclass(field.type, enum.Enum):
1122 field.type[value.replace(
" ",
"_")]
1123 if isinstance(value, str)
1124 else field.type(value)
1127 value = field.type(value)
1129 "Converted ZCL schema field(%s) value from: %s to: %s",
1134 converted_fields[field.name] = value
1135 return converted_fields
1139 """Determine if a device containing the specified in cluster is paired."""
1141 zha_devices = zha_gateway.devices.values()
1142 for zha_device
in zha_devices:
1143 if skip_coordinator
and zha_device.is_coordinator:
1145 clusters_by_endpoint = zha_device.async_get_clusters()
1146 for clusters
in clusters_by_endpoint.values():
1148 cluster_id
in clusters[CLUSTER_TYPE_IN]
1149 or cluster_id
in clusters[CLUSTER_TYPE_OUT]
1157 _async_add_entities: AddEntitiesCallback,
1158 entity_class: type[ZHAEntity],
1159 entities: list[EntityData],
1162 """Add entities helper."""
1166 entities_to_add: list[ZHAEntity] = []
1167 for entity_data
in entities:
1169 entities_to_add.append(entity_class(entity_data))
1175 "Error while adding entity from entity data: %s", entity_data
1178 for entity
in entities_to_add:
1179 if not entity.enabled:
1180 entity.entity_data.entity.disable()
1185 """Clean the serial port path, applying corrections where necessary."""
1187 if path.startswith(
"socket://"):
1191 if re.match(
r"^socket://\[\d+\.\d+\.\d+\.\d+\]:\d+$", path):
1192 path = path.replace(
"[",
"").replace(
"]",
"")
1197 CONF_ZHA_OPTIONS_SCHEMA = vol.Schema(
1199 vol.Optional(CONF_DEFAULT_LIGHT_TRANSITION, default=0): vol.All(
1200 vol.Coerce(float), vol.Range(min=0, max=2**16 / 10)
1202 vol.Required(CONF_ENABLE_ENHANCED_LIGHT_TRANSITION, default=
False): cv.boolean,
1203 vol.Required(CONF_ENABLE_LIGHT_TRANSITIONING_FLAG, default=
True): cv.boolean,
1204 vol.Required(CONF_GROUP_MEMBERS_ASSUME_STATE, default=
True): cv.boolean,
1205 vol.Required(CONF_ENABLE_IDENTIFY_ON_JOIN, default=
True): cv.boolean,
1207 CONF_CONSIDER_UNAVAILABLE_MAINS,
1208 default=CONF_DEFAULT_CONSIDER_UNAVAILABLE_MAINS,
1211 CONF_CONSIDER_UNAVAILABLE_BATTERY,
1212 default=CONF_DEFAULT_CONSIDER_UNAVAILABLE_BATTERY,
1214 vol.Required(CONF_ENABLE_MAINS_STARTUP_POLLING, default=
True): cv.boolean,
1216 extra=vol.REMOVE_EXTRA,
1219 CONF_ZHA_ALARM_SCHEMA = vol.Schema(
1221 vol.Required(CONF_ALARM_MASTER_CODE, default=
"1234"): cv.string,
1222 vol.Required(CONF_ALARM_FAILED_TRIES, default=3): cv.positive_int,
1223 vol.Required(CONF_ALARM_ARM_REQUIRES_CODE, default=
False): cv.boolean,
1229 """Create ZHA lib configuration from HA config objects."""
1232 assert ha_zha_data.config_entry
is not None
1233 assert ha_zha_data.yaml_config
is not None
1237 path = ha_zha_data.config_entry.data[CONF_DEVICE][CONF_DEVICE_PATH]
1240 if path != cleaned_path:
1241 _LOGGER.debug(
"Cleaned serial port path %r -> %r", path, cleaned_path)
1242 ha_zha_data.config_entry.data[CONF_DEVICE][CONF_DEVICE_PATH] = cleaned_path
1243 hass.config_entries.async_update_entry(
1244 ha_zha_data.config_entry, data=ha_zha_data.config_entry.data
1249 app_config = copy.deepcopy(ha_zha_data.yaml_config.get(CONF_ZIGPY, {}))
1250 database = ha_zha_data.yaml_config.get(
1252 hass.config.path(DEFAULT_DATABASE_NAME),
1254 app_config[CONF_DATABASE] = database
1255 app_config[CONF_DEVICE] = ha_zha_data.config_entry.data[CONF_DEVICE]
1257 radio_type = RadioType[ha_zha_data.config_entry.data[CONF_RADIO_TYPE]]
1263 and app_config.get(CONF_NWK, {}).
get(CONF_NWK_CHANNEL)
is None
1265 app_config.setdefault(CONF_NWK, {})[CONF_NWK_CHANNEL] = 15
1267 options: MappingProxyType[str, Any] = ha_zha_data.config_entry.options.get(
1268 CUSTOM_CONFIGURATION, {}
1272 light_options: LightOptions = LightOptions(
1273 default_light_transition=zha_options.get(CONF_DEFAULT_LIGHT_TRANSITION),
1274 enable_enhanced_light_transition=zha_options.get(
1275 CONF_ENABLE_ENHANCED_LIGHT_TRANSITION
1277 enable_light_transitioning_flag=zha_options.get(
1278 CONF_ENABLE_LIGHT_TRANSITIONING_FLAG
1280 group_members_assume_state=zha_options.get(CONF_GROUP_MEMBERS_ASSUME_STATE),
1282 device_options: DeviceOptions = DeviceOptions(
1283 enable_identify_on_join=zha_options.get(CONF_ENABLE_IDENTIFY_ON_JOIN),
1284 consider_unavailable_mains=zha_options.get(CONF_CONSIDER_UNAVAILABLE_MAINS),
1285 consider_unavailable_battery=zha_options.get(CONF_CONSIDER_UNAVAILABLE_BATTERY),
1286 enable_mains_startup_polling=zha_options.get(CONF_ENABLE_MAINS_STARTUP_POLLING),
1288 acp_options: AlarmControlPanelOptions = AlarmControlPanelOptions(
1289 master_code=ha_acp_options.get(CONF_ALARM_MASTER_CODE),
1290 failed_tries=ha_acp_options.get(CONF_ALARM_FAILED_TRIES),
1291 arm_requires_code=ha_acp_options.get(CONF_ALARM_ARM_REQUIRES_CODE),
1293 coord_config: CoordinatorConfiguration = CoordinatorConfiguration(
1294 path=app_config[CONF_DEVICE][CONF_DEVICE_PATH],
1295 baudrate=app_config[CONF_DEVICE][CONF_BAUDRATE],
1296 flow_control=app_config[CONF_DEVICE][CONF_FLOW_CONTROL],
1297 radio_type=radio_type.name,
1299 quirks_config: QuirksConfiguration = QuirksConfiguration(
1300 enabled=ha_zha_data.yaml_config.get(CONF_ENABLE_QUIRKS,
True),
1301 custom_quirks_path=ha_zha_data.yaml_config.get(CONF_CUSTOM_QUIRKS_PATH),
1303 overrides_config: dict[str, DeviceOverridesConfiguration] = {}
1304 overrides: dict[str, dict[str, Any]] = cast(
1305 dict[str, dict[str, Any]], ha_zha_data.yaml_config.get(CONF_DEVICE_CONFIG)
1307 if overrides
is not None:
1308 for unique_id, override
in overrides.items():
1309 overrides_config[unique_id] = DeviceOverridesConfiguration(
1310 type=override[
"type"],
1314 zigpy_config=app_config,
1315 config=ZHAConfiguration(
1316 light_options=light_options,
1317 device_options=device_options,
1318 alarm_control_panel_options=acp_options,
1319 coordinator_configuration=coord_config,
1320 quirks_configuration=quirks_config,
1321 device_overrides=overrides_config,
1323 local_timezone=ZoneInfo(hass.config.time_zone),
1327 def convert_zha_error_to_ha_error[**_P, _EntityT: ZHAEntity](
1328 func: Callable[Concatenate[_EntityT, _P], Awaitable[
None]],
1329 ) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any,
None]]:
1330 """Decorate ZHA commands and re-raises ZHAException as HomeAssistantError."""
1332 @functools.wraps(func)
1333 async
def handler(self: _EntityT, *args: _P.args, **kwargs: _P.kwargs) ->
None:
1335 return await func(self, *args, **kwargs)
1336 except ZHAException
as err:
1343 """Return a new dictionary excluding keys with None values."""
1344 return {k: v
for k, v
in obj.items()
if v
is not None}
bool is_group_entity(self)
None __init__(self, HomeAssistant hass, ZHAGatewayProxy gateway)
None emit(self, LogRecord record)
None handle_zha_channel_cfg_done(self, ClusterHandlerConfigurationComplete event)
None __init__(self, Device device, ZHAGatewayProxy gateway_proxy)
None handle_zha_event(self, ZHAEvent zha_event)
dict[str, Any] device_info(self)
None handle_zha_channel_bind(self, ClusterBindEvent event)
None device_id(self, str device_id)
dict[str, Any] zha_device_info(self)
None handle_zha_channel_configure_reporting(self, ClusterConfigureReportingEvent event)
EntityReference|None get_entity_reference(self, str entity_id)
None _cleanup_group_entity_registry_entries(self, ZHAGroupProxy zha_group_proxy)
None async_enable_debug_mode(self, _LogFilterType|None filterer=None)
None handle_group_member_added(self, GroupEvent event)
None handle_group_removed(self, GroupEvent event)
None _handle_entity_registry_updated(self, Event[er.EventEntityRegistryUpdatedData] event)
None _update_group_entities(self, GroupEvent group_event)
None handle_device_removed(self, DeviceRemovedEvent event)
collections.defaultdict[EUI64, list[EntityReference]] ha_entity_refs(self)
ZHAGroupProxy _async_get_or_create_group_proxy(self, GroupInfo group_info)
None handle_raw_device_initialized(self, RawDeviceInitializedEvent event)
None handle_group_member_removed(self, GroupEvent event)
None handle_device_fully_initialized(self, DeviceFullInitEvent event)
None async_disable_debug_mode(self, _LogFilterType|None filterer=None)
ZHADeviceProxy _async_get_or_create_device_proxy(self, Device zha_device)
None handle_device_left(self, DeviceLeftEvent event)
None async_initialize_devices_and_entities(self)
None _create_entity_metadata(self, ZHADeviceProxy|ZHAGroupProxy proxy_object)
None handle_connection_lost(self, ConnectionLostEvent event)
None register_entity_reference(self, str ha_entity_id, EntityData entity_data, dr.DeviceInfo ha_device_info, asyncio.Future[Any] remove_future)
None remove_entity_reference(self, ZHAEntity entity)
None handle_group_added(self, GroupEvent event)
None _async_remove_device(self, ZHADeviceProxy device, list[EntityReference]|None entity_refs)
ZHADeviceProxy|None get_device_proxy(self, EUI64 ieee)
ZHAGroupProxy|None get_group_proxy(self, int|str group_id)
None handle_device_joined(self, DeviceJoinedEvent event)
None __init__(self, HomeAssistant hass, ConfigEntry config_entry, Gateway gateway)
None _send_group_gateway_message(self, ZHAGroupProxy zha_group_proxy, str gateway_message_type)
None __init__(self, Group group, ZHAGatewayProxy gateway_proxy)
None log(self, int level, str msg, *Any args, **kwargs)
list[GroupEntityReference] associated_entities(self, GroupMember member)
dict[str, Any] group_info(self)
web.Response get(self, web.Request request, str config_key)
None _async_add_entities(AvmWrapper avm_wrapper, AddEntitiesCallback async_add_entities, FritzData data_fritz)
bool is_multiprotocol_url(str url)
DeviceModel|None get_device(int device_id, list[DeviceModel] devices)
str _clean_serial_port_path(str path)
Gateway get_zha_gateway(HomeAssistant hass)
None async_set_logger_levels(dict[str, int] levels)
vol.Schema cluster_command_schema_to_vol_schema(CommandSchema schema)
dict[str, int] async_capture_log_levels()
Any schema_type_to_vol(Any field_type)
dict[str, Any] convert_to_zcl_values(dict[str, Any] fields, CommandSchema schema)
ZHAData create_zha_config(HomeAssistant hass, HAZHAData ha_zha_data)
ZHAGatewayProxy get_zha_gateway_proxy(HomeAssistant hass)
ZHADeviceProxy async_get_zha_device_proxy(HomeAssistant hass, str device_id)
ConfigEntry get_config_entry(HomeAssistant hass)
None async_add_entities(AddEntitiesCallback _async_add_entities, type[ZHAEntity] entity_class, list[EntityData] entities, **kwargs)
HAZHAData get_zha_data(HomeAssistant hass)
def async_cluster_exists(HomeAssistant hass, cluster_id, skip_coordinator=True)
dict[str, Any] exclude_none_values(Mapping[str, Any] obj)
AreaRegistry async_get(HomeAssistant hass)
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)