Update changelog for version 0.1.18, add voice status feedback for recognition states, improve wake word handling, and enhance error reporting for unsupported browsers.

This commit is contained in:
techartdev
2026-02-20 18:18:30 +02:00
parent 9a22cea159
commit e2fbd7ac74
4 changed files with 62 additions and 3 deletions
+7
View File
@@ -2,6 +2,13 @@
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.18] - 2026-02-20
### Fixed
- Voice input now requires wake word only for continuous voice mode, not for manual mic usage.
- Added in-card voice status feedback (listening, wake-word wait, sending, error) to make microphone behavior visible.
- Improved handling for unsupported speech-recognition browsers with explicit UI status.
## [0.1.17] - 2026-02-20 ## [0.1.17] - 2026-02-20
### Fixed ### Fixed
+1 -1
View File
@@ -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.17", "version": "0.1.18",
"dependencies": ["conversation"], "dependencies": ["conversation"],
"after_dependencies": ["hassio", "lovelace"] "after_dependencies": ["hassio", "lovelace"]
} }
@@ -63,6 +63,7 @@ class OpenClawChatCard extends HTMLElement {
this._wakeWordEnabled = false; this._wakeWordEnabled = false;
this._wakeWord = "hey openclaw"; this._wakeWord = "hey openclaw";
this._alwaysVoiceMode = false; this._alwaysVoiceMode = false;
this._voiceStatus = "";
} }
// ── HA card interface ─────────────────────────────────────────────── // ── HA card interface ───────────────────────────────────────────────
@@ -391,6 +392,8 @@ class OpenClawChatCard extends HTMLElement {
_startVoiceRecognition() { _startVoiceRecognition() {
if (!("webkitSpeechRecognition" in window) && !("SpeechRecognition" in window)) { if (!("webkitSpeechRecognition" in window) && !("SpeechRecognition" in window)) {
console.warn("OpenClaw: Speech recognition not supported in this browser"); console.warn("OpenClaw: Speech recognition not supported in this browser");
this._voiceStatus = "Speech recognition not supported by this browser.";
this._render();
return; return;
} }
@@ -399,6 +402,9 @@ class OpenClawChatCard extends HTMLElement {
this._recognition.continuous = this._isVoiceMode; this._recognition.continuous = this._isVoiceMode;
this._recognition.interimResults = true; this._recognition.interimResults = true;
this._recognition.lang = this._hass?.language || "en-US"; this._recognition.lang = this._hass?.language || "en-US";
this._voiceStatus = this._isVoiceMode
? `Listening (wake word: ${this._wakeWord || "hey openclaw"})`
: "Listening…";
this._recognition.onresult = (event) => { this._recognition.onresult = (event) => {
const result = event.results[event.results.length - 1]; const result = event.results[event.results.length - 1];
@@ -406,29 +412,40 @@ class OpenClawChatCard extends HTMLElement {
const text = result[0].transcript?.trim(); const text = result[0].transcript?.trim();
if (!text) return; if (!text) return;
if (this._wakeWordEnabled) { const requireWakeWord = this._wakeWordEnabled && this._isVoiceMode;
if (requireWakeWord) {
const wake = this._wakeWord || "hey openclaw"; const wake = this._wakeWord || "hey openclaw";
const lower = text.toLowerCase(); const lower = text.toLowerCase();
const wakePos = lower.indexOf(wake); const wakePos = lower.indexOf(wake);
if (wakePos < 0) { if (wakePos < 0) {
this._voiceStatus = `Heard: \"${text}\" (waiting for wake word)`;
this._render();
return; return;
} }
let command = text.slice(wakePos + wake.length).trim(); let command = text.slice(wakePos + wake.length).trim();
command = command.replace(/^[,:;.!?\-]+\s*/, ""); command = command.replace(/^[,:;.!?\-]+\s*/, "");
if (!command) { if (!command) {
this._voiceStatus = "Wake word detected. Say command after wake word.";
this._render();
return; return;
} }
this._voiceStatus = "Sending…";
this._render();
this._sendMessage(command); this._sendMessage(command);
return; return;
} }
this._voiceStatus = "Sending…";
this._render();
this._sendMessage(text); this._sendMessage(text);
} }
}; };
this._recognition.onerror = (event) => { this._recognition.onerror = (event) => {
console.error("OpenClaw: Speech recognition error:", event.error); console.error("OpenClaw: Speech recognition error:", event.error);
this._voiceStatus = `Voice error: ${event.error}`;
this._render(); this._render();
}; };
@@ -441,6 +458,7 @@ class OpenClawChatCard extends HTMLElement {
// Ignore — may already be started // Ignore — may already be started
} }
} else { } else {
this._voiceStatus = "";
this._render(); this._render();
} }
}; };
@@ -455,6 +473,7 @@ class OpenClawChatCard extends HTMLElement {
this._recognition = null; this._recognition = null;
} }
this._isVoiceMode = false; this._isVoiceMode = false;
this._voiceStatus = "";
} }
_toggleVoiceMode() { _toggleVoiceMode() {
@@ -740,6 +759,11 @@ class OpenClawChatCard extends HTMLElement {
animation: pulse 1.5s infinite; animation: pulse 1.5s infinite;
margin-right: 4px; margin-right: 4px;
} }
.voice-status {
padding: 4px 16px 0 16px;
font-size: 12px;
color: var(--oc-text-secondary);
}
@keyframes pulse { @keyframes pulse {
0%, 100% { opacity: 1; } 0%, 100% { opacity: 1; }
50% { opacity: 0.3; } 50% { opacity: 0.3; }
@@ -777,6 +801,8 @@ class OpenClawChatCard extends HTMLElement {
} }
</div> </div>
${this._voiceStatus ? `<div class="voice-status">${this._escapeHtml(this._voiceStatus)}</div>` : ""}
<div class="input-area"> <div class="input-area">
<textarea <textarea
id="input" id="input"
+27 -1
View File
@@ -63,6 +63,7 @@ class OpenClawChatCard extends HTMLElement {
this._wakeWordEnabled = false; this._wakeWordEnabled = false;
this._wakeWord = "hey openclaw"; this._wakeWord = "hey openclaw";
this._alwaysVoiceMode = false; this._alwaysVoiceMode = false;
this._voiceStatus = "";
} }
// ── HA card interface ─────────────────────────────────────────────── // ── HA card interface ───────────────────────────────────────────────
@@ -391,6 +392,8 @@ class OpenClawChatCard extends HTMLElement {
_startVoiceRecognition() { _startVoiceRecognition() {
if (!("webkitSpeechRecognition" in window) && !("SpeechRecognition" in window)) { if (!("webkitSpeechRecognition" in window) && !("SpeechRecognition" in window)) {
console.warn("OpenClaw: Speech recognition not supported in this browser"); console.warn("OpenClaw: Speech recognition not supported in this browser");
this._voiceStatus = "Speech recognition not supported by this browser.";
this._render();
return; return;
} }
@@ -399,6 +402,9 @@ class OpenClawChatCard extends HTMLElement {
this._recognition.continuous = this._isVoiceMode; this._recognition.continuous = this._isVoiceMode;
this._recognition.interimResults = true; this._recognition.interimResults = true;
this._recognition.lang = this._hass?.language || "en-US"; this._recognition.lang = this._hass?.language || "en-US";
this._voiceStatus = this._isVoiceMode
? `Listening (wake word: ${this._wakeWord || "hey openclaw"})`
: "Listening…";
this._recognition.onresult = (event) => { this._recognition.onresult = (event) => {
const result = event.results[event.results.length - 1]; const result = event.results[event.results.length - 1];
@@ -406,29 +412,40 @@ class OpenClawChatCard extends HTMLElement {
const text = result[0].transcript?.trim(); const text = result[0].transcript?.trim();
if (!text) return; if (!text) return;
if (this._wakeWordEnabled) { const requireWakeWord = this._wakeWordEnabled && this._isVoiceMode;
if (requireWakeWord) {
const wake = this._wakeWord || "hey openclaw"; const wake = this._wakeWord || "hey openclaw";
const lower = text.toLowerCase(); const lower = text.toLowerCase();
const wakePos = lower.indexOf(wake); const wakePos = lower.indexOf(wake);
if (wakePos < 0) { if (wakePos < 0) {
this._voiceStatus = `Heard: \"${text}\" (waiting for wake word)`;
this._render();
return; return;
} }
let command = text.slice(wakePos + wake.length).trim(); let command = text.slice(wakePos + wake.length).trim();
command = command.replace(/^[,:;.!?\-]+\s*/, ""); command = command.replace(/^[,:;.!?\-]+\s*/, "");
if (!command) { if (!command) {
this._voiceStatus = "Wake word detected. Say command after wake word.";
this._render();
return; return;
} }
this._voiceStatus = "Sending…";
this._render();
this._sendMessage(command); this._sendMessage(command);
return; return;
} }
this._voiceStatus = "Sending…";
this._render();
this._sendMessage(text); this._sendMessage(text);
} }
}; };
this._recognition.onerror = (event) => { this._recognition.onerror = (event) => {
console.error("OpenClaw: Speech recognition error:", event.error); console.error("OpenClaw: Speech recognition error:", event.error);
this._voiceStatus = `Voice error: ${event.error}`;
this._render(); this._render();
}; };
@@ -441,6 +458,7 @@ class OpenClawChatCard extends HTMLElement {
// Ignore — may already be started // Ignore — may already be started
} }
} else { } else {
this._voiceStatus = "";
this._render(); this._render();
} }
}; };
@@ -455,6 +473,7 @@ class OpenClawChatCard extends HTMLElement {
this._recognition = null; this._recognition = null;
} }
this._isVoiceMode = false; this._isVoiceMode = false;
this._voiceStatus = "";
} }
_toggleVoiceMode() { _toggleVoiceMode() {
@@ -740,6 +759,11 @@ class OpenClawChatCard extends HTMLElement {
animation: pulse 1.5s infinite; animation: pulse 1.5s infinite;
margin-right: 4px; margin-right: 4px;
} }
.voice-status {
padding: 4px 16px 0 16px;
font-size: 12px;
color: var(--oc-text-secondary);
}
@keyframes pulse { @keyframes pulse {
0%, 100% { opacity: 1; } 0%, 100% { opacity: 1; }
50% { opacity: 0.3; } 50% { opacity: 0.3; }
@@ -777,6 +801,8 @@ class OpenClawChatCard extends HTMLElement {
} }
</div> </div>
${this._voiceStatus ? `<div class="voice-status">${this._escapeHtml(this._voiceStatus)}</div>` : ""}
<div class="input-area"> <div class="input-area">
<textarea <textarea
id="input" id="input"