diff --git a/README.md b/README.md index d95e68b..2ab2f4c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ ## IKEA Dirigera Hub Integration -This is an actively maintained fork of [sanjoyg/dirigera_platform](https://github.com/sanjoyg/dirigera_platform), which appears to be abandoned (last activity March 2025). This integration connects Home Assistant with the IKEA Dirigera hub, built on the [dirigera](https://github.com/Leggin/dirigera) Python library by Nicolas Hilberg. +This is a fork of [sanjoyg/dirigera_platform](https://github.com/sanjoyg/dirigera_platform), which was stagnant for a while, but the upstream maintainer resumed working on it, so I recommend to switch back to the upstream repository by SanjoyG. I will keep this respository up and may or may not continue working on it separately from the upstream repo and will post PR requests upstream when I add or improve things. + +This integration connects Home Assistant with the IKEA Dirigera hub, built on the [dirigera](https://github.com/Leggin/dirigera) Python library by Nicolas Hilberg. This fork addresses most of the outstanding issues from the upstream repository. Contributions are welcome — feel free to open [issues](https://github.com/nrbrt/dirigera_platform/issues) or submit pull requests. diff --git a/custom_components/dirigera_platform/__init__.py b/custom_components/dirigera_platform/__init__.py index 8a89a5e..9950caa 100644 --- a/custom_components/dirigera_platform/__init__.py +++ b/custom_components/dirigera_platform/__init__.py @@ -12,6 +12,7 @@ import voluptuous as vol from homeassistant import config_entries, core +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.components.light import PLATFORM_SCHEMA from homeassistant.const import CONF_IP_ADDRESS, CONF_TOKEN, Platform @@ -195,7 +196,12 @@ async def async_setup_entry( platform = ikea_gateway() hass.data[DOMAIN][PLATFORM] = platform logger.debug("Starting make_devices...") - await platform.make_devices(hass,hass_data[CONF_IP_ADDRESS], hass_data[CONF_TOKEN]) + try: + await platform.make_devices(hass,hass_data[CONF_IP_ADDRESS], hass_data[CONF_TOKEN]) + except (ConnectionError, OSError) as err: + raise ConfigEntryNotReady( + f"Cannot connect to IKEA Dirigera hub at {hass_data[CONF_IP_ADDRESS]}: {err}" + ) from err #await hass.async_add_executor_job(platform.make_devices,hass, hass_data[CONF_IP_ADDRESS], hass_data[CONF_TOKEN]) diff --git a/custom_components/dirigera_platform/base_classes.py b/custom_components/dirigera_platform/base_classes.py index 9a408b5..80e108a 100644 --- a/custom_components/dirigera_platform/base_classes.py +++ b/custom_components/dirigera_platform/base_classes.py @@ -92,11 +92,19 @@ def available(self): def should_register_with_listener(self): return True + @property + def device_identifier(self) -> str: + """Return the identifier used for HA device grouping. + Uses relation_id to group sub-devices of the same physical hardware.""" + if self._json_data.relation_id: + return self._json_data.relation_id + return self._json_data.id + @property def device_info(self) -> DeviceInfo: - + return DeviceInfo( - identifiers={("dirigera_platform", self._json_data.id)}, + identifiers={("dirigera_platform", self.device_identifier)}, name=self.name, manufacturer=self._json_data.attributes.manufacturer, model=self._json_data.attributes.model, @@ -107,6 +115,15 @@ def device_info(self) -> DeviceInfo: @property def name(self): if self._json_data.attributes.custom_name is None or len(self._json_data.attributes.custom_name) == 0: + # For sub-devices with relation_id, try to get name from a sibling + if self._json_data.relation_id: + for reg_entry in hub_event_listener.device_registry.values(): + sibling = reg_entry.entity + if (hasattr(sibling, '_json_data') + and sibling._json_data.relation_id == self._json_data.relation_id + and sibling._json_data.id != self._json_data.id + and sibling._json_data.attributes.custom_name): + return sibling._json_data.attributes.custom_name return self.unique_id return self._json_data.attributes.custom_name diff --git a/custom_components/dirigera_platform/device_trigger.py b/custom_components/dirigera_platform/device_trigger.py index 404aaa1..c03dd2c 100644 --- a/custom_components/dirigera_platform/device_trigger.py +++ b/custom_components/dirigera_platform/device_trigger.py @@ -42,11 +42,8 @@ async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict[s registry_entity = registry_entry.entity - if "identifiers" not in registry_entity.device_info or len(list(registry_entity.device_info["identifiers"])[0]) < 2: - logger.warning(f"entity_id: {entity_id} corresponding in dirigera_platform entity doesnt have identifiers or isnt 2 entries long, device_info : {registry_entity.device_info}") - logger.info(registry_entity.device_info["identifiers"]) - registry_entity_id = list(registry_entity.device_info["identifiers"])[0][1] - + registry_entity_id = registry_entity.unique_id + if registry_entity_id != entity_id: logger.error(f"Found controller with entity id : {registry_entity_id} but doesnt match requested entity id: {entity_id}") continue diff --git a/custom_components/dirigera_platform/hub_event_listener.py b/custom_components/dirigera_platform/hub_event_listener.py index 7b5b94f..e0fa7fa 100644 --- a/custom_components/dirigera_platform/hub_event_listener.py +++ b/custom_components/dirigera_platform/hub_event_listener.py @@ -165,7 +165,8 @@ async def sync_all_device_areas(self): entity = registry_entry.entity if hasattr(entity, '_json_data') and entity._json_data.room is not None: room_name = entity._json_data.room.name - await self._update_device_area(device_id, room_name) + identifier = entity._json_data.relation_id or entity._json_data.id + await self._update_device_area(identifier, room_name) synced_count += 1 except Exception as ex: logger.error(f"Failed to sync area for device {device_id}: {ex}") @@ -536,7 +537,7 @@ def on_message(self, ws:Any, ws_msg:str): # but not yet in the HA device registry try: self._hass.loop.call_soon_threadsafe( - lambda room=new_room.name, device_id=id: self._hass.async_create_task( + lambda room=new_room.name, device_id=(entity._json_data.relation_id or id): self._hass.async_create_task( self._update_device_area(device_id, room) ) ) @@ -549,7 +550,7 @@ def on_message(self, ws:Any, ws_msg:str): room_changed = True try: self._hass.loop.call_soon_threadsafe( - lambda device_id=id: self._hass.async_create_task( + lambda device_id=(entity._json_data.relation_id or id): self._hass.async_create_task( self._update_device_area(device_id, "") ) ) @@ -621,7 +622,7 @@ def on_message(self, ws:Any, ws_msg:str): if name_changed and new_name is not None: try: self._hass.loop.call_soon_threadsafe( - lambda name=new_name, device_id=id: self._hass.async_create_task( + lambda name=new_name, device_id=(entity._json_data.relation_id or id): self._hass.async_create_task( self._update_device_name(device_id, name) ) ) diff --git a/custom_components/dirigera_platform/light.py b/custom_components/dirigera_platform/light.py index 1848870..046700f 100644 --- a/custom_components/dirigera_platform/light.py +++ b/custom_components/dirigera_platform/light.py @@ -193,7 +193,7 @@ def available(self): def device_info(self) -> DeviceInfo: return DeviceInfo( - identifiers={("dirigera_platform", self._json_data.id)}, + identifiers={("dirigera_platform", self._json_data.relation_id or self._json_data.id)}, name=self.name, manufacturer=self._json_data.attributes.manufacturer, model=self._json_data.attributes.model, @@ -394,7 +394,7 @@ def available(self): def device_info(self) -> DeviceInfo: # Register the device for updates - hub_event_listener.register(self.unique_id, self) + hub_event_listener.register(self.unique_id, registry_entry(self)) return DeviceInfo( identifiers={("dirigera_platform", self._device_set.id)},