130 lines
5.0 KiB
Markdown
130 lines
5.0 KiB
Markdown
---
|
|
title: "Extending the WebSocket API"
|
|
---
|
|
|
|
As a component you might have information that you want to make available to the frontend. For example, the media player will want to make album covers available for the frontend to show. Our frontend is communicating with the backend over the websocket API, which can be extended with custom commands.
|
|
|
|
## Registering a command (Python)
|
|
|
|
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
|
|
|
|
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`.
|
|
|
|
```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({
|
|
'type': WS_TYPE_MEDIA_PLAYER_THUMBNAIL,
|
|
# The entity that we want to retrieve the thumbnail for.
|
|
'entity_id': cv.entity_id
|
|
})
|
|
```
|
|
|
|
### Defining a handler
|
|
|
|
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`.
|
|
|
|
```python
|
|
@callback
|
|
def websocket_handle_thumbnail(hass, connection, msg):
|
|
"""Handle getting a thumbnail."""
|
|
|
|
# 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):
|
|
"""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(
|
|
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()
|
|
|
|
# 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')
|
|
}))
|
|
|
|
# Player exist. Queue up a job to send the thumbnail.
|
|
hass.async_add_job(send_image())
|
|
```
|
|
|
|
### Registering with the Websocket API
|
|
|
|
With all pieces defined, it's time to register the command. This is done inside your setup method.
|
|
|
|
```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)
|
|
```
|
|
|
|
## Calling the command from the frontend (JavaScript)
|
|
|
|
With your command defined, it's time to call it from the frontend! This is done using JavaScript. You will need access to the `hass` object which holds the WebSocket connection to the backend. Then just call `hass.connection.sendMessagePromise`. This will return a promise that will resolve if the command succeeds and errors if the command fails.
|
|
|
|
```js
|
|
hass.connection.sendMessagePromise({
|
|
type: 'media_player/thumbnail',
|
|
entity_id: 'media_player.living_room_tv',
|
|
}).then(
|
|
(resp) => {
|
|
console.log('Message success!', resp.result);
|
|
},
|
|
(err) => {
|
|
console.error('Message failed!', err);
|
|
}
|
|
);
|
|
```
|
|
|
|
If your command is not sending a response, you can use `hass.connection.sendMessage`.
|