Update changelog for version 0.1.7, resolve chat card startup race, enhance frontend registration with retries, and add MIT license file
This commit is contained in:
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
All notable changes to the OpenClaw Home Assistant Integration will be documented in this file.
|
All notable changes to the OpenClaw Home Assistant Integration will be documented in this file.
|
||||||
|
|
||||||
|
## [0.1.7] - 2026-02-20
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Resolved chat card startup race where Lovelace resources were attempted before HTTP/Lovelace were ready, causing `Custom element not found: openclaw-chat-card`.
|
||||||
|
- Frontend registration now retries and waits for Home Assistant startup readiness before giving up.
|
||||||
|
- Static JS path registration is now idempotent and only marked successful after the path is actually registered.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added MIT license file at repository root (`LICENSE`).
|
||||||
|
|
||||||
## [0.1.6] - 2025-01-01
|
## [0.1.6] - 2025-01-01
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 techartdev, Tech Art Ltd
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -81,4 +81,4 @@ action:
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
See [LICENSE](../LICENSE).
|
See [LICENSE](LICENSE).
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Sets up the OpenClaw integration: API client, coordinator, platforms, and servic
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -13,6 +14,7 @@ from typing import Any
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
@@ -173,42 +175,60 @@ async def _async_register_frontend(hass: HomeAssistant) -> None:
|
|||||||
Called as a fire-and-forget task from async_setup_entry.
|
Called as a fire-and-forget task from async_setup_entry.
|
||||||
Wrapped entirely in try/except so it can NEVER crash the integration.
|
Wrapped entirely in try/except so it can NEVER crash the integration.
|
||||||
"""
|
"""
|
||||||
frontend_key = f"{DOMAIN}_frontend_registered"
|
frontend_key = f"{DOMAIN}_frontend_registration_started"
|
||||||
if hass.data.get(frontend_key):
|
if hass.data.get(frontend_key):
|
||||||
return
|
return
|
||||||
hass.data[frontend_key] = True
|
hass.data[frontend_key] = True
|
||||||
|
|
||||||
url = _CARD_URL
|
async def _register_with_retries() -> None:
|
||||||
|
for _ in range(12):
|
||||||
|
url = await _async_register_static_path(hass)
|
||||||
|
if url and await _async_add_lovelace_resource(hass, url):
|
||||||
|
return
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
# ── Step 1: Serve the JS via a static HTTP path ───────────────────
|
_LOGGER.warning(
|
||||||
if _CARD_PATH.exists() and hass.http is not None:
|
"Could not auto-register OpenClaw chat card resource after retries. "
|
||||||
try:
|
"Add it manually in Dashboard resources: %s",
|
||||||
# HA 2024.11+ — async_register_static_paths w/ StaticPathConfig
|
_CARD_URL,
|
||||||
from homeassistant.components.http import StaticPathConfig # noqa: PLC0415
|
)
|
||||||
await hass.http.async_register_static_paths(
|
|
||||||
[StaticPathConfig(url, str(_CARD_PATH), cache_headers=True)]
|
|
||||||
)
|
|
||||||
_LOGGER.debug("Registered static path (new API): %s", url)
|
|
||||||
except (ImportError, AttributeError):
|
|
||||||
try:
|
|
||||||
# HA <2024.11 — synchronous register_static_path
|
|
||||||
hass.http.register_static_path(url, str(_CARD_PATH), True)
|
|
||||||
_LOGGER.debug("Registered static path (legacy API): %s", url)
|
|
||||||
except Exception as err: # noqa: BLE001
|
|
||||||
_LOGGER.debug("Could not register static path '%s': %s", url, err)
|
|
||||||
url = f"/local/{_CARD_FILENAME}" # fall back to /local/ URL
|
|
||||||
except Exception as err: # noqa: BLE001
|
|
||||||
_LOGGER.debug("Could not register static path '%s': %s", url, err)
|
|
||||||
url = f"/local/{_CARD_FILENAME}"
|
|
||||||
else:
|
|
||||||
# JS not in package (shouldn't happen) or HTTP not ready — try /local/
|
|
||||||
url = f"/local/{_CARD_FILENAME}"
|
|
||||||
|
|
||||||
# ── Step 2: Add to Lovelace resource store ───────────────────────
|
hass.async_create_task(_register_with_retries())
|
||||||
await _async_add_lovelace_resource(hass, url)
|
|
||||||
|
async def _on_ha_started(_event) -> None:
|
||||||
|
await _register_with_retries()
|
||||||
|
|
||||||
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _on_ha_started)
|
||||||
|
|
||||||
|
|
||||||
async def _async_add_lovelace_resource(hass: HomeAssistant, url: str) -> None:
|
async def _async_register_static_path(hass: HomeAssistant) -> str | None:
|
||||||
|
"""Register the packaged chat-card JS as a static path when HTTP is ready."""
|
||||||
|
static_key = f"{DOMAIN}_static_registered"
|
||||||
|
if hass.data.get(static_key):
|
||||||
|
return _CARD_URL
|
||||||
|
|
||||||
|
if not _CARD_PATH.exists():
|
||||||
|
_LOGGER.warning("Chat card JS not found at %s", _CARD_PATH)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if hass.http is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
from homeassistant.components.http import StaticPathConfig # noqa: PLC0415
|
||||||
|
|
||||||
|
await hass.http.async_register_static_paths(
|
||||||
|
[StaticPathConfig(_CARD_URL, str(_CARD_PATH), cache_headers=True)]
|
||||||
|
)
|
||||||
|
except (ImportError, AttributeError):
|
||||||
|
hass.http.register_static_path(_CARD_URL, str(_CARD_PATH), True)
|
||||||
|
|
||||||
|
hass.data[static_key] = True
|
||||||
|
_LOGGER.debug("Registered static path: %s", _CARD_URL)
|
||||||
|
return _CARD_URL
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_add_lovelace_resource(hass: HomeAssistant, url: str) -> bool:
|
||||||
"""Add the card URL to Lovelace's resource store if not already present."""
|
"""Add the card URL to Lovelace's resource store if not already present."""
|
||||||
# Lovelace stores resources in hass.data["lovelace"]["resources"].
|
# Lovelace stores resources in hass.data["lovelace"]["resources"].
|
||||||
# It is a ResourceStorageCollection with:
|
# It is a ResourceStorageCollection with:
|
||||||
@@ -216,22 +236,17 @@ async def _async_add_lovelace_resource(hass: HomeAssistant, url: str) -> None:
|
|||||||
# .async_create_item(data) → persists a new resource
|
# .async_create_item(data) → persists a new resource
|
||||||
lovelace_data = hass.data.get("lovelace")
|
lovelace_data = hass.data.get("lovelace")
|
||||||
if not lovelace_data:
|
if not lovelace_data:
|
||||||
_LOGGER.debug(
|
return False
|
||||||
"Lovelace not loaded; resource '%s' must be added manually if needed",
|
|
||||||
url,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
resource_collection = lovelace_data.get("resources")
|
resource_collection = lovelace_data.get("resources")
|
||||||
if resource_collection is None:
|
if resource_collection is None:
|
||||||
_LOGGER.debug("Lovelace resource store not available")
|
return False
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
existing_urls = {item["url"] for item in resource_collection.async_items()}
|
existing_urls = {item["url"] for item in resource_collection.async_items()}
|
||||||
if url in existing_urls:
|
if url in existing_urls:
|
||||||
_LOGGER.debug("Lovelace resource already registered: %s", url)
|
_LOGGER.debug("Lovelace resource already registered: %s", url)
|
||||||
return
|
return True
|
||||||
|
|
||||||
await resource_collection.async_create_item(
|
await resource_collection.async_create_item(
|
||||||
{"res_type": "module", "url": url}
|
{"res_type": "module", "url": url}
|
||||||
@@ -240,6 +255,7 @@ async def _async_add_lovelace_resource(hass: HomeAssistant, url: str) -> None:
|
|||||||
"Auto-registered Lovelace resource: %s — the chat card is ready to use.",
|
"Auto-registered Lovelace resource: %s — the chat card is ready to use.",
|
||||||
url,
|
url,
|
||||||
)
|
)
|
||||||
|
return True
|
||||||
except Exception as err: # noqa: BLE001
|
except Exception as err: # noqa: BLE001
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Could not auto-register Lovelace resource '%s': %s. "
|
"Could not auto-register Lovelace resource '%s': %s. "
|
||||||
@@ -247,6 +263,7 @@ async def _async_add_lovelace_resource(hass: HomeAssistant, url: str) -> None:
|
|||||||
url,
|
url,
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# ── Service registration ──────────────────────────────────────────────────────
|
# ── Service registration ──────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues",
|
"issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"version": "0.1.6",
|
"version": "0.1.7",
|
||||||
"dependencies": ["conversation"],
|
"dependencies": ["conversation"],
|
||||||
"after_dependencies": ["hassio", "lovelace"]
|
"after_dependencies": ["hassio", "lovelace"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user