Update version to 0.1.41 and enhance integration with voice mode fixes, gateway connection status, and updated documentation
This commit is contained in:
@@ -2,6 +2,16 @@
|
||||
|
||||
All notable changes to the OpenClaw Home Assistant Integration will be documented in this file.
|
||||
|
||||
## [0.1.41] - 2026-02-20
|
||||
|
||||
### Fixed
|
||||
- Fixed voice-mode option sync so wake-word enabled/disabled changes are reloaded before toggling voice mode.
|
||||
- Removed legacy always-on voice mode behavior that could force sticky voice-mode state.
|
||||
- Added live gateway connection badge to the chat card header using existing OpenClaw status entities.
|
||||
|
||||
### Changed
|
||||
- Removed "Always-on voice mode" option from integration options UI and translations.
|
||||
|
||||
## [0.1.40] - 2026-02-20
|
||||
|
||||
### Added
|
||||
|
||||
@@ -28,15 +28,17 @@ OpenClaw is a Home Assistant custom integration that connects your HA instance t
|
||||
- `openclaw_message_received`
|
||||
- `openclaw_tool_invoked`
|
||||
- **Sensors / status entities** for model and connection state
|
||||
- Includes tool telemetry sensors (`Last Tool`, `Last Tool Status`, `Last Tool Duration`, `Last Tool Invoked`)
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- Home Assistant with Supervisor support for addon discovery
|
||||
- Home Assistant Core `2025.1.0+` (declared minimum)
|
||||
- Supervisor is optional (used for auto-discovery)
|
||||
- OpenClaw Assistant addon installed and running
|
||||
|
||||
The integration auto-detects the addon. You do not need to manually set an API base URL.
|
||||
The integration can auto-detect the addon when Supervisor is available. You can always configure host/port/token manually.
|
||||
|
||||
---
|
||||
|
||||
@@ -73,6 +75,8 @@ The integration auto-detects the addon. You do not need to manually set an API b
|
||||
|
||||
The card is registered automatically by the integration.
|
||||
|
||||
The card header shows live gateway state (`Online` / `Offline`) using existing OpenClaw status entities.
|
||||
|
||||
```yaml
|
||||
type: custom:openclaw-chat-card
|
||||
title: OpenClaw Chat
|
||||
@@ -125,7 +129,6 @@ When enabled, OpenClaw tool-call responses can execute Home Assistant services.
|
||||
|
||||
- **Wake word enabled**
|
||||
- **Wake word** (default: `hey openclaw`)
|
||||
- **Always voice mode** (continuous listening while card is open)
|
||||
- **Voice input provider** (`browser` or `assist_stt`)
|
||||
|
||||
### Voice provider usage
|
||||
@@ -138,7 +141,7 @@ When enabled, OpenClaw tool-call responses can execute Home Assistant services.
|
||||
- **`assist_stt`**
|
||||
- Uses Home Assistant STT provider via `/api/stt/<provider>`.
|
||||
- Intended for manual mic input (press mic, speak, auto-stop, transcribe, send).
|
||||
- Continuous voice mode is not used in this provider.
|
||||
- If continuous Voice Mode is enabled while this provider is selected, the card uses browser speech for continuous listening.
|
||||
|
||||
For `assist_stt`, make sure an STT engine is configured in **Settings → Voice assistants**.
|
||||
|
||||
@@ -291,6 +294,11 @@ action:
|
||||
- For `browser`: open browser console for `OpenClaw: Speech recognition error`; repeated `network` usually means browser speech backend failure
|
||||
- For `assist_stt`: check network calls to `/api/stt/<provider>` and verify Home Assistant Voice/STT provider is configured
|
||||
|
||||
### Tool sensors show `Unknown`
|
||||
|
||||
- `Last Tool*` sensors stay `Unknown` until at least one `openclaw.invoke_tool` service call has executed.
|
||||
- `Session Count` remains `0` if gateway policy blocks `sessions_list` for `/tools/invoke`.
|
||||
|
||||
### Responses do not appear after sending
|
||||
|
||||
- Verify `openclaw_message_received` is being fired in Developer Tools → Events
|
||||
|
||||
@@ -57,7 +57,6 @@ from .const import (
|
||||
CONF_INCLUDE_EXPOSED_CONTEXT,
|
||||
CONF_WAKE_WORD,
|
||||
CONF_WAKE_WORD_ENABLED,
|
||||
CONF_ALWAYS_VOICE_MODE,
|
||||
CONF_ALLOW_BRAVE_WEBSPEECH,
|
||||
CONF_VOICE_PROVIDER,
|
||||
CONTEXT_STRATEGY_TRUNCATE,
|
||||
@@ -67,7 +66,6 @@ from .const import (
|
||||
DEFAULT_INCLUDE_EXPOSED_CONTEXT,
|
||||
DEFAULT_WAKE_WORD,
|
||||
DEFAULT_WAKE_WORD_ENABLED,
|
||||
DEFAULT_ALWAYS_VOICE_MODE,
|
||||
DEFAULT_ALLOW_BRAVE_WEBSPEECH,
|
||||
DEFAULT_VOICE_PROVIDER,
|
||||
DOMAIN,
|
||||
@@ -92,7 +90,7 @@ _CARD_PATH = Path(__file__).parent / "www" / _CARD_FILENAME
|
||||
# URL at which the card JS is served (registered via register_static_path)
|
||||
_CARD_STATIC_URL = f"/openclaw/{_CARD_FILENAME}"
|
||||
# Versioned URL used for Lovelace resource registration to avoid stale browser cache
|
||||
_CARD_URL = f"{_CARD_STATIC_URL}?v=0.1.40"
|
||||
_CARD_URL = f"{_CARD_STATIC_URL}?v=0.1.41"
|
||||
|
||||
OpenClawConfigEntry = ConfigEntry
|
||||
|
||||
@@ -802,10 +800,6 @@ def _async_register_websocket_api(hass: HomeAssistant) -> None:
|
||||
DEFAULT_WAKE_WORD_ENABLED,
|
||||
),
|
||||
CONF_WAKE_WORD: options.get(CONF_WAKE_WORD, DEFAULT_WAKE_WORD),
|
||||
CONF_ALWAYS_VOICE_MODE: options.get(
|
||||
CONF_ALWAYS_VOICE_MODE,
|
||||
DEFAULT_ALWAYS_VOICE_MODE,
|
||||
),
|
||||
CONF_ALLOW_BRAVE_WEBSPEECH: options.get(
|
||||
CONF_ALLOW_BRAVE_WEBSPEECH,
|
||||
DEFAULT_ALLOW_BRAVE_WEBSPEECH,
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -44,7 +44,6 @@ from .const import (
|
||||
CONF_INCLUDE_EXPOSED_CONTEXT,
|
||||
CONF_WAKE_WORD,
|
||||
CONF_WAKE_WORD_ENABLED,
|
||||
CONF_ALWAYS_VOICE_MODE,
|
||||
CONF_ALLOW_BRAVE_WEBSPEECH,
|
||||
CONF_VOICE_PROVIDER,
|
||||
CONTEXT_STRATEGY_CLEAR,
|
||||
@@ -57,7 +56,6 @@ from .const import (
|
||||
DEFAULT_INCLUDE_EXPOSED_CONTEXT,
|
||||
DEFAULT_WAKE_WORD,
|
||||
DEFAULT_WAKE_WORD_ENABLED,
|
||||
DEFAULT_ALWAYS_VOICE_MODE,
|
||||
DEFAULT_ALLOW_BRAVE_WEBSPEECH,
|
||||
DEFAULT_VOICE_PROVIDER,
|
||||
DOMAIN,
|
||||
@@ -462,13 +460,6 @@ class OpenClawOptionsFlow(OptionsFlow):
|
||||
DEFAULT_WAKE_WORD,
|
||||
),
|
||||
): str,
|
||||
vol.Optional(
|
||||
CONF_ALWAYS_VOICE_MODE,
|
||||
default=options.get(
|
||||
CONF_ALWAYS_VOICE_MODE,
|
||||
DEFAULT_ALWAYS_VOICE_MODE,
|
||||
),
|
||||
): bool,
|
||||
vol.Optional(
|
||||
CONF_ALLOW_BRAVE_WEBSPEECH,
|
||||
default=options.get(
|
||||
|
||||
@@ -30,7 +30,6 @@ CONF_CONTEXT_STRATEGY = "context_strategy"
|
||||
CONF_ENABLE_TOOL_CALLS = "enable_tool_calls"
|
||||
CONF_WAKE_WORD_ENABLED = "wake_word_enabled"
|
||||
CONF_WAKE_WORD = "wake_word"
|
||||
CONF_ALWAYS_VOICE_MODE = "always_voice_mode"
|
||||
CONF_ALLOW_BRAVE_WEBSPEECH = "allow_brave_webspeech"
|
||||
CONF_VOICE_PROVIDER = "voice_provider"
|
||||
|
||||
@@ -40,7 +39,6 @@ DEFAULT_CONTEXT_STRATEGY = "truncate"
|
||||
DEFAULT_ENABLE_TOOL_CALLS = False
|
||||
DEFAULT_WAKE_WORD_ENABLED = False
|
||||
DEFAULT_WAKE_WORD = "hey openclaw"
|
||||
DEFAULT_ALWAYS_VOICE_MODE = False
|
||||
DEFAULT_ALLOW_BRAVE_WEBSPEECH = False
|
||||
DEFAULT_VOICE_PROVIDER = "browser"
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues",
|
||||
"requirements": [],
|
||||
"version": "0.1.40",
|
||||
"version": "0.1.41",
|
||||
"dependencies": ["conversation"],
|
||||
"after_dependencies": ["hassio", "lovelace"]
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
"enable_tool_calls": "Enable tool calls (execute services)",
|
||||
"wake_word_enabled": "Require wake word",
|
||||
"wake_word": "Wake word",
|
||||
"always_voice_mode": "Always-on voice mode",
|
||||
"allow_brave_webspeech": "Allow Web Speech in Brave (experimental)",
|
||||
"voice_provider": "Voice input provider (browser or HA STT)"
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
"enable_tool_calls": "Enable tool calls (execute services)",
|
||||
"wake_word_enabled": "Require wake word",
|
||||
"wake_word": "Wake word",
|
||||
"always_voice_mode": "Always-on voice mode",
|
||||
"allow_brave_webspeech": "Allow Web Speech in Brave (experimental)",
|
||||
"voice_provider": "Voice input provider (browser or HA STT)"
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* + subscribes to openclaw_message_received events.
|
||||
*/
|
||||
|
||||
const CARD_VERSION = "0.3.4";
|
||||
const CARD_VERSION = "0.3.5";
|
||||
|
||||
// Max time (ms) to show the thinking indicator before falling back to an error
|
||||
const THINKING_TIMEOUT_MS = 120_000;
|
||||
@@ -62,7 +62,6 @@ class OpenClawChatCard extends HTMLElement {
|
||||
this._historySyncRetryTimer = null;
|
||||
this._wakeWordEnabled = false;
|
||||
this._wakeWord = "hey openclaw";
|
||||
this._alwaysVoiceMode = false;
|
||||
this._voiceStatus = "";
|
||||
this._voiceRetryTimer = null;
|
||||
this._voiceRetryCount = 0;
|
||||
@@ -123,8 +122,24 @@ class OpenClawChatCard extends HTMLElement {
|
||||
this._subscribeToEvents();
|
||||
this._syncHistoryFromBackend();
|
||||
this._loadIntegrationSettings();
|
||||
this._render();
|
||||
}
|
||||
this._render();
|
||||
}
|
||||
|
||||
_getGatewayConnectionState() {
|
||||
const statusEntity = this._hass?.states?.["sensor.openclaw_status"];
|
||||
const binaryEntity = this._hass?.states?.["binary_sensor.openclaw_connected"];
|
||||
const status = String(statusEntity?.state || "").toLowerCase();
|
||||
const connected = String(binaryEntity?.state || "").toLowerCase() === "on";
|
||||
|
||||
if (connected || status === "online") {
|
||||
return { label: "Online", css: "online" };
|
||||
}
|
||||
if (status === "offline" || String(binaryEntity?.state || "").toLowerCase() === "off") {
|
||||
return { label: "Offline", css: "offline" };
|
||||
}
|
||||
|
||||
return { label: "Unknown", css: "unknown" };
|
||||
}
|
||||
|
||||
getCardSize() {
|
||||
@@ -321,7 +336,7 @@ class OpenClawChatCard extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
async _loadIntegrationSettings() {
|
||||
async _loadIntegrationSettings(includePipeline = true) {
|
||||
if (!this._hass) return;
|
||||
|
||||
try {
|
||||
@@ -336,7 +351,6 @@ class OpenClawChatCard extends HTMLElement {
|
||||
|
||||
this._wakeWordEnabled = !!result?.wake_word_enabled;
|
||||
this._wakeWord = (result?.wake_word || "hey openclaw").toString().trim().toLowerCase();
|
||||
this._alwaysVoiceMode = !!result?.always_voice_mode;
|
||||
this._allowBraveWebSpeechIntegration = !!result?.allow_brave_webspeech;
|
||||
this._voiceProviderIntegration =
|
||||
result?.voice_provider === "assist_stt" ? "assist_stt" : "browser";
|
||||
@@ -344,7 +358,8 @@ class OpenClawChatCard extends HTMLElement {
|
||||
? this._normalizeSpeechLanguage(result.language)
|
||||
: null;
|
||||
|
||||
try {
|
||||
if (includePipeline) {
|
||||
try {
|
||||
let pipelineResult;
|
||||
if (typeof this._hass.callWS === "function") {
|
||||
pipelineResult = await this._hass.callWS({ type: "assist_pipeline/pipeline/list" });
|
||||
@@ -372,13 +387,9 @@ class OpenClawChatCard extends HTMLElement {
|
||||
if (ttsLanguage) {
|
||||
this._integrationTtsLanguage = this._normalizeSpeechLanguage(ttsLanguage);
|
||||
}
|
||||
} catch (pipelineErr) {
|
||||
console.debug("OpenClaw: assist pipeline language detection skipped:", pipelineErr);
|
||||
}
|
||||
|
||||
if (this._alwaysVoiceMode && !this._isVoiceMode) {
|
||||
this._isVoiceMode = true;
|
||||
this._startVoiceRecognition();
|
||||
} catch (pipelineErr) {
|
||||
console.debug("OpenClaw: assist pipeline language detection skipped:", pipelineErr);
|
||||
}
|
||||
}
|
||||
this._render();
|
||||
} catch (err) {
|
||||
@@ -1162,7 +1173,8 @@ class OpenClawChatCard extends HTMLElement {
|
||||
}, delayMs);
|
||||
}
|
||||
|
||||
_toggleVoiceMode() {
|
||||
async _toggleVoiceMode() {
|
||||
await this._loadIntegrationSettings(false);
|
||||
this._isVoiceMode = !this._isVoiceMode;
|
||||
if (this._isVoiceMode) {
|
||||
this._startVoiceRecognition();
|
||||
@@ -1303,6 +1315,7 @@ class OpenClawChatCard extends HTMLElement {
|
||||
.join("");
|
||||
|
||||
const voiceActive = this._recognition !== null;
|
||||
const gatewayState = this._getGatewayConnectionState();
|
||||
|
||||
this.shadowRoot.innerHTML = `
|
||||
<style>
|
||||
@@ -1332,6 +1345,24 @@ class OpenClawChatCard extends HTMLElement {
|
||||
font-weight: 500;
|
||||
color: var(--oc-text);
|
||||
}
|
||||
.header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.gateway-status {
|
||||
font-size: 11px;
|
||||
border: 1px solid var(--oc-border);
|
||||
border-radius: 999px;
|
||||
padding: 2px 8px;
|
||||
color: var(--oc-text-secondary);
|
||||
}
|
||||
.gateway-status.online {
|
||||
color: #10b981;
|
||||
}
|
||||
.gateway-status.offline {
|
||||
color: #ef4444;
|
||||
}
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
@@ -1503,7 +1534,10 @@ class OpenClawChatCard extends HTMLElement {
|
||||
|
||||
<ha-card>
|
||||
<div class="header">
|
||||
<h3>${this._escapeHtml(config.title)}</h3>
|
||||
<div class="header-title">
|
||||
<h3>${this._escapeHtml(config.title)}</h3>
|
||||
<span class="gateway-status ${gatewayState.css}">Gateway: ${gatewayState.label}</span>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
${
|
||||
config.show_voice_button
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(async () => {
|
||||
try {
|
||||
if (!customElements.get("openclaw-chat-card")) {
|
||||
const src = "/openclaw/openclaw-chat-card.js?v=0.1.40";
|
||||
const src = "/openclaw/openclaw-chat-card.js?v=0.1.41";
|
||||
console.info("OpenClaw loader importing", src);
|
||||
await import(src);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user