feat: OpenClaw HTTP bridge, HA conversation agent fixes, voice pipeline tooling
- Add openclaw-http-bridge.py: HTTP server translating POST requests to OpenClaw CLI calls - Add launchd plist for HTTP bridge (port 8081, auto-start) - Add install-to-docker-ha.sh: deploy custom component to Docker HA via SSH - Add package-for-ha.sh: create distributable tarball of custom component - Add test-services.sh: comprehensive voice pipeline service checker Fixes from code review: - Use OpenClawAgent (HTTP) in async_setup_entry instead of OpenClawCLIAgent (CLI agent fails inside Docker HA where openclaw binary doesn't exist) - Update all port references from 8080 to 8081 (HTTP bridge port) - Remove overly permissive CORS headers from HTTP bridge - Fix zombie process leak: kill child process on CLI timeout - Remove unused subprocess import in conversation.py - Add version field to Kokoro TTS Wyoming info - Update TODO.md with voice pipeline progress
This commit is contained in:
115
homeai-agent/custom_components/install-to-docker-ha.sh
Executable file
115
homeai-agent/custom_components/install-to-docker-ha.sh
Executable file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install OpenClaw Conversation component to Docker Home Assistant on 10.0.0.199
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
COMPONENT_NAME="openclaw_conversation"
|
||||
HA_HOST="${HA_HOST:-10.0.0.199}"
|
||||
HA_CONTAINER="${HA_CONTAINER:-homeassistant}"
|
||||
|
||||
echo "Installing OpenClaw Conversation to Docker Home Assistant"
|
||||
echo "=========================================================="
|
||||
echo "Host: $HA_HOST"
|
||||
echo "Container: $HA_CONTAINER"
|
||||
echo ""
|
||||
|
||||
# Check if we can reach the host
|
||||
if ! ping -c 1 -W 2 "$HA_HOST" &>/dev/null; then
|
||||
echo "Error: Cannot reach $HA_HOST"
|
||||
echo "Please ensure the server is accessible"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create temporary tarball
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
TARBALL="$TEMP_DIR/openclaw_conversation.tar.gz"
|
||||
|
||||
echo "Creating component archive..."
|
||||
cd "$SCRIPT_DIR"
|
||||
tar -czf "$TARBALL" \
|
||||
--exclude='*.pyc' \
|
||||
--exclude='__pycache__' \
|
||||
--exclude='.DS_Store' \
|
||||
"$COMPONENT_NAME"
|
||||
|
||||
echo "✓ Archive created: $(du -h "$TARBALL" | cut -f1)"
|
||||
echo ""
|
||||
|
||||
# Copy to remote host
|
||||
echo "Copying to $HA_HOST:/tmp/..."
|
||||
if scp -q "$TARBALL" "$HA_HOST:/tmp/openclaw_conversation.tar.gz"; then
|
||||
echo "✓ File copied successfully"
|
||||
else
|
||||
echo "✗ Failed to copy file"
|
||||
echo ""
|
||||
echo "Troubleshooting:"
|
||||
echo " 1. Ensure SSH access is configured: ssh $HA_HOST"
|
||||
echo " 2. Check SSH keys are set up"
|
||||
echo " 3. Try manual copy: scp $TARBALL $HA_HOST:/tmp/"
|
||||
rm -rf "$TEMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract into container
|
||||
echo ""
|
||||
echo "Installing into Home Assistant container..."
|
||||
ssh "$HA_HOST" << 'EOF'
|
||||
# Find the Home Assistant container
|
||||
CONTAINER=$(docker ps --filter "name=homeassistant" --format "{{.Names}}" | head -n 1)
|
||||
|
||||
if [ -z "$CONTAINER" ]; then
|
||||
echo "Error: Home Assistant container not found"
|
||||
echo "Available containers:"
|
||||
docker ps --format "{{.Names}}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found container: $CONTAINER"
|
||||
|
||||
# Copy tarball into container
|
||||
docker cp /tmp/openclaw_conversation.tar.gz "$CONTAINER:/tmp/"
|
||||
|
||||
# Extract into custom_components
|
||||
docker exec "$CONTAINER" sh -c '
|
||||
mkdir -p /config/custom_components
|
||||
cd /config/custom_components
|
||||
tar -xzf /tmp/openclaw_conversation.tar.gz
|
||||
rm /tmp/openclaw_conversation.tar.gz
|
||||
ls -la openclaw_conversation/
|
||||
'
|
||||
|
||||
# Cleanup
|
||||
rm /tmp/openclaw_conversation.tar.gz
|
||||
|
||||
echo ""
|
||||
echo "✓ Component installed successfully!"
|
||||
EOF
|
||||
|
||||
# Cleanup local temp
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
echo ""
|
||||
echo "=========================================================="
|
||||
echo "Installation complete!"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Restart Home Assistant:"
|
||||
echo " ssh $HA_HOST 'docker restart $HA_CONTAINER'"
|
||||
echo ""
|
||||
echo " 2. Open Home Assistant UI: http://$HA_HOST:8123"
|
||||
echo ""
|
||||
echo " 3. Go to Settings → Devices & Services → Add Integration"
|
||||
echo ""
|
||||
echo " 4. Search for 'OpenClaw Conversation'"
|
||||
echo ""
|
||||
echo " 5. Configure:"
|
||||
echo " - OpenClaw Host: 10.0.0.101 ⚠️ (Mac Mini IP, NOT $HA_HOST)"
|
||||
echo " - OpenClaw Port: 8081 (HTTP Bridge port)"
|
||||
echo " - Agent Name: main"
|
||||
echo " - Timeout: 30"
|
||||
echo ""
|
||||
echo " IMPORTANT: All services (OpenClaw, Wyoming STT/TTS/Satellite) run on"
|
||||
echo " 10.0.0.101 (Mac Mini), not $HA_HOST (HA server)"
|
||||
echo ""
|
||||
echo "See VOICE_PIPELINE_SETUP.md for complete configuration guide"
|
||||
@@ -52,12 +52,12 @@ if [[ -d "$TARGET_DIR" && -f "$TARGET_DIR/manifest.json" ]]; then
|
||||
echo " 1. Restart Home Assistant"
|
||||
echo " 2. Go to Settings → Devices & Services → Add Integration"
|
||||
echo " 3. Search for 'OpenClaw Conversation'"
|
||||
echo " 4. Configure the settings (host: localhost, port: 8080)"
|
||||
echo " 4. Configure the settings (host: localhost, port: 8081)"
|
||||
echo ""
|
||||
echo " Or add to configuration.yaml:"
|
||||
echo " openclaw_conversation:"
|
||||
echo " openclaw_host: localhost"
|
||||
echo " openclaw_port: 8080"
|
||||
echo " openclaw_port: 8081"
|
||||
echo " agent_name: main"
|
||||
echo " timeout: 30"
|
||||
else
|
||||
|
||||
@@ -26,7 +26,7 @@ A custom conversation agent for Home Assistant that routes all voice/text querie
|
||||
4. Search for "OpenClaw Conversation"
|
||||
5. Configure the settings:
|
||||
- **OpenClaw Host**: `localhost` (or IP of Mac Mini)
|
||||
- **OpenClaw Port**: `8080`
|
||||
- **OpenClaw Port**: `8081` (HTTP Bridge)
|
||||
- **Agent Name**: `main` (or your configured agent)
|
||||
- **Timeout**: `30` seconds
|
||||
|
||||
@@ -49,7 +49,7 @@ Add to your `configuration.yaml`:
|
||||
```yaml
|
||||
openclaw_conversation:
|
||||
openclaw_host: localhost
|
||||
openclaw_port: 8080
|
||||
openclaw_port: 8081
|
||||
agent_name: main
|
||||
timeout: 30
|
||||
```
|
||||
@@ -95,7 +95,7 @@ Once configured, the OpenClaw agent will be available as a conversation agent in
|
||||
|
||||
1. Verify OpenClaw host/port settings
|
||||
2. Ensure OpenClaw is accessible from HA container/host
|
||||
3. Check network connectivity: `curl http://localhost:8080/status`
|
||||
3. Check network connectivity: `curl http://localhost:8081/status`
|
||||
|
||||
## Files
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ from .const import (
|
||||
DEFAULT_TIMEOUT,
|
||||
DOMAIN,
|
||||
)
|
||||
from .conversation import OpenClawCLIAgent
|
||||
from .conversation import OpenClawAgent, OpenClawCLIAgent
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -76,11 +76,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
# Store entry data
|
||||
hass.data[DOMAIN][entry.entry_id] = entry.data
|
||||
|
||||
# Register the conversation agent
|
||||
agent = OpenClawCLIAgent(hass, entry.data)
|
||||
# Register the conversation agent (HTTP-based for cross-network access)
|
||||
agent = OpenClawAgent(hass, entry.data)
|
||||
|
||||
from homeassistant.components import conversation
|
||||
conversation.async_set_agent(hass, DOMAIN, agent)
|
||||
conversation.async_set_agent(hass, entry, agent)
|
||||
|
||||
_LOGGER.info("OpenClaw Conversation agent registered from config entry")
|
||||
|
||||
@@ -91,7 +91,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
# Unregister the conversation agent
|
||||
from homeassistant.components import conversation
|
||||
conversation.async_unset_agent(hass, DOMAIN)
|
||||
conversation.async_unset_agent(hass, entry)
|
||||
|
||||
hass.data[DOMAIN].pop(entry.entry_id, None)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ CONF_TIMEOUT = "timeout"
|
||||
|
||||
# Defaults
|
||||
DEFAULT_HOST = "localhost"
|
||||
DEFAULT_PORT = 8080
|
||||
DEFAULT_PORT = 8081 # OpenClaw HTTP Bridge (not 8080 gateway)
|
||||
DEFAULT_AGENT = "main"
|
||||
DEFAULT_TIMEOUT = 30
|
||||
|
||||
|
||||
@@ -187,8 +187,6 @@ class OpenClawCLIAgent(AbstractConversationAgent):
|
||||
|
||||
async def _call_openclaw_cli(self, message: str) -> str:
|
||||
"""Call OpenClaw CLI and return the response."""
|
||||
import subprocess
|
||||
|
||||
cmd = [
|
||||
"openclaw",
|
||||
"agent",
|
||||
@@ -196,6 +194,7 @@ class OpenClawCLIAgent(AbstractConversationAgent):
|
||||
"--agent", self.agent_name,
|
||||
]
|
||||
|
||||
proc = None
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*cmd,
|
||||
@@ -215,6 +214,9 @@ class OpenClawCLIAgent(AbstractConversationAgent):
|
||||
return stdout.decode().strip()
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
if proc is not None:
|
||||
proc.kill()
|
||||
await proc.wait()
|
||||
_LOGGER.error("Timeout calling OpenClaw CLI")
|
||||
return "I'm sorry, the request timed out."
|
||||
except FileNotFoundError:
|
||||
|
||||
46
homeai-agent/custom_components/package-for-ha.sh
Executable file
46
homeai-agent/custom_components/package-for-ha.sh
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
# Package OpenClaw Conversation component for Home Assistant installation
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
COMPONENT_NAME="openclaw_conversation"
|
||||
OUTPUT_DIR="$SCRIPT_DIR/dist"
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
ARCHIVE_NAME="openclaw_conversation_${TIMESTAMP}.tar.gz"
|
||||
|
||||
echo "Packaging OpenClaw Conversation component..."
|
||||
echo ""
|
||||
|
||||
# Create dist directory
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Create tarball
|
||||
cd "$SCRIPT_DIR"
|
||||
tar -czf "$OUTPUT_DIR/$ARCHIVE_NAME" \
|
||||
--exclude='*.pyc' \
|
||||
--exclude='__pycache__' \
|
||||
--exclude='.DS_Store' \
|
||||
"$COMPONENT_NAME"
|
||||
|
||||
# Create latest symlink
|
||||
cd "$OUTPUT_DIR"
|
||||
ln -sf "$ARCHIVE_NAME" openclaw_conversation_latest.tar.gz
|
||||
|
||||
echo "✓ Package created: $OUTPUT_DIR/$ARCHIVE_NAME"
|
||||
echo ""
|
||||
echo "Installation instructions:"
|
||||
echo ""
|
||||
echo "1. Copy to Home Assistant server:"
|
||||
echo " scp $OUTPUT_DIR/$ARCHIVE_NAME user@10.0.0.199:/tmp/"
|
||||
echo ""
|
||||
echo "2. SSH into Home Assistant server:"
|
||||
echo " ssh user@10.0.0.199"
|
||||
echo ""
|
||||
echo "3. Extract to custom_components:"
|
||||
echo " cd /config/custom_components"
|
||||
echo " tar -xzf /tmp/$ARCHIVE_NAME"
|
||||
echo ""
|
||||
echo "4. Restart Home Assistant"
|
||||
echo ""
|
||||
echo "Or use the install.sh script for automated installation."
|
||||
Reference in New Issue
Block a user