Update the WS API docs (#937)

This commit is contained in:
Paulus Schoutsen 2021-05-17 01:40:11 -07:00 committed by GitHub
parent 5401141431
commit 35fed7195f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 46 additions and 86 deletions

View File

@ -2,15 +2,7 @@
title: "WebSocket API" 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: 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.
- [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.
## Server states ## Server states
@ -61,7 +53,8 @@ When a client connects to the server, the server sends out `auth_required`.
```json ```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 ```json
{ {
"type": "auth_ok" "type": "auth_ok",
"ha_version": "2021.5.3"
} }
``` ```

View File

@ -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. 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 ```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 from homeassistant.components import websocket_api
import homeassistant.helpers.config_validation as cv
@websocket_api.websocket_command(
# The schema for the message
SCHEMA_WEBSOCKET_GET_THUMBNAIL = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{ {
"type": WS_TYPE_MEDIA_PLAYER_THUMBNAIL, vol.Required("type"): "frontend/get_panels",
# The entity that we want to retrieve the thumbnail for. vol.Optional("preload_panels"): bool,
"entity_id": cv.entity_id,
} }
) )
@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. 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`.
#### 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`.
```python ```python
@callback from homeassistant.components import websocket_api
def websocket_handle_thumbnail(hass, connection, msg):
"""Handle getting a thumbnail."""
# We know the answer without having to fetch any information, @websocket_api.websocket_command(
# so we send it directly. {
connection.to_write.put_nowait( vol.Required("type"): "camera/get_thumbnail",
websocket_api.result_message( vol.Optional("entity_id"): str,
msg["id"], {"thumbnail": "http://via.placeholder.com/350x150"} }
) )
) @websocket_api.async_response
``` async def ws_handle_thumbnail(
hass: HomeAssistant, connection: ActiveConnection, msg: dict
#### Sending a delayed response ) -> None:
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):
"""Handle get media player cover command.""" """Handle get media player cover command."""
# Retrieve media player using passed in entity id. # Retrieve media player using passed in entity id.
player = hass.data[DOMAIN].get_entity(msg["entity_id"]) player = hass.data[DOMAIN].get_entity(msg["entity_id"])
# If the player does not exist, send an error message. # If the player does not exist, send an error message.
if player is None: if player is None:
connection.to_write.put_nowait( connection.send_error(
websocket_api.error_message(
msg["id"], "entity_not_found", "Entity not found" msg["id"], "entity_not_found", "Entity not found"
) )
) )
return return
# Define a function to be enqueued. data, content_type = await player.async_get_media_image()
async def send_image():
"""Send image."""
data, content_type = await player.async_get_media_image()
# No media player thumbnail available # No media player thumbnail available
if data is None: if data is None:
connection.send_message_outside( connection.send_error(
websocket_api.error_message( msg["id"], "thumbnail_fetch_failed", "Failed to fetch thumbnail"
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"),
},
)
) )
return
# Player exist. Queue up a job to send the thumbnail. connection.send_result(
hass.async_add_job(send_image()) msg["id"],
{
"content_type": content_type,
"content": base64.b64encode(data).decode("utf-8"),
},
)
``` ```
### Registering with the Websocket API ### 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 ```python
async def async_setup(hass, config): async def async_setup(hass, config):
"""Setup of your component.""" """Setup of your component."""
hass.components.websocket_api.async_register_command( hass.components.websocket_api.async_register_command(ws_get_panels)
WS_TYPE_MEDIA_PLAYER_THUMBNAIL, hass.components.websocket_api.async_register_command(ws_handle_thumbnail)
websocket_handle_thumbnail,
SCHEMA_WEBSOCKET_GET_THUMBNAIL,
)
``` ```
## Calling the command from the frontend (JavaScript) ## Calling the command from the frontend (JavaScript)