mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-13 12:16:29 +00:00
Initial WS support (#2439)
* Initial WS support * test * Update frontend to fc7c4af2 * Fix issue with closing states * log error * make data optional * limit stopping states * Move wrappers to HomeAssistantWebSocket * use info * Use call_soon * Use lookuptable for WS commands * Fix tests
This commit is contained in:
parent
c342231052
commit
b31ecfefcd
@ -1 +1 @@
|
|||||||
Subproject commit 03d4174163166fa420b0cc7077ef2fa469f1164d
|
Subproject commit fc7c4af27ac0d8ccfa0d23b2405b7523adcb0f92
|
@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
new Function("import('/api/hassio/app/frontend_latest/entrypoint.5c3eb78c.js')")();
|
new Function("import('/api/hassio/app/frontend_latest/entrypoint.17d3e180.js')")();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
var el = document.createElement('script');
|
var el = document.createElement('script');
|
||||||
el.src = '/api/hassio/app/frontend_es5/entrypoint.9603bcfc.js';
|
el.src = '/api/hassio/app/frontend_es5/entrypoint.1e6aec4d.js';
|
||||||
document.body.appendChild(el);
|
document.body.appendChild(el);
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"chunk.165d124a6f3e8a41abb2.js","sources":["webpack://home-assistant-frontend/chunk.165d124a6f3e8a41abb2.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"chunk.581c71aa259dd86618ad.js","sources":["webpack://home-assistant-frontend/chunk.581c71aa259dd86618ad.js"],"mappings":"AAAA","sourceRoot":""}
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"chunk.ca8af569f5af755b6c71.js","sources":["webpack://home-assistant-frontend/chunk.ca8af569f5af755b6c71.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"chunk.d0222f0b152d21991ec4.js","sources":["webpack://home-assistant-frontend/chunk.d0222f0b152d21991ec4.js"],"mappings":"AAAA","sourceRoot":""}
|
|
3
supervisor/api/panel/frontend_es5/entrypoint.1e6aec4d.js
Normal file
3
supervisor/api/panel/frontend_es5/entrypoint.1e6aec4d.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/entrypoint.1e6aec4d.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/entrypoint.1e6aec4d.js.gz
Normal file
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"entrypoint.1e6aec4d.js","sources":["webpack://home-assistant-frontend/entrypoint.1e6aec4d.js"],"mappings":";AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"entrypoint.9603bcfc.js","sources":["webpack://home-assistant-frontend/entrypoint.9603bcfc.js"],"mappings":";AAAA","sourceRoot":""}
|
|
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"entrypoint.js": "/api/hassio/app/frontend_es5/entrypoint.9603bcfc.js"
|
"entrypoint.js": "/api/hassio/app/frontend_es5/entrypoint.1e6aec4d.js"
|
||||||
}
|
}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +1 @@
|
|||||||
{"version":3,"file":"chunk.608800a294b1d02a0765.js","sources":["webpack://home-assistant-frontend/chunk.608800a294b1d02a0765.js"],"mappings":"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","sourceRoot":""}
|
{"version":3,"file":"chunk.2ec57cdfeeb3748fc49f.js","sources":["webpack://home-assistant-frontend/chunk.2ec57cdfeeb3748fc49f.js"],"mappings":"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","sourceRoot":""}
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"chunk.c7a82086c5d680500dd2.js","sources":["webpack://home-assistant-frontend/chunk.c7a82086c5d680500dd2.js"],"mappings":"AAAA;;AAqMA;AACA;;AAEA;;AAEA;;AAEA;AAjBA;AACA;AACA;;AAEA;AAeA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyIA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"chunk.e24a51f3c66fd6498ace.js","sources":["webpack://home-assistant-frontend/chunk.e24a51f3c66fd6498ace.js"],"mappings":"AAAA;;AAyKA;AACA;;AAEA;;AAEA;;AAEA;AAjBA;AACA;AACA;;AAEA;AAeA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyIA","sourceRoot":""}
|
|
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_latest/entrypoint.17d3e180.js.gz
Normal file
BIN
supervisor/api/panel/frontend_latest/entrypoint.17d3e180.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"entrypoint.js": "/api/hassio/app/frontend_latest/entrypoint.5c3eb78c.js"
|
"entrypoint.js": "/api/hassio/app/frontend_latest/entrypoint.17d3e180.js"
|
||||||
}
|
}
|
@ -55,6 +55,9 @@ class Core(CoreSysAttributes):
|
|||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
self._state = new_state
|
self._state = new_state
|
||||||
|
self.sys_homeassistant.websocket.supervisor_update_event(
|
||||||
|
"info", {"state": new_state}
|
||||||
|
)
|
||||||
|
|
||||||
async def connect(self):
|
async def connect(self):
|
||||||
"""Connect Supervisor container."""
|
"""Connect Supervisor container."""
|
||||||
|
@ -39,6 +39,14 @@ class HomeAssistantAuthError(HomeAssistantAPIError):
|
|||||||
"""Home Assistant Auth API exception."""
|
"""Home Assistant Auth API exception."""
|
||||||
|
|
||||||
|
|
||||||
|
class HomeAssistantWSError(HomeAssistantAPIError):
|
||||||
|
"""Home Assistant websocket error."""
|
||||||
|
|
||||||
|
|
||||||
|
class HomeAssistantWSNotSupported(HomeAssistantWSError):
|
||||||
|
"""Raise when WebSockets are not supported."""
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistantJobError(HomeAssistantError, JobException):
|
class HomeAssistantJobError(HomeAssistantError, JobException):
|
||||||
"""Raise on Home Assistant job error."""
|
"""Raise on Home Assistant job error."""
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ from ..validate import SCHEMA_HASS_CONFIG
|
|||||||
from .api import HomeAssistantAPI
|
from .api import HomeAssistantAPI
|
||||||
from .core import HomeAssistantCore
|
from .core import HomeAssistantCore
|
||||||
from .secrets import HomeAssistantSecrets
|
from .secrets import HomeAssistantSecrets
|
||||||
|
from .websocket import HomeAssistantWebSocket
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG)
|
super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG)
|
||||||
self.coresys: CoreSys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self._api: HomeAssistantAPI = HomeAssistantAPI(coresys)
|
self._api: HomeAssistantAPI = HomeAssistantAPI(coresys)
|
||||||
|
self._websocket: HomeAssistantWebSocket = HomeAssistantWebSocket(coresys)
|
||||||
self._core: HomeAssistantCore = HomeAssistantCore(coresys)
|
self._core: HomeAssistantCore = HomeAssistantCore(coresys)
|
||||||
self._secrets: HomeAssistantSecrets = HomeAssistantSecrets(coresys)
|
self._secrets: HomeAssistantSecrets = HomeAssistantSecrets(coresys)
|
||||||
|
|
||||||
@ -50,6 +52,11 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
"""Return API handler for core."""
|
"""Return API handler for core."""
|
||||||
return self._api
|
return self._api
|
||||||
|
|
||||||
|
@property
|
||||||
|
def websocket(self) -> HomeAssistantWebSocket:
|
||||||
|
"""Return Websocket handler for core."""
|
||||||
|
return self._websocket
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def core(self) -> HomeAssistantCore:
|
def core(self) -> HomeAssistantCore:
|
||||||
"""Return Core handler for docker."""
|
"""Return Core handler for docker."""
|
||||||
|
167
supervisor/homeassistant/websocket.py
Normal file
167
supervisor/homeassistant/websocket.py
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
"""Home Assistant Websocket API."""
|
||||||
|
import logging
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
from awesomeversion import AwesomeVersion
|
||||||
|
|
||||||
|
from ..const import CoreState
|
||||||
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
|
from ..exceptions import (
|
||||||
|
HomeAssistantAPIError,
|
||||||
|
HomeAssistantWSError,
|
||||||
|
HomeAssistantWSNotSupported,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CLOSING_STATES = [
|
||||||
|
CoreState.SHUTDOWN,
|
||||||
|
CoreState.STOPPING,
|
||||||
|
CoreState.CLOSE,
|
||||||
|
]
|
||||||
|
|
||||||
|
MIN_VERSION = {"supervisor/event": "2021.2.4"}
|
||||||
|
|
||||||
|
|
||||||
|
class WSClient:
|
||||||
|
"""Home Assistant Websocket client."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, ha_version: AwesomeVersion, client: aiohttp.ClientWebSocketResponse
|
||||||
|
):
|
||||||
|
"""Initialise the WS client."""
|
||||||
|
self.ha_version = ha_version
|
||||||
|
self.client = client
|
||||||
|
self.message_id = 0
|
||||||
|
|
||||||
|
async def async_send_command(self, message: Dict[str, Any]):
|
||||||
|
"""Send a websocket command."""
|
||||||
|
self.message_id += 1
|
||||||
|
message["id"] = self.message_id
|
||||||
|
|
||||||
|
_LOGGER.debug("Sending: %s", message)
|
||||||
|
try:
|
||||||
|
await self.client.send_json(message)
|
||||||
|
except HomeAssistantWSNotSupported:
|
||||||
|
return
|
||||||
|
|
||||||
|
response = await self.client.receive_json()
|
||||||
|
|
||||||
|
_LOGGER.debug("Received: %s", response)
|
||||||
|
|
||||||
|
if response["success"]:
|
||||||
|
return response["result"]
|
||||||
|
|
||||||
|
raise HomeAssistantWSError(response)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def connect_with_auth(
|
||||||
|
cls, session: aiohttp.ClientSession, url: str, token: str
|
||||||
|
) -> "WSClient":
|
||||||
|
"""Create an authenticated websocket client."""
|
||||||
|
try:
|
||||||
|
client = await session.ws_connect(url)
|
||||||
|
except aiohttp.client_exceptions.ClientConnectorError:
|
||||||
|
raise HomeAssistantWSError("Can't connect") from None
|
||||||
|
|
||||||
|
hello_message = await client.receive_json()
|
||||||
|
|
||||||
|
try:
|
||||||
|
await client.send_json({"type": "auth", "access_token": token})
|
||||||
|
except HomeAssistantWSNotSupported:
|
||||||
|
return
|
||||||
|
|
||||||
|
auth_ok_message = await client.receive_json()
|
||||||
|
|
||||||
|
if auth_ok_message["type"] != "auth_ok":
|
||||||
|
raise HomeAssistantAPIError("AUTH NOT OK")
|
||||||
|
|
||||||
|
return cls(AwesomeVersion(hello_message["ha_version"]), client)
|
||||||
|
|
||||||
|
|
||||||
|
class HomeAssistantWebSocket(CoreSysAttributes):
|
||||||
|
"""Home Assistant Websocket API."""
|
||||||
|
|
||||||
|
def __init__(self, coresys: CoreSys):
|
||||||
|
"""Initialize Home Assistant object."""
|
||||||
|
self.coresys: CoreSys = coresys
|
||||||
|
self._client: Optional[WSClient] = None
|
||||||
|
|
||||||
|
async def _get_ws_client(self) -> WSClient:
|
||||||
|
"""Return a websocket client."""
|
||||||
|
await self.sys_homeassistant.api.ensure_access_token()
|
||||||
|
client = await WSClient.connect_with_auth(
|
||||||
|
self.sys_websession_ssl,
|
||||||
|
f"{self.sys_homeassistant.api_url}/api/websocket",
|
||||||
|
self.sys_homeassistant.api.access_token,
|
||||||
|
)
|
||||||
|
|
||||||
|
return client
|
||||||
|
|
||||||
|
async def async_send_command(self, message: Dict[str, Any]):
|
||||||
|
"""Send a command with the WS client."""
|
||||||
|
if self.sys_core.state in CLOSING_STATES:
|
||||||
|
raise HomeAssistantWSNotSupported(
|
||||||
|
f"Can't execute in a ${self.sys_core.state} state"
|
||||||
|
)
|
||||||
|
if not await self.sys_homeassistant.api.check_api_state():
|
||||||
|
# No core access, don't try.
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._client:
|
||||||
|
self._client = await self._get_ws_client()
|
||||||
|
|
||||||
|
message_type = message.get("type")
|
||||||
|
|
||||||
|
if (
|
||||||
|
message_type is not None
|
||||||
|
and message_type in MIN_VERSION
|
||||||
|
and self._client.ha_version < MIN_VERSION[message_type]
|
||||||
|
):
|
||||||
|
_LOGGER.info(
|
||||||
|
"WebSocket command %s is not supported untill core-%s. Ignoring WebSocket message.",
|
||||||
|
message_type,
|
||||||
|
MIN_VERSION[message_type],
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await self._client.async_send_command(message)
|
||||||
|
except HomeAssistantAPIError as err:
|
||||||
|
raise HomeAssistantWSError from err
|
||||||
|
|
||||||
|
async def async_supervisor_update_event(
|
||||||
|
self, key: str, data: Optional[Dict[str, Any]] = None
|
||||||
|
):
|
||||||
|
"""Send a supervisor/event command."""
|
||||||
|
try:
|
||||||
|
await self.async_send_command(
|
||||||
|
{
|
||||||
|
"type": "supervisor/event",
|
||||||
|
"data": {
|
||||||
|
"event": "supervisor-update",
|
||||||
|
"update_key": key,
|
||||||
|
"data": data or {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except HomeAssistantWSNotSupported:
|
||||||
|
pass
|
||||||
|
except HomeAssistantWSError as err:
|
||||||
|
_LOGGER.error(err)
|
||||||
|
|
||||||
|
def supervisor_update_event(self, key: str, data: Optional[Dict[str, Any]] = None):
|
||||||
|
"""Send a supervisor/event command."""
|
||||||
|
if self.sys_core.state not in CLOSING_STATES:
|
||||||
|
self.sys_loop.call_soon(
|
||||||
|
self.sys_loop.create_task,
|
||||||
|
self.async_supervisor_update_event(key, data),
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_command(self, message: Dict[str, Any]):
|
||||||
|
"""Send a supervisor/event command."""
|
||||||
|
if self.sys_core.state not in CLOSING_STATES:
|
||||||
|
self.sys_loop.call_soon(
|
||||||
|
self.sys_loop.create_task, self.async_send_command(message)
|
||||||
|
)
|
@ -6,6 +6,7 @@ from uuid import uuid4
|
|||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from aiohttp.test_utils import TestClient
|
from aiohttp.test_utils import TestClient
|
||||||
|
from awesomeversion import AwesomeVersion
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from supervisor.api import RestAPI
|
from supervisor.api import RestAPI
|
||||||
@ -20,6 +21,12 @@ from tests.common import exists_fixture, load_fixture, load_json_fixture
|
|||||||
# pylint: disable=redefined-outer-name, protected-access
|
# pylint: disable=redefined-outer-name, protected-access
|
||||||
|
|
||||||
|
|
||||||
|
async def mock_async_return_true() -> bool:
|
||||||
|
"""Mock methods to return True."""
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def docker() -> DockerAPI:
|
def docker() -> DockerAPI:
|
||||||
"""Mock DockerAPI."""
|
"""Mock DockerAPI."""
|
||||||
@ -148,6 +155,12 @@ async def coresys(loop, docker, network_manager, aiohttp_client) -> CoreSys:
|
|||||||
coresys_obj.supervisor._connectivity = True
|
coresys_obj.supervisor._connectivity = True
|
||||||
coresys_obj.host.network._connectivity = True
|
coresys_obj.host.network._connectivity = True
|
||||||
|
|
||||||
|
# WebSocket
|
||||||
|
coresys_obj.homeassistant.api.check_api_state = mock_async_return_true
|
||||||
|
coresys_obj.homeassistant._websocket._client = AsyncMock(
|
||||||
|
ha_version=AwesomeVersion("2021.2.4")
|
||||||
|
)
|
||||||
|
|
||||||
yield coresys_obj
|
yield coresys_obj
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,3 +2,4 @@
|
|||||||
|
|
||||||
TEST_INTERFACE = "eth0"
|
TEST_INTERFACE = "eth0"
|
||||||
TEST_INTERFACE_WLAN = "wlan0"
|
TEST_INTERFACE_WLAN = "wlan0"
|
||||||
|
TEST_WS_URL = "ws://test.org:3000"
|
||||||
|
49
tests/homeassistant/test_websocket.py
Normal file
49
tests/homeassistant/test_websocket.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
"""Test websocket."""
|
||||||
|
# pylint: disable=protected-access, import-error
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from awesomeversion import AwesomeVersion
|
||||||
|
|
||||||
|
from supervisor.coresys import CoreSys
|
||||||
|
|
||||||
|
|
||||||
|
async def test_send_command(coresys: CoreSys):
|
||||||
|
"""Test websocket error on listen."""
|
||||||
|
client = coresys.homeassistant.websocket._client
|
||||||
|
await coresys.homeassistant.websocket.async_send_command({"type": "test"})
|
||||||
|
client.async_send_command.assert_called_with({"type": "test"})
|
||||||
|
|
||||||
|
await coresys.homeassistant.websocket.async_supervisor_update_event(
|
||||||
|
"test", {"lorem": "ipsum"}
|
||||||
|
)
|
||||||
|
client.async_send_command.assert_called_with(
|
||||||
|
{
|
||||||
|
"type": "supervisor/event",
|
||||||
|
"data": {
|
||||||
|
"event": "supervisor-update",
|
||||||
|
"update_key": "test",
|
||||||
|
"data": {"lorem": "ipsum"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_send_command_old_core_version(coresys: CoreSys, caplog):
|
||||||
|
"""Test websocket error on listen."""
|
||||||
|
caplog.set_level(logging.INFO)
|
||||||
|
client = coresys.homeassistant.websocket._client
|
||||||
|
client.ha_version = AwesomeVersion("1970.1.1")
|
||||||
|
|
||||||
|
await coresys.homeassistant.websocket.async_send_command(
|
||||||
|
{"type": "supervisor/event"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"WebSocket command supervisor/event is not supported untill core-2021.2.4"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
||||||
|
await coresys.homeassistant.websocket.async_supervisor_update_event(
|
||||||
|
"test", {"lorem": "ipsum"}
|
||||||
|
)
|
||||||
|
client.async_send_command.assert_not_called()
|
Loading…
x
Reference in New Issue
Block a user