Add HTTPS/SSL support and enhance connection handling

- Implemented HTTPS/SSL support for OpenClaw gateways in `lan_https` mode.
- Added configuration options for SSL certificate verification.
- Improved auto-discovery for `lan_https` access mode.
- Updated documentation in README and changelog for new features and fixes.
- Fixed connection errors related to SSL certificate verification.
This commit is contained in:
techartdev
2026-02-23 11:49:21 +02:00
parent 8f82526bb9
commit 99aaef05c1
10 changed files with 150 additions and 17 deletions
+12
View File
@@ -2,6 +2,18 @@
All notable changes to the OpenClaw Home Assistant Integration will be documented in this file.
## [0.1.52] - 2026-02-23
### Added
- Added HTTPS / SSL support for connecting to OpenClaw gateways running in `lan_https` mode or behind TLS reverse proxies.
- Auto-discovery now detects `access_mode: lan_https` and connects to the internal gateway port automatically (no certificate setup needed for local addons).
- Added `Verify SSL certificate` option in manual config for self-signed certificate environments.
- Added `ssl_error` config flow error with actionable guidance.
- Added comprehensive remote connection documentation to README with setup table for all access modes.
### Fixed
- Fixed "400 Bad Request — plain HTTP request was sent to HTTPS port" when the addon uses `lan_https` access mode.
## [0.1.51] - 2026-02-23
### Fixed
+67 -3
View File
@@ -38,10 +38,68 @@ OpenClaw is a Home Assistant custom integration that connects your HA instance t
## Requirements
- Home Assistant Core `2025.1.0+` (declared minimum)
- Supervisor is optional (used for auto-discovery)
- OpenClaw Assistant addon installed and running
- An **OpenClaw gateway** with `enable_openai_api` enabled — either:
- The [OpenClaw Assistant addon](https://github.com/techartdev/OpenClawHomeAssistant) running on the same HA instance (auto-discovery supported), **or**
- Any standalone [OpenClaw](https://github.com/openclaw/openclaw) installation reachable over the network (manual config)
- Supervisor is optional (used only for addon auto-discovery)
The integration can auto-detect the addon when Supervisor is available. You can always configure host/port/token manually.
> **No addon required.** If you have OpenClaw running anywhere — on a separate server, a VPS, a Docker container, or even another machine on your LAN — this integration can connect to it via the manual configuration flow.
---
## Connection modes
The integration supports connecting to OpenClaw in several ways:
### Local addon (auto-discovery)
If the OpenClaw Assistant addon is installed on the **same** Home Assistant instance, the integration auto-discovers it:
- Reads token from the shared filesystem
- Detects `access_mode` and chooses the correct port automatically
- No manual config needed — just click **Submit** on the confirm step
> **`lan_https` mode**: The integration automatically connects to the internal gateway port (plain HTTP on loopback), bypassing the HTTPS proxy entirely. No certificate setup required.
### Remote or standalone OpenClaw instance (manual config)
You can connect to **any reachable OpenClaw gateway** — whether it's the HA addon on another machine, a standalone `openclaw` install on a VPS, or a Docker container on your LAN. The integration doesn't care how OpenClaw is installed; it only needs the `/v1/chat/completions` endpoint.
**Prerequisites on the OpenClaw instance:**
1. The OpenAI-compatible API must be **enabled**:
- **Addon users**: Set `enable_openai_api: true` in addon settings
- **Standalone users**: Set `gateway.http.endpoints.chatCompletions.enabled: true` in `openclaw.json`, or run:
```sh
openclaw config set gateway.http.endpoints.chatCompletions.enabled true
```
2. The gateway must be **network-reachable** from your HA instance (not bound to loopback only)
3. You need the **gateway auth token**:
```sh
openclaw config get gateway.auth.token
```
**Setup steps:**
1. Go to **Settings → Devices & Services → Add Integration → OpenClaw**
2. Auto-discovery will fail (no local addon) — you'll see the **Manual Configuration** form
3. Fill in:
- **Gateway Host**: IP or hostname of the remote machine (e.g. `192.168.1.50`)
- **Gateway Port**: The gateway port (default `18789`)
- **Gateway Token**: Auth token from the remote `openclaw.json`
- **Use SSL (HTTPS)**: Check if connecting to an HTTPS endpoint
- **Verify SSL certificate**: Uncheck for self-signed certificates (e.g. `lan_https` mode)
### Common remote scenarios
| Remote access mode | Host | Port | Use SSL | Verify SSL | Notes |
|---|---|---|---|---|---|
| Standalone OpenClaw (plain HTTP on LAN) | Remote IP | 18789 | ❌ | — | Default `openclaw gateway run` config |
| `lan_https` (addon built-in HTTPS proxy) | Remote IP | 18789 | ✅ | ❌ | Self-signed cert; disable verification |
| Behind reverse proxy (NPM/Caddy with Let's Encrypt) | Domain or IP | 443 | ✅ | ✅ | Trusted cert from a real CA |
| Plain HTTP addon on LAN | Remote IP | 18789 | ❌ | — | Addon `bind_mode` must be `lan` |
| Tailscale | Tailscale IP | 18789 | ❌ | — | Encrypted tunnel; plain HTTP is fine |
> **Security note**: Avoid exposing plain HTTP gateways to the public internet. Use `lan_https`, a reverse proxy with TLS, or Tailscale for remote access.
---
@@ -307,6 +365,12 @@ action:
- Verify `openclaw_message_received` is being fired in Developer Tools → Events
- Confirm session IDs match between card and service calls
### "400 Bad Request — plain HTTP request was sent to HTTPS port"
- The gateway is running in `lan_https` mode (built-in HTTPS proxy)
- **Local addon**: Remove and re-add the integration — auto-discovery now detects `lan_https` and uses the correct internal port automatically
- **Remote connection**: Enable **Use SSL (HTTPS)** and disable **Verify SSL certificate** in the manual config
---
## Development notes
+7 -3
View File
@@ -51,6 +51,7 @@ from .const import (
CONF_GATEWAY_PORT,
CONF_GATEWAY_TOKEN,
CONF_USE_SSL,
CONF_VERIFY_SSL,
CONF_CONTEXT_MAX_CHARS,
CONF_CONTEXT_STRATEGY,
CONF_ENABLE_TOOL_CALLS,
@@ -92,7 +93,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.51"
_CARD_URL = f"{_CARD_STATIC_URL}?v=0.1.52"
OpenClawConfigEntry = ConfigEntry
@@ -130,13 +131,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: OpenClawConfigEntry) ->
Creates the API client, coordinator, and forwards setup to platforms.
"""
session = async_get_clientsession(hass)
use_ssl = entry.data.get(CONF_USE_SSL, False)
verify_ssl = entry.data.get(CONF_VERIFY_SSL, True)
session = async_get_clientsession(hass, verify_ssl=verify_ssl)
client = OpenClawApiClient(
host=entry.data[CONF_GATEWAY_HOST],
port=entry.data[CONF_GATEWAY_PORT],
token=entry.data[CONF_GATEWAY_TOKEN],
use_ssl=entry.data.get(CONF_USE_SSL, False),
use_ssl=use_ssl,
verify_ssl=verify_ssl,
session=session,
)
+19
View File
@@ -49,6 +49,7 @@ class OpenClawApiClient:
port: int,
token: str,
use_ssl: bool = False,
verify_ssl: bool = True,
session: aiohttp.ClientSession | None = None,
) -> None:
"""Initialize the API client.
@@ -58,14 +59,19 @@ class OpenClawApiClient:
port: Gateway port number.
token: Authentication token from openclaw.json.
use_ssl: Use HTTPS instead of HTTP.
verify_ssl: Verify SSL certificates (set False for self-signed certs).
session: Optional aiohttp session (reused from HA).
"""
self._host = host
self._port = port
self._token = token
self._use_ssl = use_ssl
self._verify_ssl = verify_ssl
self._session = session
self._base_url = f"{'https' if use_ssl else 'http'}://{host}:{port}"
# ssl=False disables cert verification for self-signed certs;
# ssl=None uses default verification.
self._ssl_param: bool | None = False if (use_ssl and not verify_ssl) else None
@property
def base_url(self) -> str:
@@ -121,6 +127,7 @@ class OpenClawApiClient:
url,
headers=self._headers(),
timeout=timeout,
ssl=self._ssl_param,
**kwargs,
) as resp:
if resp.status == 401:
@@ -146,6 +153,13 @@ class OpenClawApiClient:
)
return await resp.json()
except aiohttp.ClientConnectorCertificateError as err:
raise OpenClawConnectionError(
f"SSL certificate verification failed for {url}. "
f"If using self-signed certificates (e.g. lan_https mode), "
f"disable 'Verify SSL certificate' in the integration config. "
f"Error: {err}"
) from err
except (aiohttp.ClientConnectorError, aiohttp.ClientOSError, asyncio.TimeoutError) as err:
raise OpenClawConnectionError(
f"Cannot connect to OpenClaw gateway at {url}: {err}"
@@ -213,6 +227,7 @@ class OpenClawApiClient:
headers=headers,
json=payload,
timeout=STREAM_TIMEOUT,
ssl=self._ssl_param,
) as resp:
if resp.status == 401:
raise OpenClawAuthError("Authentication failed")
@@ -274,6 +289,7 @@ class OpenClawApiClient:
headers=headers,
json=payload,
timeout=STREAM_TIMEOUT,
ssl=self._ssl_param,
) as resp:
if resp.status == 401:
raise OpenClawAuthError("Authentication failed")
@@ -338,6 +354,7 @@ class OpenClawApiClient:
headers=self._headers(),
json={"messages": [], "stream": False},
timeout=API_TIMEOUT,
ssl=self._ssl_param,
) as resp:
if resp.status in (401, 403):
raise OpenClawAuthError(
@@ -381,6 +398,7 @@ class OpenClawApiClient:
async with session.get(
self._base_url,
timeout=API_TIMEOUT,
ssl=self._ssl_param,
) as resp:
return resp.status < 500
except (aiohttp.ClientConnectorError, aiohttp.ClientOSError, asyncio.TimeoutError) as err:
@@ -424,6 +442,7 @@ class OpenClawApiClient:
headers=headers,
json=payload,
timeout=STREAM_TIMEOUT,
ssl=self._ssl_param,
) as resp:
if resp.status == 401:
raise OpenClawAuthError("Authentication failed")
+35 -4
View File
@@ -38,6 +38,7 @@ from .const import (
CONF_GATEWAY_PORT,
CONF_GATEWAY_TOKEN,
CONF_USE_SSL,
CONF_VERIFY_SSL,
CONF_CONTEXT_MAX_CHARS,
CONF_CONTEXT_STRATEGY,
CONF_ENABLE_TOOL_CALLS,
@@ -233,11 +234,30 @@ async def _async_try_discover_addon(hass: HomeAssistant) -> dict[str, Any] | Non
if port is None:
port = DEFAULT_GATEWAY_PORT
# Detect access mode — lan_https uses a built-in HTTPS reverse proxy.
# In that mode nginx terminates TLS on the external (user-facing) port
# and the gateway itself listens on port+1 on loopback (plain HTTP).
# Since HA and the addon share host_network, we connect to the internal
# port via loopback to avoid self-signed certificate issues entirely.
access_mode = addon_options.get("access_mode", "custom")
use_ssl = False
verify_ssl = True
if access_mode == "lan_https":
_LOGGER.info(
"Detected access_mode=lan_https — using internal gateway port %d "
"(external HTTPS proxy is on port %d)",
port + 1,
port,
)
port = port + 1
return {
CONF_GATEWAY_HOST: DEFAULT_GATEWAY_HOST,
CONF_GATEWAY_PORT: port,
CONF_GATEWAY_TOKEN: token,
CONF_USE_SSL: False,
CONF_USE_SSL: use_ssl,
CONF_VERIFY_SSL: verify_ssl,
CONF_ADDON_CONFIG_PATH: str(config_dir),
}
@@ -248,14 +268,16 @@ async def _async_validate_connection(
port: int,
token: str,
use_ssl: bool = False,
verify_ssl: bool = True,
) -> bool:
"""Validate that we can connect and authenticate to the gateway."""
session = async_get_clientsession(hass)
session = async_get_clientsession(hass, verify_ssl=verify_ssl)
client = OpenClawApiClient(
host=host,
port=port,
token=token,
use_ssl=use_ssl,
verify_ssl=verify_ssl,
session=session,
)
return await client.async_check_connection()
@@ -312,6 +334,7 @@ class OpenClawConfigFlow(ConfigFlow, domain=DOMAIN):
self._discovered[CONF_GATEWAY_PORT],
self._discovered[CONF_GATEWAY_TOKEN],
self._discovered.get(CONF_USE_SSL, False),
self._discovered.get(CONF_VERIFY_SSL, True),
)
except OpenClawAuthError:
connected = False
@@ -355,17 +378,23 @@ class OpenClawConfigFlow(ConfigFlow, domain=DOMAIN):
port = user_input[CONF_GATEWAY_PORT]
token = user_input[CONF_GATEWAY_TOKEN]
use_ssl = user_input.get(CONF_USE_SSL, False)
verify_ssl = user_input.get(CONF_VERIFY_SSL, True)
try:
connected = await _async_validate_connection(
self.hass, host, port, token, use_ssl
self.hass, host, port, token, use_ssl, verify_ssl
)
except OpenClawAuthError:
errors["base"] = "invalid_auth"
connected = False
except OpenClawConnectionError:
except OpenClawConnectionError as err:
err_msg = str(err).lower()
if "ssl" in err_msg or "certificate" in err_msg:
errors["base"] = "ssl_error"
else:
errors["base"] = "cannot_connect"
connected = False
_LOGGER.warning("Connection error during config check: %s", err)
except OpenClawApiError as err:
errors["base"] = "openai_api_disabled"
connected = False
@@ -379,6 +408,7 @@ class OpenClawConfigFlow(ConfigFlow, domain=DOMAIN):
CONF_GATEWAY_PORT: port,
CONF_GATEWAY_TOKEN: token,
CONF_USE_SSL: use_ssl,
CONF_VERIFY_SSL: verify_ssl,
},
)
if "base" not in errors:
@@ -396,6 +426,7 @@ class OpenClawConfigFlow(ConfigFlow, domain=DOMAIN):
): vol.All(int, vol.Range(min=1, max=65535)),
vol.Required(CONF_GATEWAY_TOKEN): str,
vol.Optional(CONF_USE_SSL, default=False): bool,
vol.Optional(CONF_VERIFY_SSL, default=True): bool,
}
),
errors=errors,
+1
View File
@@ -21,6 +21,7 @@ CONF_GATEWAY_HOST = "gateway_host"
CONF_GATEWAY_PORT = "gateway_port"
CONF_GATEWAY_TOKEN = "gateway_token"
CONF_USE_SSL = "use_ssl"
CONF_VERIFY_SSL = "verify_ssl"
CONF_ADDON_CONFIG_PATH = "addon_config_path"
# Options
+1 -1
View File
@@ -8,7 +8,7 @@
"iot_class": "local_polling",
"issue_tracker": "https://github.com/techartdev/OpenClawHomeAssistant/issues",
"requirements": [],
"version": "0.1.51",
"version": "0.1.52",
"dependencies": ["conversation"],
"after_dependencies": ["hassio", "lovelace"]
}
+3 -3
View File
@@ -17,14 +17,14 @@
"gateway_host": "Gateway Host",
"gateway_port": "Gateway Port",
"gateway_token": "Gateway Token",
"use_ssl": "Use SSL (HTTPS)"
"use_ssl": "Use SSL (HTTPS)",
"verify_ssl": "Verify SSL certificate"
}
}
},
"error": {
"cannot_connect": "Cannot connect to the OpenClaw gateway. Ensure the addon is running.",
"invalid_auth": "Invalid gateway token. Check your OpenClaw configuration.",
"openai_api_disabled": "The gateway returned an unexpected response — the OpenAI-compatible API is likely disabled. In the OpenClaw addon settings enable 'enable_openai_api', restart the addon, and try again.",
"invalid_auth": "Invalid gateway token. Check your OpenClaw configuration.", "ssl_error": "SSL certificate verification failed. If using self-signed certificates (e.g. lan_https mode), uncheck 'Verify SSL certificate' or use automatic discovery.", "openai_api_disabled": "The gateway returned an unexpected response — the OpenAI-compatible API is likely disabled. In the OpenClaw addon settings enable 'enable_openai_api', restart the addon, and try again.",
"unknown": "An unexpected error occurred."
},
"abort": {
@@ -17,13 +17,15 @@
"gateway_host": "Gateway Host",
"gateway_port": "Gateway Port",
"gateway_token": "Gateway Token",
"use_ssl": "Use SSL (HTTPS)"
"use_ssl": "Use SSL (HTTPS)",
"verify_ssl": "Verify SSL certificate"
}
}
},
"error": {
"cannot_connect": "Cannot connect to the OpenClaw gateway. Ensure the addon is running.",
"invalid_auth": "Invalid gateway token. Check your OpenClaw configuration.",
"ssl_error": "SSL certificate verification failed. If using self-signed certificates (e.g. lan_https mode), uncheck 'Verify SSL certificate' or use automatic discovery.",
"openai_api_disabled": "The gateway returned an unexpected response — the OpenAI-compatible API is likely disabled. In the OpenClaw addon settings enable 'enable_openai_api', restart the addon, and try again.",
"unknown": "An unexpected error occurred."
},
+1 -1
View File
@@ -1,7 +1,7 @@
(async () => {
try {
if (!customElements.get("openclaw-chat-card")) {
const src = "/openclaw/openclaw-chat-card.js?v=0.1.51";
const src = "/openclaw/openclaw-chat-card.js?v=0.1.52";
console.info("OpenClaw loader importing", src);
await import(src);
}