Compare commits

..

1 Commits

View File

@@ -1,4 +1,6 @@
import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.hass.hassapi as hass
import urllib.request
import json
# ============================================================================= # =============================================================================
# EventDispatcher / EventHandler — HA event subscription helpers # EventDispatcher / EventHandler — HA event subscription helpers
@@ -13,6 +15,11 @@ import appdaemon.plugins.hass.hassapi as hass
# event_data : dict of key/value pairs that must all match the event # event_data : dict of key/value pairs that must all match the event
# payload (deep partial match — nested dicts are matched # payload (deep partial match — nested dicts are matched
# recursively). None means "match any payload". # recursively). None means "match any payload".
# Special key: ``device_name`` — for zha_event payloads you
# may supply the friendly ZHA device name instead of the raw
# IEEE address. It is resolved once at construction time via
# the ``zha/get_devices`` service and stored as
# ``device_ieee`` in the effective filter dict.
# reset_data : optional dict. When set, the dispatcher becomes a # reset_data : optional dict. When set, the dispatcher becomes a
# one-shot latch: the callback fires once on event_data # one-shot latch: the callback fires once on event_data
# match, then waits for a reset_data match before it can # match, then waits for a reset_data match before it can
@@ -51,13 +58,15 @@ import appdaemon.plugins.hass.hassapi as hass
class EventDispatcher: class EventDispatcher:
def __init__(self,ad_api,event_name,callback,event_data,reset_data,event_context): def __init__(self,ad_api,event_name,callback,event_data,reset_data,event_context):
self.ad_api = ad_api
event_data = self._resolve_zha_device_name(event_data)
reset_data = self._resolve_zha_device_name(reset_data)
self.event_name = event_name self.event_name = event_name
self.callback = callback self.callback = callback
self.event_data = event_data self.event_data = event_data
self.reset_data = reset_data self.reset_data = reset_data
self.waiting_for_reset = False self.waiting_for_reset = False
self.event_context = event_context self.event_context = event_context
self.ad_api = ad_api
if event_data == None: if event_data == None:
self.ad_api.listen_event(self.on_event,event_name) self.ad_api.listen_event(self.on_event,event_name)
else: else:
@@ -78,6 +87,59 @@ class EventDispatcher:
def on_event(self, event_name, data, kwargs): def on_event(self, event_name, data, kwargs):
self.process_event(data) self.process_event(data)
def _lookup_zha_ieee(self, device_name):
# Resolve a ZHA device friendly name to its IEEE address by rendering
# a Jinja2 template via the HA REST API (/api/template).
# Retrieves the HA URL and token from the HASS plugin config object.
try:
plugin = self.ad_api.AD.plugins.get_plugin_object('default')
ha_url = str(plugin.config.ha_url).rstrip('/')
token = plugin.config.token.get_secret_value()
template = (
"{% set ns = namespace(ieee='') %}"
"{% for eid in integration_entities('zha') %}"
" {% set did = device_id(eid) %}"
" {% if did and not ns.ieee %}"
" {% if device_attr(did, 'name_by_user') == '" + device_name + "'"
" or device_attr(did, 'name') == '" + device_name + "' %}"
" {% for conn in device_attr(did, 'connections') %}"
" {% if conn[0] == 'zigbee' %}{% set ns.ieee = conn[1] %}{% endif %}"
" {% endfor %}"
" {% endif %}"
" {% endif %}"
"{% endfor %}"
"{{ ns.ieee }}"
)
payload = json.dumps({'template': template}).encode('utf-8')
req = urllib.request.Request(
f"{ha_url}/api/template",
data=payload,
headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'},
method='POST',
)
with urllib.request.urlopen(req, timeout=10) as resp:
result = resp.read().decode('utf-8').strip()
return result if result else None
except Exception as e:
self.ad_api.log_error(f"[EventDispatcher] Failed to resolve ZHA device name '{device_name}': {e}")
return None
def _resolve_zha_device_name(self, data):
# Replace a device_name key in data with device_ieee looked up from
# the ZHA device registry. Returns data unchanged when the key is
# absent or data is None.
if data is None or 'device_name' not in data:
return data
name = data['device_name']
ieee = self._lookup_zha_ieee(name)
resolved = {k: v for k, v in data.items() if k != 'device_name'}
if ieee is not None:
self.ad_api.log_info(f"[EventDispatcher] Resolved ZHA device name '{name}' to IEEE '{ieee}'")
resolved['device_ieee'] = ieee
else:
self.ad_api.log_error(f"[EventDispatcher] ZHA device '{name}' not found — 'device_name' filter ignored")
return resolved
def process_event(self,data): def process_event(self,data):
def are_data_matching(ref_data, data): def are_data_matching(ref_data, data):
if ref_data != None: if ref_data != None:
@@ -119,10 +181,7 @@ class EventHandler:
for event_block in events_block.values(): for event_block in events_block.values():
register_event_with_params(event_block,callback,event_context) register_event_with_params(event_block,callback,event_context)
def log(self,message,**kwargs):
self.__ad_api.log(message,**kwargs)
def add_dispatcher(self,event_name,callback,event_data = None,reset_data = None ,event_context = None): def add_dispatcher(self,event_name,callback,event_data = None,reset_data = None ,event_context = None):
self.log(f'Registering dispatcher {callback.__name__} for event "{event_name}" ({event_data})') self.__ad_api.log_info(f'Registering dispatcher {callback.__name__} for event "{event_name}" ({event_data})')
dispatcher = EventDispatcher(self.__ad_api,event_name,callback,event_data,reset_data,event_context) dispatcher = EventDispatcher(self.__ad_api,event_name,callback,event_data,reset_data,event_context)
self.event_dispatchers.append(dispatcher) self.event_dispatchers.append(dispatcher)