Update version to 0.1.48 and improve chat-card gateway status detection
This commit is contained in:
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
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.48] - 2026-02-21
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Improved chat-card gateway header status detection to handle suffixed entity IDs (for example `_2`) and common connected/offline state variants.
|
||||||
|
|
||||||
## [0.1.47] - 2026-02-21
|
## [0.1.47] - 2026-02-21
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ _CARD_PATH = Path(__file__).parent / "www" / _CARD_FILENAME
|
|||||||
# URL at which the card JS is served (registered via register_static_path)
|
# URL at which the card JS is served (registered via register_static_path)
|
||||||
_CARD_STATIC_URL = f"/openclaw/{_CARD_FILENAME}"
|
_CARD_STATIC_URL = f"/openclaw/{_CARD_FILENAME}"
|
||||||
# Versioned URL used for Lovelace resource registration to avoid stale browser cache
|
# Versioned URL used for Lovelace resource registration to avoid stale browser cache
|
||||||
_CARD_URL = f"{_CARD_STATIC_URL}?v=0.1.47"
|
_CARD_URL = f"{_CARD_STATIC_URL}?v=0.1.48"
|
||||||
|
|
||||||
OpenClawConfigEntry = ConfigEntry
|
OpenClawConfigEntry = ConfigEntry
|
||||||
|
|
||||||
|
|||||||
@@ -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.47",
|
"version": "0.1.48",
|
||||||
"dependencies": ["conversation"],
|
"dependencies": ["conversation"],
|
||||||
"after_dependencies": ["hassio", "lovelace"]
|
"after_dependencies": ["hassio", "lovelace"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
* + subscribes to openclaw_message_received events.
|
* + subscribes to openclaw_message_received events.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const CARD_VERSION = "0.3.6";
|
const CARD_VERSION = "0.3.7";
|
||||||
|
|
||||||
// Max time (ms) to show the thinking indicator before falling back to an error
|
// Max time (ms) to show the thinking indicator before falling back to an error
|
||||||
const THINKING_TIMEOUT_MS = 120_000;
|
const THINKING_TIMEOUT_MS = 120_000;
|
||||||
@@ -75,6 +75,8 @@ class OpenClawChatCard extends HTMLElement {
|
|||||||
this._voiceProviderIntegration = "browser";
|
this._voiceProviderIntegration = "browser";
|
||||||
this._preferredAssistSttEngine = null;
|
this._preferredAssistSttEngine = null;
|
||||||
this._preferredAssistTtsEngine = null;
|
this._preferredAssistTtsEngine = null;
|
||||||
|
this._assistTtsEngines = [];
|
||||||
|
this._lastHaTtsAttempt = null;
|
||||||
this._voiceBackendBlocked = false;
|
this._voiceBackendBlocked = false;
|
||||||
this._voiceStopRequested = false;
|
this._voiceStopRequested = false;
|
||||||
this._assistRecordingActive = false;
|
this._assistRecordingActive = false;
|
||||||
@@ -129,15 +131,34 @@ class OpenClawChatCard extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_getGatewayConnectionState() {
|
_getGatewayConnectionState() {
|
||||||
const statusEntity = this._hass?.states?.["sensor.openclaw_status"];
|
const states = this._hass?.states || {};
|
||||||
const binaryEntity = this._hass?.states?.["binary_sensor.openclaw_connected"];
|
const stateEntries = Object.entries(states);
|
||||||
const status = String(statusEntity?.state || "").toLowerCase();
|
|
||||||
const connected = String(binaryEntity?.state || "").toLowerCase() === "on";
|
|
||||||
|
|
||||||
if (connected || status === "online") {
|
const exactStatus = states["sensor.openclaw_status"];
|
||||||
|
const exactConnected = states["binary_sensor.openclaw_connected"];
|
||||||
|
|
||||||
|
const statusEntity =
|
||||||
|
exactStatus ||
|
||||||
|
stateEntries.find(([entityId]) => entityId.startsWith("sensor.openclaw_status"))?.[1] ||
|
||||||
|
null;
|
||||||
|
|
||||||
|
const binaryEntity =
|
||||||
|
exactConnected ||
|
||||||
|
stateEntries.find(([entityId]) => entityId.startsWith("binary_sensor.openclaw_connected"))?.[1] ||
|
||||||
|
null;
|
||||||
|
|
||||||
|
const status = String(statusEntity?.state || "").toLowerCase();
|
||||||
|
const connectedState = String(binaryEntity?.state || "").toLowerCase();
|
||||||
|
|
||||||
|
const connected = ["on", "connected", "online", "true", "1"].includes(connectedState);
|
||||||
|
const disconnected = ["off", "disconnected", "offline", "false", "0"].includes(
|
||||||
|
connectedState
|
||||||
|
);
|
||||||
|
|
||||||
|
if (connected || ["online", "connected"].includes(status)) {
|
||||||
return { label: "Online", css: "online" };
|
return { label: "Online", css: "online" };
|
||||||
}
|
}
|
||||||
if (status === "offline" || String(binaryEntity?.state || "").toLowerCase() === "off") {
|
if (disconnected || ["offline", "disconnected"].includes(status)) {
|
||||||
return { label: "Offline", css: "offline" };
|
return { label: "Offline", css: "offline" };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,10 +400,15 @@ class OpenClawChatCard extends HTMLElement {
|
|||||||
const pipelines = Array.isArray(pipelineResult?.pipelines)
|
const pipelines = Array.isArray(pipelineResult?.pipelines)
|
||||||
? pipelineResult.pipelines
|
? pipelineResult.pipelines
|
||||||
: [];
|
: [];
|
||||||
|
const discoveredTtsEngines = pipelines
|
||||||
|
.map((pipeline) => pipeline?.tts_engine)
|
||||||
|
.filter((engine) => typeof engine === "string" && engine.trim().length > 0)
|
||||||
|
.map((engine) => engine.trim());
|
||||||
|
this._assistTtsEngines = [...new Set(discoveredTtsEngines)];
|
||||||
const preferredPipeline =
|
const preferredPipeline =
|
||||||
pipelines.find((pipeline) => pipeline?.id === preferredPipelineId) || pipelines[0];
|
pipelines.find((pipeline) => pipeline?.id === preferredPipelineId) || pipelines[0];
|
||||||
this._preferredAssistSttEngine = preferredPipeline?.stt_engine || null;
|
this._preferredAssistSttEngine = preferredPipeline?.stt_engine || null;
|
||||||
this._preferredAssistTtsEngine = preferredPipeline?.tts_engine || null;
|
this._preferredAssistTtsEngine = preferredPipeline?.tts_engine || this._assistTtsEngines[0] || null;
|
||||||
|
|
||||||
const sttLanguage = preferredPipeline?.stt_language || preferredPipeline?.language;
|
const sttLanguage = preferredPipeline?.stt_language || preferredPipeline?.language;
|
||||||
const ttsLanguage =
|
const ttsLanguage =
|
||||||
@@ -1319,8 +1345,9 @@ class OpenClawChatCard extends HTMLElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reason = this._lastHaTtsAttempt ? ` (${this._lastHaTtsAttempt})` : "";
|
||||||
this._voiceStatus =
|
this._voiceStatus =
|
||||||
"Home Assistant TTS unavailable; using browser default voice as fallback.";
|
`Home Assistant TTS unavailable${reason}; using browser default voice as fallback.`;
|
||||||
this._render();
|
this._render();
|
||||||
try {
|
try {
|
||||||
speechSynthesis.cancel();
|
speechSynthesis.cancel();
|
||||||
@@ -1394,8 +1421,24 @@ class OpenClawChatCard extends HTMLElement {
|
|||||||
async _speakViaHomeAssistantTts(text, language) {
|
async _speakViaHomeAssistantTts(text, language) {
|
||||||
if (!this._hass) return false;
|
if (!this._hass) return false;
|
||||||
|
|
||||||
const engine = this._preferredAssistTtsEngine;
|
this._lastHaTtsAttempt = null;
|
||||||
if (!engine) return false;
|
|
||||||
|
const configuredEngine =
|
||||||
|
typeof this._config?.ha_tts_engine === "string" ? this._config.ha_tts_engine.trim() : "";
|
||||||
|
const engineCandidates = [];
|
||||||
|
for (const candidate of [configuredEngine, this._preferredAssistTtsEngine, ...this._assistTtsEngines]) {
|
||||||
|
if (typeof candidate !== "string") continue;
|
||||||
|
const normalized = candidate.trim();
|
||||||
|
if (!normalized) continue;
|
||||||
|
if (!engineCandidates.includes(normalized)) {
|
||||||
|
engineCandidates.push(normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!engineCandidates.length) {
|
||||||
|
this._lastHaTtsAttempt = "no_tts_engine_configured_in_assist_pipeline";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const token = this._hass?.auth?.data?.access_token;
|
const token = this._hass?.auth?.data?.access_token;
|
||||||
const headers = {
|
const headers = {
|
||||||
@@ -1403,6 +1446,12 @@ class OpenClawChatCard extends HTMLElement {
|
|||||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let audioPath = null;
|
||||||
|
let selectedEngine = null;
|
||||||
|
let lastError = "unknown_error";
|
||||||
|
|
||||||
|
for (const engine of engineCandidates) {
|
||||||
|
console.info("OpenClaw: trying HA TTS engine", engine, "language", language);
|
||||||
const payloads = [
|
const payloads = [
|
||||||
{
|
{
|
||||||
platform: engine,
|
platform: engine,
|
||||||
@@ -1424,7 +1473,6 @@ class OpenClawChatCard extends HTMLElement {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let audioPath = null;
|
|
||||||
for (const payload of payloads) {
|
for (const payload of payloads) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/tts_get_url", {
|
const response = await fetch("/api/tts_get_url", {
|
||||||
@@ -1432,30 +1480,50 @@ class OpenClawChatCard extends HTMLElement {
|
|||||||
headers,
|
headers,
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
if (!response.ok) continue;
|
|
||||||
|
if (!response.ok) {
|
||||||
|
lastError = `engine=${engine}, tts_get_url_http=${response.status}`;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const body = await response.json();
|
const body = await response.json();
|
||||||
const candidate = body?.url || body?.path || body?.audio_url || body?.audioUrl;
|
const candidate = body?.url || body?.path || body?.audio_url || body?.audioUrl;
|
||||||
if (!candidate) continue;
|
if (!candidate) {
|
||||||
|
lastError = `engine=${engine}, no_audio_url_in_response`;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
audioPath = String(candidate).startsWith("http")
|
audioPath = String(candidate).startsWith("http")
|
||||||
? String(candidate)
|
? String(candidate)
|
||||||
: String(candidate).startsWith("/")
|
: String(candidate).startsWith("/")
|
||||||
? String(candidate)
|
? String(candidate)
|
||||||
: `/${String(candidate)}`;
|
: `/${String(candidate)}`;
|
||||||
|
selectedEngine = engine;
|
||||||
break;
|
break;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.debug("OpenClaw: HA TTS URL request failed", err);
|
console.debug("OpenClaw: HA TTS URL request failed", err);
|
||||||
|
lastError = `engine=${engine}, tts_get_url_request_failed`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!audioPath) return false;
|
if (audioPath) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!audioPath || !selectedEngine) {
|
||||||
|
this._lastHaTtsAttempt = lastError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const audioResponse = await fetch(audioPath, {
|
const audioResponse = await fetch(audioPath, {
|
||||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||||
});
|
});
|
||||||
if (!audioResponse.ok) return false;
|
if (!audioResponse.ok) {
|
||||||
|
this._lastHaTtsAttempt = `engine=${selectedEngine}, audio_fetch_http=${audioResponse.status}`;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const audioBlob = await audioResponse.blob();
|
const audioBlob = await audioResponse.blob();
|
||||||
const objectUrl = URL.createObjectURL(audioBlob);
|
const objectUrl = URL.createObjectURL(audioBlob);
|
||||||
@@ -1481,9 +1549,11 @@ class OpenClawChatCard extends HTMLElement {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._lastHaTtsAttempt = `engine=${selectedEngine}, ok`;
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.debug("OpenClaw: HA TTS playback failed", err);
|
console.debug("OpenClaw: HA TTS playback failed", err);
|
||||||
|
this._lastHaTtsAttempt = `engine=${selectedEngine}, audio_playback_failed`;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
if (!customElements.get("openclaw-chat-card")) {
|
if (!customElements.get("openclaw-chat-card")) {
|
||||||
const src = "/openclaw/openclaw-chat-card.js?v=0.1.47";
|
const src = "/openclaw/openclaw-chat-card.js?v=0.1.48";
|
||||||
console.info("OpenClaw loader importing", src);
|
console.info("OpenClaw loader importing", src);
|
||||||
await import(src);
|
await import(src);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user