From e667cb557756da095b58300633dcf826cf0086c6 Mon Sep 17 00:00:00 2001 From: acdu1 Date: Thu, 2 Apr 2026 19:03:21 +0000 Subject: [PATCH 1/2] Change device trigger to use the new event listener and add support for more events. --- .../dirigera_platform/device_trigger.py | 66 ++++++++++++++----- .../dirigera_platform/hub_event_listener.py | 44 +++++++++++++ 2 files changed, 95 insertions(+), 15 deletions(-) diff --git a/custom_components/dirigera_platform/device_trigger.py b/custom_components/dirigera_platform/device_trigger.py index 404aaa1..0027dcc 100644 --- a/custom_components/dirigera_platform/device_trigger.py +++ b/custom_components/dirigera_platform/device_trigger.py @@ -10,6 +10,7 @@ from homeassistant.const import CONF_TYPE, CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, ATTR_ENTITY_ID from homeassistant.components.homeassistant.triggers import event as event_trigger +from homeassistant.components.homeassistant.triggers import state as state_trigger from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA from .const import DOMAIN @@ -17,7 +18,8 @@ logger = logging.getLogger("custom_components.dirigera_platform") -TRIGGER_TYPES = ["single_click", "long_press","double_click"] +CONTROLLER_TRIGGER_TYPES = ["single_click", "long_press", "double_click"] +LIGHT_TRIGGER_TYPES = ["turned_on", "turned_off"] TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend({vol.Required(CONF_TYPE): cv.string, vol.Required(ATTR_ENTITY_ID): cv.string}) async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict[str, Any]]: @@ -53,37 +55,57 @@ async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict[s logger.debug(f"Found controller to tag events to entity : {entity_name}") + entity_domain = entity_name.split('.', 1)[0] + if entity_domain == "light": + for trigger_type in LIGHT_TRIGGER_TYPES: + triggers.append( + { + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_PLATFORM: "device", + CONF_TYPE: trigger_type, + ATTR_ENTITY_ID: entity_name, + } + ) + break + # Now we have an ikea_controller # Check if entity_id has _X suffix (like "xxx_1") - this pattern must match hub_event_listener.py # If it matches, we ALWAYS use buttonX_ prefix to be consistent with event firing pattern = r'(([0-9]|[a-z]|-)*)_([0-9])+' match = re.match(pattern, entity_id) - use_prefix : bool = False + use_prefix: bool = False if match: # Device ID has _X suffix - hub_event_listener will add buttonX_ prefix logger.debug(f"Entity ID {entity_id} matches multi-button pattern, will use prefix") use_prefix = True - elif registry_entity.number_of_buttons > 1: + + if not hasattr(registry_entity, "number_of_buttons"): + logger.debug(f"Entity {entity_id} is not a controller and has no button triggers") + break + + if registry_entity.number_of_buttons > 1: # Multiple buttons without _X suffix - also use prefix logger.debug("More than one button will use prefix") use_prefix = True for btn_idx in range(registry_entity.number_of_buttons): - for trigger_type in TRIGGER_TYPES: + for trigger_type in CONTROLLER_TRIGGER_TYPES: if use_prefix: trigger_name = f"button{btn_idx+1}_{trigger_type}" else: trigger_name = trigger_type - + triggers.append( - { - CONF_DEVICE_ID: device_id, - CONF_DOMAIN: DOMAIN, - CONF_PLATFORM: "device", - CONF_TYPE: trigger_name, - ATTR_ENTITY_ID: entity_name - }) + { + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_PLATFORM: "device", + CONF_TYPE: trigger_name, + ATTR_ENTITY_ID: entity_name, + } + ) break @@ -92,7 +114,21 @@ async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict[s async def async_attach_trigger(hass, config, action, trigger_info): logger.debug(f"Got to async_attach_trigger config: {config}, action: {action}, trigger_info: {trigger_info}") - + + if config[CONF_TYPE] in LIGHT_TRIGGER_TYPES: + # Attach a HA state trigger for light on/off events + state_config = state_trigger.TRIGGER_SCHEMA( + { + state_trigger.CONF_PLATFORM: "state", + state_trigger.CONF_ENTITY_ID: config[ATTR_ENTITY_ID], + state_trigger.CONF_TO: "on" if config[CONF_TYPE] == "turned_on" else "off", + } + ) + return await state_trigger.async_attach_trigger( + hass, state_config, action, trigger_info, platform_type="device" + ) + + # Controller triggers (button presses) use the custom event bus path event_config = event_trigger.TRIGGER_SCHEMA( { event_trigger.CONF_PLATFORM: "event", @@ -100,11 +136,11 @@ async def async_attach_trigger(hass, config, action, trigger_info): event_trigger.CONF_EVENT_DATA: { CONF_DEVICE_ID: config[CONF_DEVICE_ID], CONF_TYPE: config[CONF_TYPE], - ATTR_ENTITY_ID: config[ATTR_ENTITY_ID] + ATTR_ENTITY_ID: config[ATTR_ENTITY_ID], }, } ) - + return await event_trigger.async_attach_trigger( hass, event_config, action, trigger_info, platform_type="device" ) \ No newline at end of file diff --git a/custom_components/dirigera_platform/hub_event_listener.py b/custom_components/dirigera_platform/hub_event_listener.py index 7b5b94f..177bbb6 100644 --- a/custom_components/dirigera_platform/hub_event_listener.py +++ b/custom_components/dirigera_platform/hub_event_listener.py @@ -174,6 +174,15 @@ async def sync_all_device_areas(self): def on_error(self, ws:Any, ws_msg:str): logger.debug(f"on_error hub event listener {ws_msg}") + def _fire_dirigera_event(self, device_id: str, entity_id: str, trigger_type: str): + event_data = { + "type": trigger_type, + "device_id": device_id, + ATTR_ENTITY_ID: entity_id, + } + self._hass.bus.fire(event_type="dirigera_platform_event", event_data=event_data) + logger.debug(f"dirigera_platform_event fired: {event_data}") + def parse_scene_update(self, msg): global controller_trigger_last_time_map # Verify that this is controller initiated @@ -310,10 +319,14 @@ def _apply_scene_actions(self, msg): continue entity = registry_value.entity + old_is_on = None + if hasattr(entity._json_data.attributes, "is_on"): + old_is_on = entity._json_data.attributes.is_on # Only process light-relevant attributes from scene actions light_attrs = ["isOn", "lightLevel", "colorTemperature", "colorHue", "colorSaturation"] updated = False + scene_on_off_event = None for key in attributes: if key not in light_attrs: @@ -323,6 +336,8 @@ def _apply_scene_actions(self, msg): logger.debug(f"Scene action: setting {key_attr} to {attributes[key]} on {device_id}") setattr(entity._json_data.attributes, key_attr, attributes[key]) updated = True + if key == "isOn" and old_is_on is not None and old_is_on != attributes[key]: + scene_on_off_event = "turned_on" if attributes[key] else "turned_off" except Exception as ex: logger.warning(f"Scene action: failed to set {key} on {device_id}: {ex}") @@ -339,6 +354,11 @@ def _apply_scene_actions(self, msg): try: entity.schedule_update_ha_state() logger.debug(f"Scene action: scheduled HA state update for {device_id}") + if scene_on_off_event is not None: + try: + self._fire_dirigera_event(device_id, entity.registry_entry.entity_id, scene_on_off_event) + except Exception: + pass except Exception as ex: logger.warning(f"Scene action: failed to schedule update for {device_id}: {ex}") @@ -505,6 +525,13 @@ def on_message(self, ws:Any, ws_msg:str): registry_value = hub_event_listener.get_registry_entry(id) entity = registry_value.entity + old_is_on = None + if device_type == "light": + try: + old_is_on = entity._json_data.attributes.is_on + except Exception: + old_is_on = None + reachability_changed = False if "isReachable" in info: try: @@ -637,6 +664,23 @@ def on_message(self, ws:Any, ws_msg:str): logger.debug(f"Cascading to cascade entity : {registry_value.cascade_entity.unique_id}") registry_value.cascade_entity.schedule_update_ha_state(False) + # Fire platform event for light on/off state changes to support device triggers + if device_type == "light" and has_attributes and "attributes" in info and "isOn" in info["attributes"]: + new_is_on = info["attributes"]["isOn"] + if old_is_on is not None and new_is_on != old_is_on: + trigger_type = "turned_on" if new_is_on else "turned_off" + try: + entity_id = entity.registry_entry.entity_id + except Exception: + entity_id = None + self._fire_dirigera_event(id, entity_id or "", trigger_type) + if registry_value.cascade_entity is not None: + try: + cascade_entity_id = registry_value.cascade_entity.registry_entry.entity_id + except Exception: + cascade_entity_id = None + self._fire_dirigera_event(registry_value.cascade_entity.unique_id, cascade_entity_id or "", trigger_type) + except Exception as ex: # Temp solution to not log entries From 3a03e34caa6fb15c2a4fb9d68c126a4f8b6102d0 Mon Sep 17 00:00:00 2001 From: acdu1 Date: Thu, 2 Apr 2026 19:16:28 +0000 Subject: [PATCH 2/2] Update manifest.json --- custom_components/dirigera_platform/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/dirigera_platform/manifest.json b/custom_components/dirigera_platform/manifest.json index 124471c..23d8cb0 100755 --- a/custom_components/dirigera_platform/manifest.json +++ b/custom_components/dirigera_platform/manifest.json @@ -9,5 +9,5 @@ "issue_tracker": "https://github.com/nrbrt/dirigera_platform/issues", "loggers": ["custom_components.dirigera_platform"], "requirements": ["dirigera==1.2.6"], - "version": "0.0.1" + "version": "0.2.5" }