diff --git a/immich_album_watcher/__init__.py b/immich_album_watcher/__init__.py index e069c41..e9c5b3b 100644 --- a/immich_album_watcher/__init__.py +++ b/immich_album_watcher/__init__.py @@ -12,6 +12,7 @@ from .const import ( CONF_ALBUM_ID, CONF_ALBUM_NAME, CONF_API_KEY, + CONF_HUB_NAME, CONF_IMMICH_URL, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL, @@ -27,6 +28,7 @@ _LOGGER = logging.getLogger(__name__) class ImmichHubData: """Data for the Immich hub.""" + name: str url: str api_key: str scan_interval: int @@ -48,12 +50,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ImmichConfigEntry) -> bo """Set up Immich Album Watcher hub from a config entry.""" hass.data.setdefault(DOMAIN, {}) + hub_name = entry.data.get(CONF_HUB_NAME, "Immich") url = entry.data[CONF_IMMICH_URL] api_key = entry.data[CONF_API_KEY] scan_interval = entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) # Store hub data entry.runtime_data = ImmichHubData( + name=hub_name, url=url, api_key=api_key, scan_interval=scan_interval, @@ -104,6 +108,7 @@ async def _async_setup_subentry_coordinator( album_id=album_id, album_name=album_name, scan_interval=hub_data.scan_interval, + hub_name=hub_data.name, ) # Fetch initial data diff --git a/immich_album_watcher/binary_sensor.py b/immich_album_watcher/binary_sensor.py index 20cf146..7e21316 100644 --- a/immich_album_watcher/binary_sensor.py +++ b/immich_album_watcher/binary_sensor.py @@ -15,12 +15,14 @@ from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.util import slugify from .const import ( ATTR_ALBUM_ID, ATTR_ALBUM_NAME, CONF_ALBUM_ID, CONF_ALBUM_NAME, + CONF_HUB_NAME, DOMAIN, NEW_ASSETS_RESET_DELAY, ) @@ -71,7 +73,9 @@ class ImmichAlbumNewAssetsSensor( self._subentry = subentry self._album_id = subentry.data[CONF_ALBUM_ID] self._album_name = subentry.data.get(CONF_ALBUM_NAME, "Unknown Album") - self._attr_unique_id = f"{subentry.subentry_id}_new_assets" + self._hub_name = entry.data.get(CONF_HUB_NAME, "Immich") + unique_id_prefix = slugify(f"{self._hub_name}_album_{self._album_name}") + self._attr_unique_id = f"{unique_id_prefix}_new_assets" @property def _album_data(self) -> AlbumData | None: diff --git a/immich_album_watcher/camera.py b/immich_album_watcher/camera.py index 2fffea7..6f5947a 100644 --- a/immich_album_watcher/camera.py +++ b/immich_album_watcher/camera.py @@ -15,8 +15,9 @@ from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.util import slugify -from .const import CONF_ALBUM_ID, CONF_ALBUM_NAME, DOMAIN +from .const import CONF_ALBUM_ID, CONF_ALBUM_NAME, CONF_HUB_NAME, DOMAIN from .coordinator import AlbumData, ImmichAlbumWatcherCoordinator _LOGGER = logging.getLogger(__name__) @@ -66,7 +67,9 @@ class ImmichAlbumThumbnailCamera( self._subentry = subentry self._album_id = subentry.data[CONF_ALBUM_ID] self._album_name = subentry.data.get(CONF_ALBUM_NAME, "Unknown Album") - self._attr_unique_id = f"{subentry.subentry_id}_thumbnail" + self._hub_name = entry.data.get(CONF_HUB_NAME, "Immich") + unique_id_prefix = slugify(f"{self._hub_name}_album_{self._album_name}") + self._attr_unique_id = f"{unique_id_prefix}_thumbnail" self._cached_image: bytes | None = None self._last_thumbnail_id: str | None = None diff --git a/immich_album_watcher/config_flow.py b/immich_album_watcher/config_flow.py index 745415c..5761c14 100644 --- a/immich_album_watcher/config_flow.py +++ b/immich_album_watcher/config_flow.py @@ -23,6 +23,7 @@ from .const import ( CONF_ALBUM_ID, CONF_ALBUM_NAME, CONF_API_KEY, + CONF_HUB_NAME, CONF_IMMICH_URL, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL, @@ -92,6 +93,7 @@ class ImmichAlbumWatcherConfigFlow(ConfigFlow, domain=DOMAIN): errors: dict[str, str] = {} if user_input is not None: + hub_name = user_input[CONF_HUB_NAME].strip() self._url = user_input[CONF_IMMICH_URL].rstrip("/") self._api_key = user_input[CONF_API_KEY] @@ -105,8 +107,9 @@ class ImmichAlbumWatcherConfigFlow(ConfigFlow, domain=DOMAIN): self._abort_if_unique_id_configured() return self.async_create_entry( - title="Immich Album Watcher", + title=hub_name, data={ + CONF_HUB_NAME: hub_name, CONF_IMMICH_URL: self._url, CONF_API_KEY: self._api_key, }, @@ -129,6 +132,7 @@ class ImmichAlbumWatcherConfigFlow(ConfigFlow, domain=DOMAIN): step_id="user", data_schema=vol.Schema( { + vol.Required(CONF_HUB_NAME, default="Immich"): str, vol.Required(CONF_IMMICH_URL): str, vol.Required(CONF_API_KEY): str, } diff --git a/immich_album_watcher/const.py b/immich_album_watcher/const.py index 5d1d6ad..b9e9222 100644 --- a/immich_album_watcher/const.py +++ b/immich_album_watcher/const.py @@ -6,6 +6,7 @@ from typing import Final DOMAIN: Final = "immich_album_watcher" # Configuration keys +CONF_HUB_NAME: Final = "hub_name" CONF_IMMICH_URL: Final = "immich_url" CONF_API_KEY: Final = "api_key" CONF_ALBUMS: Final = "albums" @@ -26,6 +27,7 @@ EVENT_ASSETS_ADDED: Final = f"{DOMAIN}_assets_added" EVENT_ASSETS_REMOVED: Final = f"{DOMAIN}_assets_removed" # Attributes +ATTR_HUB_NAME: Final = "hub_name" ATTR_ALBUM_ID: Final = "album_id" ATTR_ALBUM_NAME: Final = "album_name" ATTR_ALBUM_URL: Final = "album_url" diff --git a/immich_album_watcher/coordinator.py b/immich_album_watcher/coordinator.py index c4506e8..ab5d927 100644 --- a/immich_album_watcher/coordinator.py +++ b/immich_album_watcher/coordinator.py @@ -29,6 +29,7 @@ from .const import ( ATTR_ASSET_TYPE, ATTR_ASSET_URL, ATTR_CHANGE_TYPE, + ATTR_HUB_NAME, ATTR_PEOPLE, ATTR_REMOVED_ASSETS, ATTR_REMOVED_COUNT, @@ -217,6 +218,7 @@ class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]): album_id: str, album_name: str, scan_interval: int, + hub_name: str = "Immich", ) -> None: """Initialize the coordinator.""" super().__init__( @@ -229,6 +231,7 @@ class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]): self._api_key = api_key self._album_id = album_id self._album_name = album_name + self._hub_name = hub_name self._previous_state: AlbumData | None = None self._session: aiohttp.ClientSession | None = None self._people_cache: dict[str, str] = {} # person_id -> name @@ -542,6 +545,7 @@ class ImmichAlbumWatcherCoordinator(DataUpdateCoordinator[AlbumData | None]): added_assets_detail.append(asset_detail) event_data = { + ATTR_HUB_NAME: self._hub_name, ATTR_ALBUM_ID: change.album_id, ATTR_ALBUM_NAME: change.album_name, ATTR_CHANGE_TYPE: change.change_type, diff --git a/immich_album_watcher/manifest.json b/immich_album_watcher/manifest.json index e40cb2b..0658adc 100644 --- a/immich_album_watcher/manifest.json +++ b/immich_album_watcher/manifest.json @@ -8,6 +8,5 @@ "iot_class": "cloud_polling", "issue_tracker": "https://github.com/your-repo/immich-album-watcher/issues", "requirements": [], - "single_config_entry": true, - "version": "1.1.0" + "version": "1.2.0" } diff --git a/immich_album_watcher/sensor.py b/immich_album_watcher/sensor.py index 363e044..06354a4 100644 --- a/immich_album_watcher/sensor.py +++ b/immich_album_watcher/sensor.py @@ -20,6 +20,7 @@ from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.util import slugify from .const import ( ATTR_ALBUM_ID, @@ -36,6 +37,7 @@ from .const import ( ATTR_VIDEO_COUNT, CONF_ALBUM_ID, CONF_ALBUM_NAME, + CONF_HUB_NAME, DOMAIN, SERVICE_GET_RECENT_ASSETS, SERVICE_REFRESH, @@ -112,6 +114,9 @@ class ImmichAlbumBaseSensor(CoordinatorEntity[ImmichAlbumWatcherCoordinator], Se self._subentry = subentry self._album_id = subentry.data[CONF_ALBUM_ID] self._album_name = subentry.data.get(CONF_ALBUM_NAME, "Unknown Album") + self._hub_name = entry.data.get(CONF_HUB_NAME, "Immich") + # Generate unique_id prefix: {hub_name}_album_{album_name} + self._unique_id_prefix = slugify(f"{self._hub_name}_album_{self._album_name}") @property def _album_data(self) -> AlbumData | None: @@ -171,7 +176,7 @@ class ImmichAlbumAssetCountSensor(ImmichAlbumBaseSensor): ) -> None: """Initialize the sensor.""" super().__init__(coordinator, entry, subentry) - self._attr_unique_id = f"{subentry.subentry_id}_asset_count" + self._attr_unique_id = f"{self._unique_id_prefix}_asset_count" @property def native_value(self) -> int | None: @@ -222,7 +227,7 @@ class ImmichAlbumPhotoCountSensor(ImmichAlbumBaseSensor): ) -> None: """Initialize the sensor.""" super().__init__(coordinator, entry, subentry) - self._attr_unique_id = f"{subentry.subentry_id}_photo_count" + self._attr_unique_id = f"{self._unique_id_prefix}_photo_count" @property def native_value(self) -> int | None: @@ -247,7 +252,7 @@ class ImmichAlbumVideoCountSensor(ImmichAlbumBaseSensor): ) -> None: """Initialize the sensor.""" super().__init__(coordinator, entry, subentry) - self._attr_unique_id = f"{subentry.subentry_id}_video_count" + self._attr_unique_id = f"{self._unique_id_prefix}_video_count" @property def native_value(self) -> int | None: @@ -272,7 +277,7 @@ class ImmichAlbumLastUpdatedSensor(ImmichAlbumBaseSensor): ) -> None: """Initialize the sensor.""" super().__init__(coordinator, entry, subentry) - self._attr_unique_id = f"{subentry.subentry_id}_last_updated" + self._attr_unique_id = f"{self._unique_id_prefix}_last_updated" @property def native_value(self) -> datetime | None: @@ -302,7 +307,7 @@ class ImmichAlbumCreatedSensor(ImmichAlbumBaseSensor): ) -> None: """Initialize the sensor.""" super().__init__(coordinator, entry, subentry) - self._attr_unique_id = f"{subentry.subentry_id}_created" + self._attr_unique_id = f"{self._unique_id_prefix}_created" @property def native_value(self) -> datetime | None: @@ -332,7 +337,7 @@ class ImmichAlbumPeopleSensor(ImmichAlbumBaseSensor): ) -> None: """Initialize the sensor.""" super().__init__(coordinator, entry, subentry) - self._attr_unique_id = f"{subentry.subentry_id}_people_count" + self._attr_unique_id = f"{self._unique_id_prefix}_people_count" @property def native_value(self) -> int | None: @@ -366,7 +371,7 @@ class ImmichAlbumPublicUrlSensor(ImmichAlbumBaseSensor): ) -> None: """Initialize the sensor.""" super().__init__(coordinator, entry, subentry) - self._attr_unique_id = f"{subentry.subentry_id}_public_url" + self._attr_unique_id = f"{self._unique_id_prefix}_public_url" @property def native_value(self) -> str | None: @@ -411,7 +416,7 @@ class ImmichAlbumProtectedUrlSensor(ImmichAlbumBaseSensor): ) -> None: """Initialize the sensor.""" super().__init__(coordinator, entry, subentry) - self._attr_unique_id = f"{subentry.subentry_id}_protected_url" + self._attr_unique_id = f"{self._unique_id_prefix}_protected_url" @property def native_value(self) -> str | None: @@ -451,7 +456,7 @@ class ImmichAlbumProtectedPasswordSensor(ImmichAlbumBaseSensor): ) -> None: """Initialize the sensor.""" super().__init__(coordinator, entry, subentry) - self._attr_unique_id = f"{subentry.subentry_id}_protected_password" + self._attr_unique_id = f"{self._unique_id_prefix}_protected_password" @property def native_value(self) -> str | None: diff --git a/immich_album_watcher/text.py b/immich_album_watcher/text.py index 4e51f54..ac127e0 100644 --- a/immich_album_watcher/text.py +++ b/immich_album_watcher/text.py @@ -11,12 +11,14 @@ from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.util import slugify from .const import ( ATTR_ALBUM_ID, ATTR_ALBUM_PROTECTED_URL, CONF_ALBUM_ID, CONF_ALBUM_NAME, + CONF_HUB_NAME, DOMAIN, ) from .coordinator import AlbumData, ImmichAlbumWatcherCoordinator @@ -68,7 +70,9 @@ class ImmichAlbumProtectedPasswordText( self._subentry = subentry self._album_id = subentry.data[CONF_ALBUM_ID] self._album_name = subentry.data.get(CONF_ALBUM_NAME, "Unknown Album") - self._attr_unique_id = f"{subentry.subentry_id}_protected_password_edit" + self._hub_name = entry.data.get(CONF_HUB_NAME, "Immich") + unique_id_prefix = slugify(f"{self._hub_name}_album_{self._album_name}") + self._attr_unique_id = f"{unique_id_prefix}_protected_password_edit" @property def _album_data(self) -> AlbumData | None: diff --git a/immich_album_watcher/translations/en.json b/immich_album_watcher/translations/en.json index 443c4ef..3188154 100644 --- a/immich_album_watcher/translations/en.json +++ b/immich_album_watcher/translations/en.json @@ -51,10 +51,12 @@ "title": "Connect to Immich", "description": "Enter your Immich server details. You can get an API key from Immich → User Settings → API Keys.", "data": { + "hub_name": "Hub Name", "immich_url": "Immich URL", "api_key": "API Key" }, "data_description": { + "hub_name": "A name for this Immich server (used in entity IDs)", "immich_url": "The URL of your Immich server (e.g., http://192.168.1.100:2283)", "api_key": "Your Immich API key" } diff --git a/immich_album_watcher/translations/ru.json b/immich_album_watcher/translations/ru.json index 7621cca..7d76ab2 100644 --- a/immich_album_watcher/translations/ru.json +++ b/immich_album_watcher/translations/ru.json @@ -51,10 +51,12 @@ "title": "Подключение к Immich", "description": "Введите данные вашего сервера Immich. API-ключ можно получить в Immich → Настройки пользователя → API-ключи.", "data": { + "hub_name": "Название хаба", "immich_url": "URL Immich", "api_key": "API-ключ" }, "data_description": { + "hub_name": "Название для этого сервера Immich (используется в ID сущностей)", "immich_url": "URL вашего сервера Immich (например, http://192.168.1.100:2283)", "api_key": "Ваш API-ключ Immich" }