diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b07552..a40ed7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ All notable changes to the OpenClaw Home Assistant Integration will be documented in this file. +## [0.1.9] - 2026-02-20 + +### Fixed +- Updated Lovelace resource registration to use Home Assistant 2026.2 storage API (`hass.data[LOVELACE_DATA].resources`) with legacy fallback. +- Prevented silent resource-registration failure caused by reading the old `hass.data["lovelace"]` key only. + +### Changed +- Updated `hacs.json` minimum Home Assistant version to `2026.2.0`. + +## [0.1.8] - 2026-02-20 + +### Fixed +- Removed false-positive config-flow warning for `enable_openai_api=false` when Supervisor options are missing or use a different schema. +- Frontend auto-registration no longer gets stuck after an early startup failure. +- Card resource registration now retries for longer and can recover on integration reload. + +### Changed +- Frontend registration task is now de-duplicated while running and marked complete only after successful Lovelace resource creation. + ## [0.1.7] - 2026-02-20 ### Fixed diff --git a/custom_components/openclaw/__init__.py b/custom_components/openclaw/__init__.py index e0ef39d..8f43b29 100644 --- a/custom_components/openclaw/__init__.py +++ b/custom_components/openclaw/__init__.py @@ -13,6 +13,11 @@ from typing import Any import voluptuous as vol +try: + from homeassistant.components.lovelace.const import LOVELACE_DATA +except ImportError: # pragma: no cover + LOVELACE_DATA = "lovelace" + from homeassistant.config_entries import ConfigEntry from homeassistant.const import EVENT_HOMEASSISTANT_STARTED from homeassistant.core import HomeAssistant, ServiceCall, callback @@ -175,15 +180,21 @@ async def _async_register_frontend(hass: HomeAssistant) -> None: Called as a fire-and-forget task from async_setup_entry. Wrapped entirely in try/except so it can NEVER crash the integration. """ - frontend_key = f"{DOMAIN}_frontend_registration_started" - if hass.data.get(frontend_key): + frontend_done_key = f"{DOMAIN}_frontend_registered" + frontend_task_key = f"{DOMAIN}_frontend_registration_task" + + if hass.data.get(frontend_done_key): + return + + existing_task = hass.data.get(frontend_task_key) + if existing_task and not existing_task.done(): return - hass.data[frontend_key] = True async def _register_with_retries() -> None: - for _ in range(12): + for _ in range(60): url = await _async_register_static_path(hass) if url and await _async_add_lovelace_resource(hass, url): + hass.data[frontend_done_key] = True return await asyncio.sleep(5) @@ -193,9 +204,17 @@ async def _async_register_frontend(hass: HomeAssistant) -> None: _CARD_URL, ) - hass.async_create_task(_register_with_retries()) + task = hass.async_create_task(_register_with_retries()) + hass.data[frontend_task_key] = task + + def _clear_task(_fut: asyncio.Future) -> None: + hass.data.pop(frontend_task_key, None) + + task.add_done_callback(_clear_task) async def _on_ha_started(_event) -> None: + if hass.data.get(frontend_done_key): + return await _register_with_retries() hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _on_ha_started) @@ -230,15 +249,20 @@ async def _async_register_static_path(hass: HomeAssistant) -> str | None: async def _async_add_lovelace_resource(hass: HomeAssistant, url: str) -> bool: """Add the card URL to Lovelace's resource store if not already present.""" - # Lovelace stores resources in hass.data["lovelace"]["resources"]. - # It is a ResourceStorageCollection with: + # Lovelace stores resources in hass.data[LOVELACE_DATA].resources on + # modern Home Assistant versions (fallback to legacy dict key below). + # Resource collection supports: # .async_items() → list of {"id", "res_type", "url"} dicts # .async_create_item(data) → persists a new resource - lovelace_data = hass.data.get("lovelace") + lovelace_data = hass.data.get(LOVELACE_DATA) or hass.data.get("lovelace") if not lovelace_data: return False - resource_collection = lovelace_data.get("resources") + if isinstance(lovelace_data, dict): + resource_collection = lovelace_data.get("resources") + else: + resource_collection = getattr(lovelace_data, "resources", None) + if resource_collection is None: return False diff --git a/custom_components/openclaw/config_flow.py b/custom_components/openclaw/config_flow.py index a57cdda..dfa9c96 100644 --- a/custom_components/openclaw/config_flow.py +++ b/custom_components/openclaw/config_flow.py @@ -163,9 +163,13 @@ async def _async_try_discover_addon(hass: HomeAssistant) -> dict[str, Any] | Non ) return None - # Warn early if the OpenAI-compatible API is disabled — /v1/models will return - # HTML and the connection probe will fail with a misleading error. - if not addon_options.get("enable_openai_api", False): + # Warn only when Supervisor explicitly reports the option as disabled. + # If the key is missing (older/newer addon schema), do not warn. + enable_openai_api = addon_options.get("enable_openai_api") + if enable_openai_api is None and isinstance(addon_options.get("gateway"), dict): + enable_openai_api = addon_options["gateway"].get("enable_openai_api") + + if enable_openai_api is False: _LOGGER.warning( "Addon option 'enable_openai_api' is false. " "The integration requires this to be enabled. " diff --git a/custom_components/openclaw/manifest.json b/custom_components/openclaw/manifest.json index 43202cd..d84b33f 100644 --- a/custom_components/openclaw/manifest.json +++ b/custom_components/openclaw/manifest.json @@ -8,7 +8,7 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues", "requirements": [], - "version": "0.1.7", + "version": "0.1.9", "dependencies": ["conversation"], "after_dependencies": ["hassio", "lovelace"] } diff --git a/hacs.json b/hacs.json index 9fbd7cd..5fddc60 100644 --- a/hacs.json +++ b/hacs.json @@ -3,5 +3,5 @@ "render_readme": true, "domains": ["sensor", "binary_sensor", "conversation"], "iot_class": "local_polling", - "homeassistant": "2024.1.0" + "homeassistant": "2026.2.0" }