From 35fed7195f62ae6a737b83b5502e665c38681e70 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 17 May 2021 01:40:11 -0700 Subject: [PATCH] Update the WS API docs (#937) --- docs/api/websocket.md | 16 +--- docs/frontend/extending/websocket-api.md | 116 ++++++++--------------- 2 files changed, 46 insertions(+), 86 deletions(-) diff --git a/docs/api/websocket.md b/docs/api/websocket.md index 271e072b..ee3a320c 100644 --- a/docs/api/websocket.md +++ b/docs/api/websocket.md @@ -2,15 +2,7 @@ title: "WebSocket API" --- -Home Assistant contains a WebSocket API. This API can be used to stream information from a Home Assistant instance to any client that implements WebSockets. Implementations in different languages: - -- [JavaScript](https://github.com/home-assistant/home-assistant-js-websocket) - powers the frontend -- [Python](https://raw.githubusercontent.com/home-assistant/home-assistant-dev-helper/master/ha-websocket-client.py) - CLI client using [`asyncws`](https://async-websockets.readthedocs.io/en/latest/) -- [JavaScript/HTML](https://raw.githubusercontent.com/home-assistant/home-assistant-dev-helper/master/ha-websocket.html) - WebSocket connection in your browser - -Connect your websocket implementation to `ws://localhost:8123/api/websocket`. You will need a valid access token. - -If you are not using the [`frontend`](https://www.home-assistant.io/components/frontend/) in your setup then you need to add the [`websocket_api` component](https://www.home-assistant.io/components/websocket_api/) to your `configuration.yaml` file to use the WebSocket API. +Home Assistant contains a WebSocket API. This API can be used to stream information from a Home Assistant instance to any client that implements WebSockets. We maintain a [JavaScript library](https://github.com/home-assistant/home-assistant-js-websocket) which we use in our frontend. ## Server states @@ -61,7 +53,8 @@ When a client connects to the server, the server sends out `auth_required`. ```json { - "type": "auth_required" + "type": "auth_required", + "ha_version": "2021.5.3" } ``` @@ -78,7 +71,8 @@ If the client supplies valid authentication, the authentication phase will compl ```json { - "type": "auth_ok" + "type": "auth_ok", + "ha_version": "2021.5.3" } ``` diff --git a/docs/frontend/extending/websocket-api.md b/docs/frontend/extending/websocket-api.md index 9c4771ed..02c7109b 100644 --- a/docs/frontend/extending/websocket-api.md +++ b/docs/frontend/extending/websocket-api.md @@ -8,104 +8,73 @@ As a component you might have information that you want to make available to the To register a command, you need to have a message type, a message schema and a message handler. Your component does not have to add the websocket API as a dependency. You register your command, and if the user is using the websocket API, the command will be made available. -### Message Types +### Defining your command schema -Message types are made up the domain and the message type, separated by a forward slash. In the below example, we're defining `media_player/thumbnail`. +A command schema is made up of a message type and what type of data we expect when the command is invoked. You define both the command type and the data schema via a decorator on your command handler. Message handlers are callback functions that are run inside the event loop. ```python -# The type of the message -WS_TYPE_MEDIA_PLAYER_THUMBNAIL = "media_player/thumbnail" -``` - -### Message Schema - -The message schema defines what type of data we expect when the message is invoked. It is defined as a voluptuous schema and has to extend the base web socket command schema. - -```python -import voluptuous as vol - from homeassistant.components import websocket_api -import homeassistant.helpers.config_validation as cv - -# The schema for the message -SCHEMA_WEBSOCKET_GET_THUMBNAIL = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( +@websocket_api.websocket_command( { - "type": WS_TYPE_MEDIA_PLAYER_THUMBNAIL, - # The entity that we want to retrieve the thumbnail for. - "entity_id": cv.entity_id, + vol.Required("type"): "frontend/get_panels", + vol.Optional("preload_panels"): bool, } ) +@callback +def ws_get_panels( + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict +) -> None: + """Handle the websocket command.""" + panels = ... + connection.send_result(msg["id"], {"panels": panels}) ``` -### Defining a handler +#### Doing I/O or sending a delayed response -Message handlers are callback functions that are run inside the event loop. If you want to do I/O or have to wait for your result, create a new function and queue it up using `hass.async_add_job`. This is done so that the websocket API can get back to handling the next message as soon as possible. - -#### Sending a direct response - -If you are defining a command that is querying simple information, you might be able to fulfill the request while the handler is being called by the websocket API. To do this, use `connection.to_write.put_nowait`. +If your command needs to interact with the network, a device or needs to compute information, you will need to queue a job to do the work and send the response. To do this, make your function async and decorate with `@websocket_api.async_response`. ```python -@callback -def websocket_handle_thumbnail(hass, connection, msg): - """Handle getting a thumbnail.""" +from homeassistant.components import websocket_api - # We know the answer without having to fetch any information, - # so we send it directly. - connection.to_write.put_nowait( - websocket_api.result_message( - msg["id"], {"thumbnail": "http://via.placeholder.com/350x150"} - ) - ) -``` - -#### Sending a delayed response - -If your command needs to interact with the network, a device or needs to compute information, you will need to queue a job to do the work and send the response. To do this, use `connection.send_message_outside`. - -```python -@callback -def websocket_handle_thumbnail(hass, connection, msg): +@websocket_api.websocket_command( + { + vol.Required("type"): "camera/get_thumbnail", + vol.Optional("entity_id"): str, + } +) +@websocket_api.async_response +async def ws_handle_thumbnail( + hass: HomeAssistant, connection: ActiveConnection, msg: dict +) -> None: """Handle get media player cover command.""" # Retrieve media player using passed in entity id. player = hass.data[DOMAIN].get_entity(msg["entity_id"]) # If the player does not exist, send an error message. if player is None: - connection.to_write.put_nowait( - websocket_api.error_message( + connection.send_error( msg["id"], "entity_not_found", "Entity not found" ) ) return - # Define a function to be enqueued. - async def send_image(): - """Send image.""" - data, content_type = await player.async_get_media_image() + data, content_type = await player.async_get_media_image() - # No media player thumbnail available - if data is None: - connection.send_message_outside( - websocket_api.error_message( - msg["id"], "thumbnail_fetch_failed", "Failed to fetch thumbnail" - ) - ) - return - - connection.send_message_outside( - websocket_api.result_message( - msg["id"], - { - "content_type": content_type, - "content": base64.b64encode(data).decode("utf-8"), - }, - ) + # No media player thumbnail available + if data is None: + connection.send_error( + msg["id"], "thumbnail_fetch_failed", "Failed to fetch thumbnail" ) + return - # Player exist. Queue up a job to send the thumbnail. - hass.async_add_job(send_image()) + connection.send_result( + msg["id"], + { + "content_type": content_type, + "content": base64.b64encode(data).decode("utf-8"), + }, + ) ``` ### Registering with the Websocket API @@ -115,11 +84,8 @@ With all pieces defined, it's time to register the command. This is done inside ```python async def async_setup(hass, config): """Setup of your component.""" - hass.components.websocket_api.async_register_command( - WS_TYPE_MEDIA_PLAYER_THUMBNAIL, - websocket_handle_thumbnail, - SCHEMA_WEBSOCKET_GET_THUMBNAIL, - ) + hass.components.websocket_api.async_register_command(ws_get_panels) + hass.components.websocket_api.async_register_command(ws_handle_thumbnail) ``` ## Calling the command from the frontend (JavaScript)