mirror of
https://github.com/home-assistant/developers.home-assistant.git
synced 2025-07-15 13:26:28 +00:00
0.101 (#348)
This commit is contained in:
parent
8b3f16438f
commit
2c2a98f3b0
265
website/versioned_docs/version-0.101.0/api_lib_auth.md
Normal file
265
website/versioned_docs/version-0.101.0/api_lib_auth.md
Normal file
@ -0,0 +1,265 @@
|
||||
---
|
||||
title: Python Library: Authentication
|
||||
sidebar_label: Authentication
|
||||
id: version-0.101.0-api_lib_auth
|
||||
original_id: api_lib_auth
|
||||
---
|
||||
|
||||
The Authentication part of your library is responsible for acquiring authentication and for making authenticated requests. It should not be aware of what is in the requests.
|
||||
|
||||
Authentication comes in many forms, but it generally boils down to that each request is accompanied by an `authorization` header which contains an access token. The access token is generally a string of random numbers/letters.
|
||||
|
||||
Your library should be able to acquire the authentication tokens, update them if necessary and use the authentication to make requests. It should not offer features to store the authentication data.
|
||||
|
||||
Because authentication is going to be stored by the developer, it is important that you return the authentication to the developer in a format that can be JSON serializable. A `dict` with primitive types (`str`, `float`, `int`) is recommended.
|
||||
|
||||
If your API can be served from multiple locations, your authentication class should allow the developer to pass in the location of the API.
|
||||
|
||||
## Async example
|
||||
|
||||
Python allows developers to write code that is either synchronous or asynchronous (via `asyncio`). Home Assistant is written in async, but is able to work with synchronous libraries too. We prefer async libraries.
|
||||
|
||||
If you are writing a library in async, we recommend that you use `aiohttp`. It's a modern and mature HTTP library and is easy to use.
|
||||
|
||||
```python
|
||||
from aiohttp import ClientSession, ClientResponse
|
||||
|
||||
|
||||
class Auth:
|
||||
"""Class to make authenticated requests."""
|
||||
|
||||
def __init__(self, websession: ClientSession, host: str, , access_token: str):
|
||||
"""Initialize the auth."""
|
||||
self.websession = websession
|
||||
self.host = host
|
||||
self.access_token = access_token
|
||||
|
||||
async def request(self, method: str, path: str, **kwargs) -> ClientResponse:
|
||||
"""Make a request."""
|
||||
headers = kwargs.get('headers')
|
||||
|
||||
if headers is None:
|
||||
headers = {}
|
||||
else:
|
||||
headers = dict(headers)
|
||||
|
||||
headers["authorization"] = self.access_token
|
||||
|
||||
return await self.websession.request(
|
||||
method,
|
||||
f"{self.host}/{path}",
|
||||
**kwargs,
|
||||
headers=headers,
|
||||
)
|
||||
```
|
||||
|
||||
To use this class, you will need to create an aiohttp ClientSession and pass it together with the API info to the constructor.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import aiohttp
|
||||
|
||||
from my_package import Auth
|
||||
|
||||
|
||||
async def main():
|
||||
async with aiohttp.ClientSession() as session:
|
||||
auth = Auth(session, "http://example.com/api", "secret_access_token")
|
||||
|
||||
# This will fetch data from http://example.com/api/lights
|
||||
resp = await auth.request('get', 'lights')
|
||||
print("HTTP response status code", resp.status)
|
||||
print("HTTP response JSON content", await resp.json())
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Sync example
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
|
||||
class Auth:
|
||||
"""Class to make authenticated requests."""
|
||||
|
||||
def __init__(self, host: str, , access_token: str):
|
||||
"""Initialize the auth."""
|
||||
self.host = host
|
||||
self.access_token = access_token
|
||||
|
||||
async def request(self, method: str, path: str, **kwargs) -> requests.Response:
|
||||
"""Make a request."""
|
||||
headers = kwargs.get('headers')
|
||||
|
||||
if headers is None:
|
||||
headers = {}
|
||||
else:
|
||||
headers = dict(headers)
|
||||
|
||||
headers["authorization"] = self.access_token
|
||||
|
||||
return requests.request(
|
||||
method,
|
||||
f"{self.host}/{path}",
|
||||
**kwargs,
|
||||
headers=headers,
|
||||
)
|
||||
```
|
||||
|
||||
To use this class, construct the class with the API info.
|
||||
|
||||
```python
|
||||
from my_package import Auth
|
||||
|
||||
|
||||
auth = Auth("http://example.com/api", "secret_access_token")
|
||||
|
||||
# This will fetch data from http://example.com/api/lights
|
||||
resp = auth.request('get', 'lights')
|
||||
print("HTTP response status code", resp.status_code)
|
||||
print("HTTP response JSON content", resp.json())
|
||||
```
|
||||
|
||||
## OAuth2
|
||||
|
||||
OAuth2 is a [standardized version](https://tools.ietf.org/html/rfc6749) of an authentication schema leveraging refresh and access tokens. The access token expires within a short period of time after being issued. The refresh token can be used to acquire new access tokens.
|
||||
|
||||
Refreshing access tokens relies on a client ID and secret, which might be held by an external service. We need to structure the authentication class to be able to allow the developer to implement their own token refresh logic.
|
||||
|
||||
Home Assistant ships with the Home Assistant Cloud Account Linking service, a free cloud service to allow users to quickly connect accounts using OAuth2. Home Assistant has easy to use tools built-in to allow users to configure OAuth2-based integrations. For more info, [read here](config_entries_config_flow_handler.md#configuration-via-oauth2). These built-in tools work best if your library is implemented like the examples below.
|
||||
|
||||
### Async example
|
||||
|
||||
```python
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class AbstractAuth(ABC):
|
||||
"""Abstract class to make authenticated requests."""
|
||||
|
||||
def __init__(self, websession: ClientSession, host: str):
|
||||
"""Initialize the auth."""
|
||||
self.websession = websession
|
||||
self.host = host
|
||||
|
||||
@abstractmethod
|
||||
async def async_get_access_token(self) -> str:
|
||||
"""Return a valid access token."""
|
||||
|
||||
async def request(self, method, url, **kwargs) -> ClientResponse:
|
||||
"""Make a request."""
|
||||
headers = kwargs.get('headers')
|
||||
|
||||
if headers is None:
|
||||
headers = {}
|
||||
else:
|
||||
headers = dict(headers)
|
||||
|
||||
access_token = await self.async_get_access_token()
|
||||
headers["authorization"] = f"Bearer {access_token}"
|
||||
|
||||
return await self.websession.request(
|
||||
method,
|
||||
f"{self.host}/{url}",
|
||||
**kwargs,
|
||||
headers=headers,
|
||||
)
|
||||
```
|
||||
|
||||
Now the developer that is using your library will have to implement the abstract method for getting the access token. Let's assume that the developer has their own token manager class.
|
||||
|
||||
```python
|
||||
from my_package import AbstractAuth
|
||||
|
||||
|
||||
class Auth(AbstractAuth):
|
||||
|
||||
def __init__(self, websession: ClientSession, host: str, token_manager):
|
||||
"""Initialize the auth."""
|
||||
super().__init__(websession, host)
|
||||
self.token_manager = token_manager
|
||||
|
||||
async def async_get_access_token(self) -> str:
|
||||
"""Return a valid access token."""
|
||||
if self.token_manager.is_token_valid():
|
||||
return self.token_manager.access_token
|
||||
|
||||
await self.token_manager.fetch_access_token()
|
||||
await self.token_manager.save_access_token()
|
||||
|
||||
return self.token_manager.access_token
|
||||
```
|
||||
|
||||
### Sync example
|
||||
|
||||
If you are using `requests`, we recommend that you use the `requests_oauthlib` package. Below is an example that works with a local client ID and secret but also allows outsourcing token fetching to Home Assistant.
|
||||
|
||||
```python
|
||||
from typing import Optional, Union, Callable, Dict
|
||||
|
||||
from requests import Response
|
||||
from requests_oauthlib import OAuth2Session
|
||||
from oauthlib.oauth2 import TokenExpiredError
|
||||
|
||||
|
||||
class Auth:
|
||||
def __init__(
|
||||
self,
|
||||
host: str,
|
||||
token: Optional[Dict[str, str]] = None,
|
||||
client_id: str = None,
|
||||
client_secret: str = None,
|
||||
token_updater: Optional[Callable[[str], None]] = None,
|
||||
):
|
||||
self.host = host
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.token_updater = token_updater
|
||||
|
||||
self._oauth = OAuth2Session(
|
||||
client_id=client_id,
|
||||
client_secret=client_secret,
|
||||
token=token,
|
||||
token_updater=token_updater,
|
||||
)
|
||||
|
||||
def refresh_tokens(self) -> Dict[str, Union[str, int]]:
|
||||
"""Refresh and return new tokens."""
|
||||
token = self._oauth.refresh_token(f"{self.host}/auth/token")
|
||||
|
||||
if self.token_updater is not None:
|
||||
self.token_updater(token)
|
||||
|
||||
return token
|
||||
|
||||
def request(self, method: str, path: str, **kwargs) -> Response:
|
||||
"""Make a request.
|
||||
|
||||
We don't use the built-in token refresh mechanism of OAuth2 session because
|
||||
we want to allow overriding the token refresh logic.
|
||||
"""
|
||||
url = f"{self.host}/{path}"
|
||||
try:
|
||||
return getattr(self._oauth, method)(url, **kwargs)
|
||||
except TokenExpiredError:
|
||||
self._oauth.token = self.refresh_tokens()
|
||||
|
||||
return getattr(self._oauth, method)(url, **kwargs)
|
||||
```
|
||||
|
||||
A developer will now be able to override the refresh token function to route it via their own external service.
|
||||
|
||||
```python
|
||||
from my_package import AbstractAuth
|
||||
|
||||
|
||||
class Auth(AbstractAuth):
|
||||
|
||||
def refresh_tokens(self) -> Dict[str, Union[str, int]]:
|
||||
"""Refresh and return new tokens."""
|
||||
self.token_manager.fetch_access_token()
|
||||
self.token_manager.save_access_token()
|
||||
|
||||
return self.token_manager.access_token
|
||||
```
|
148
website/versioned_docs/version-0.101.0/api_lib_data_models.md
Normal file
148
website/versioned_docs/version-0.101.0/api_lib_data_models.md
Normal file
@ -0,0 +1,148 @@
|
||||
---
|
||||
title: Python Library: Modelling Data
|
||||
sidebar_label: Modelling Data
|
||||
id: version-0.101.0-api_lib_data_models
|
||||
original_id: api_lib_data_models
|
||||
---
|
||||
|
||||
Now that we have authentication going, we can start making authenticated requests and fetch data!
|
||||
|
||||
When modelling the data, it is important that we expose the data from the API in the same structure as that the API offers it. Some API designs might not make a lot of sense or contain typos. It is important that we still represent them in our objects. This makes it easy for developers using your library to follow the API documentation and know how it will work in your library.
|
||||
|
||||
API libraries should try to do as little as possible. So it is okay to represent data structures as classes, but you should not transform data from one value into another. For example, you should not implement conversion between Celsius and Fahrenheit temperatures. This involves making decisions on precisions of results and should therefore be left to the developer using the library.
|
||||
|
||||
For this example we're going to model an async library for a Rest API named ExampleHub that has two endpoints:
|
||||
|
||||
- get `/light/<id>`: query the information of a single light.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1234,
|
||||
"name": "Example Light",
|
||||
"is_on": true
|
||||
}
|
||||
```
|
||||
|
||||
- post `/light/<id>`: control the light. Example JSON to send: `{ "is_on": false }`. Responds with the new state of the light.
|
||||
|
||||
- get `/lights`: return a list of all lights
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1234,
|
||||
"name": "Example Light",
|
||||
"is_on": true
|
||||
},
|
||||
{
|
||||
"id": 5678,
|
||||
"name": "Example Light 2",
|
||||
"is_on": false
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
As this API represents lights, we're first going to create a class to represent a light.
|
||||
|
||||
```python
|
||||
from .auth import Auth
|
||||
|
||||
|
||||
class Light:
|
||||
"""Class that represents a Light object in the ExampleHub API."""
|
||||
|
||||
def __init__(self, raw_data: dict, auth: Auth):
|
||||
"""Initialize a light object."""
|
||||
self.raw_data = raw_data
|
||||
self.auth = auth
|
||||
|
||||
# Note: each property name maps the name in the returned data
|
||||
|
||||
@property
|
||||
def id(self) -> int:
|
||||
"""Return the ID of the light."""
|
||||
return self.raw_data['id']
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the light."""
|
||||
return self.raw_data['name']
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return if the light is on."""
|
||||
return self.raw_data['id']
|
||||
|
||||
async def async_control(self, is_on: bool):
|
||||
"""Control the light."""
|
||||
resp = await self.auth.request('post', f'light/{self.id}', json={
|
||||
'is_on': is_on
|
||||
})
|
||||
resp.raise_for_status()
|
||||
self.raw_data = await resp.json()
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the light data."""
|
||||
resp = await self.auth.request('get', f'light/{self.id}')
|
||||
resp.raise_for_status()
|
||||
self.raw_data = await resp.json()
|
||||
```
|
||||
|
||||
Now that we have a light class, we can model the root of the API, which provides the entry points into the data.
|
||||
|
||||
```python
|
||||
from typing import List
|
||||
|
||||
from .auth import Auth
|
||||
from .light import Light
|
||||
|
||||
|
||||
class ExampleHubAPI:
|
||||
"""Class to communicate with the ExampleHub API."""
|
||||
|
||||
def __init__(self, auth: Auth):
|
||||
"""Initialize the API and store the auth so we can make requests."""
|
||||
self.auth = auth
|
||||
|
||||
async def async_get_lights(self) -> List[Light]:
|
||||
"""Return the lights."""
|
||||
resp = await self.auth.request('get', 'lights')
|
||||
resp.raise_for_status()
|
||||
return [
|
||||
Light(light_data, self.auth)
|
||||
for light_data in await resp.json()
|
||||
]
|
||||
|
||||
async def async_get_light(self, light_id) -> Light:
|
||||
"""Return the lights."""
|
||||
resp = await self.auth.request('get', f'light/{light_id}')
|
||||
resp.raise_for_status()
|
||||
return Light(await resp.json(), self.auth)
|
||||
```
|
||||
|
||||
With these two files in place, we can now control our lights like this:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import aiohttp
|
||||
|
||||
from my_package import Auth, ExampleHubAPI
|
||||
|
||||
|
||||
async def main():
|
||||
async with aiohttp.ClientSession() as session:
|
||||
auth = Auth(session, "http://example.com/api", "secret_access_token")
|
||||
api = ExampleHubAPI(auth)
|
||||
|
||||
lights = await api.async_get_lights()
|
||||
|
||||
# Print light states
|
||||
for light in lights:
|
||||
print(f"The light {light.name} is {light.is_on}")
|
||||
|
||||
# Control a light.
|
||||
light = lights[0]
|
||||
await light.async_control(not light.is_on)
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
46
website/versioned_docs/version-0.101.0/api_lib_index.md
Normal file
46
website/versioned_docs/version-0.101.0/api_lib_index.md
Normal file
@ -0,0 +1,46 @@
|
||||
---
|
||||
title: Building a Python library for an API
|
||||
sidebar_label: Introduction
|
||||
id: version-0.101.0-api_lib_index
|
||||
original_id: api_lib_index
|
||||
---
|
||||
|
||||
One of the foundational rules of Home Assistant is that we do not include any protocol specific code. Instead, this code should be put into a standalone Python library and published to PyPI. This guide will describe how to get started with this!
|
||||
|
||||
For this guide we're going to assume that we're building a library for a Rest API that is accessible over HTTP and returning data structured as JSON objects. This is the most common type of API that we see. These APIs can either be accessible on the device itself, or in the cloud.
|
||||
|
||||
This guide is not a perfect fit for every API. You might have to tweak the examples.
|
||||
|
||||
> If you are a manufacturer designing a new API for your product, [please read about the best type of API to add to your products here](https://www.home-assistant.io/blog/2016/02/12/classifying-the-internet-of-things/#local-device-pushing-new-state).
|
||||
|
||||
HTTP API requests consist of four different parts:
|
||||
|
||||
- The URL. This is the path that we fetch data from. With a Rest API the URL will uniquely identify the resource. Examples of urls are `http://example.com/api/lights` and `http://example.com/api/light/1234`.
|
||||
- The HTTP method. This defines what we want from the API. The most common ones are:
|
||||
- `GET` for when we want to get information like the state of a light
|
||||
- `POST` for if we want something to be done (ie turn on a light)
|
||||
- The body. This is the data that we sent to the server to identify what needs to be done. This is how we send the command in the case of a `POST` request.
|
||||
- The headers. This contains metadata to describe your request. This will used to attach the authorization to the request.
|
||||
|
||||
## Structuring the library
|
||||
|
||||
Our library will consist of two different parts:
|
||||
|
||||
- **Authentication:** Responsible for making authenticated HTTP requests to the API endpoint and returning the results. This is the only piece of code that will actually interact with the API.
|
||||
- **Data models:** Represent the data and offer commands to interact with the data.
|
||||
|
||||
## Trying your library inside Home Assistant
|
||||
|
||||
You will need to run an editable version of your library if you want to try your library in Home Assistant before it is publised to PyPI.
|
||||
|
||||
Do so by going to your Home Assistant development environment, activating the virtual environment and typing:
|
||||
|
||||
```
|
||||
pip3 install -e ../my_lib_folder
|
||||
```
|
||||
|
||||
Now run Home Assistant without installing dependencies from PyPI to avoid overriding your package.
|
||||
|
||||
```
|
||||
hass --skip-pip
|
||||
```
|
@ -0,0 +1,120 @@
|
||||
---
|
||||
title: Integration Configuration
|
||||
sidebar_label: Configuration
|
||||
id: version-0.101.0-config_entries_config_flow_handler
|
||||
original_id: config_entries_config_flow_handler
|
||||
---
|
||||
|
||||
Integrations can be set up via the user interface by adding support for a config config to create a config entry. Components that want to support config entries will need to define a Config Flow Handler. This handler will manage the creation of entries from user input, discovery or other sources (like Hass.io).
|
||||
|
||||
Config Flow Handlers control the data that is stored in a config entry. This means that there is no need to validate that the config is correct when Home Assistant starts up. It will also prevent breaking changes, because we will be able to migrate configuration entries to new formats if the version changes.
|
||||
|
||||
When instantiating the handler, Home Assistant will make sure to load all dependencies and install the requirements of the component.
|
||||
|
||||
## Updating the manifest
|
||||
|
||||
You need to update your integrations manifest to inform Home Assistant that your integration has a config flow. This is done by adding `config_flow: true` to your manifest ([docs](creating_integration_manifest.md#config-flow)).
|
||||
|
||||
## Defining your config flow
|
||||
|
||||
Config entries uses the [data flow entry framework](data_entry_flow_index.md) to define their config flows. The config flow needs to be defined in the file `config_flow.py` in your integration folder, extend `homeassistant.config_entries.ConfigFlow` and pass a `domain` key as part of inheriting `ConfigFlow`.
|
||||
|
||||
```python
|
||||
from homeassistant import config_entries
|
||||
from .const import DOMAIN
|
||||
|
||||
class ExampleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
```
|
||||
|
||||
## Defining steps
|
||||
|
||||
Your config flow will need to define steps of your configuration flow. The docs for [Data Entry Flow](data_entry_flow_index.md) describe the different return values of a step. Here is an example on how to define the `user` step.
|
||||
|
||||
```python
|
||||
class ExampleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
async def async_step_user(self, info):
|
||||
if info is not None:
|
||||
# process info
|
||||
|
||||
return self.async_show_form(
|
||||
step_id='user',
|
||||
data_schema=vol.Schema({
|
||||
vol.Required('password'): str
|
||||
})
|
||||
)
|
||||
```
|
||||
|
||||
There are a few step names reserved for system use:
|
||||
|
||||
| Step name | Description |
|
||||
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `user` | Invoked when a user initiates a flow via the user interface. |
|
||||
| `zeroconf` | Invoked if your integration has been discovered via Zeroconf/mDNS as specified [using `zeroconf` in the manifest](creating_integration_manifest.md#zeroconf). |
|
||||
| `homekit` | Invoked if your integration has been discovered via HomeKit as specified [using `homekit` in the manifest](creating_integration_manifest.md#homekit). |
|
||||
| `ssdp` | Invoked if your integration has been discovered via SSDP/uPnP as specified [using `ssdp` in the manifest](creating_integration_manifest.md#ssdp). |
|
||||
| `discovery` | _DEPRECATED_ Invoked if your integration has been discovered by the discovery integration. |
|
||||
|
||||
## Discovery steps
|
||||
|
||||
When an integration is discovered, their respective discovery step is invoked with the discovery information. The step will have to check the following things:
|
||||
|
||||
- Make sure there are no other instances of this config flow in progress of setting up the discovered device. This can happen if there are multiple ways of discovering that a device is on the network.
|
||||
- Make sure that the device is not already set up.
|
||||
- Invoking a discovery step should never result in a finished flow and a config entry. Always confirm with the user.
|
||||
|
||||
## Discoverable integrations that require no authentication
|
||||
|
||||
If your integration is discoverable without requiring any authentication, you'll be able to use the Discoverable Flow that is built-in. This flow offers the following features:
|
||||
|
||||
- Detect if devices/services can be discovered on the network before finishing the config flow.
|
||||
- Support all manifest-based discovery protocols.
|
||||
- Limit to only 1 config entry. It is up to the config entry to discover all available devices.
|
||||
|
||||
To get started, run `python3 -m script.scaffold config_flow_discovery` and follow the instructions. This will create all the boilerplate necessary to configure your integration using discovery.
|
||||
|
||||
## Configuration via OAuth2
|
||||
|
||||
Home Assistant has built-in support for integrations that offer account linking using [the OAuth2 authorization framework](https://tools.ietf.org/html/rfc6749). To be able to leverage this, you will need to structure your Python API library in a way that allows Home Assistant to be responsible for refreshing tokens. See our [API library guide](api_lib_index.md) on how to do this.
|
||||
|
||||
The built-in OAuth2 support works out of the box with locally configured client ID / secret and with the Home Assistant Cloud Account Linking service. This service allows users to link their account with a centrally managed client ID/secret. If you want your integration to be part of this service, reach out to us at [hello@home-assistant.io](mailto:hello@home-assistant.io).
|
||||
|
||||
To get started, run `python3 -m script.scaffold config_flow_oauth2` and follow the instructions. This will create all the boilerplate necessary to configure your integration using OAuth2.
|
||||
|
||||
## Translations
|
||||
|
||||
Translations for the config flow handlers are defined under the `config` key in the component translation file `strings.json`. Example of the Hue component:
|
||||
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"title": "Philips Hue Bridge",
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Pick Hue bridge",
|
||||
"data": {
|
||||
"host": "Host"
|
||||
}
|
||||
},
|
||||
"link": {
|
||||
"title": "Link Hub",
|
||||
"description": "Press the button on the bridge to register Philips Hue with Home Assistant.\n\n"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"register_failed": "Failed to register, please try again",
|
||||
"linking": "Unknown linking error occurred."
|
||||
},
|
||||
"abort": {
|
||||
"discover_timeout": "Unable to discover Hue bridges",
|
||||
"no_bridges": "No Philips Hue bridges discovered",
|
||||
"all_configured": "All Philips Hue bridges are already configured",
|
||||
"unknown": "Unknown error occurred",
|
||||
"cannot_connect": "Unable to connect to the bridge",
|
||||
"already_configured": "Bridge is already configured"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When the translations are merged into Home Assistant, they will be automatically uploaded to [Lokalise](https://lokalise.co/) where the translation team will help to translate them in other languages. [More info on translating Home Assistant.](internationalization_translation.md)
|
@ -0,0 +1,60 @@
|
||||
---
|
||||
title: Integration Configuration Options
|
||||
sidebar_label: Configuration Options
|
||||
id: version-0.101.0-config_entries_options_flow_handler
|
||||
original_id: config_entries_options_flow_handler
|
||||
---
|
||||
|
||||
An integration that is configured via a config entry can expose options to the user to allow tweaking behavior of the integration, like which devices or locations should be integrated.
|
||||
|
||||
Config Entry Options uses the [Data Flow Entry framework](data_entry_flow_index.md) to allow users to update a config entries options. Components that want to support config entry options will need to define an Options Flow Handler.
|
||||
|
||||
## Options support
|
||||
|
||||
For an integration to support options it needs to have an `async_get_options_flow` method in its config flow handler. Calling it will return an instance of the components options flow handler.
|
||||
|
||||
```python
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
return OptionsFlowHandler()
|
||||
```
|
||||
|
||||
## Flow handler
|
||||
|
||||
The Flow handler works just like the config flow handler, except that the first step in the flow will always be `async_step_init`.
|
||||
|
||||
```python
|
||||
class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
"""Manage the options."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
"show_things",
|
||||
default=self.config_entry.options.get("show_things"),
|
||||
): bool
|
||||
}
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
## Signal updates
|
||||
|
||||
If the component should act on updated options, you can register an update listener to the config entry that will be called when the entry is updated.
|
||||
|
||||
```python
|
||||
entry.add_update_listener(update_listener)
|
||||
```
|
||||
|
||||
The Listener shall be an async function that takes the same input as async_setup_entry. Options can then be accessed from `entry.options`.
|
||||
|
||||
```python
|
||||
async def update_listener(hass, entry):
|
||||
```
|
288
website/versioned_docs/version-0.101.0/data_entry_flow_index.md
Normal file
288
website/versioned_docs/version-0.101.0/data_entry_flow_index.md
Normal file
@ -0,0 +1,288 @@
|
||||
---
|
||||
title: Data Entry Flow
|
||||
sidebar_label: Introduction
|
||||
id: version-0.101.0-data_entry_flow_index
|
||||
original_id: data_entry_flow_index
|
||||
---
|
||||
|
||||
Data Entry Flow is a data entry framework that is part of Home Assistant. Data entry is done via data entry flows. A flow can represent a simple login form or a multi-step setup wizard for a component. A Flow Manager is managing all flows that are in progress and handles creation of new flows.
|
||||
|
||||
Data Entry Flow is being used in Home Assistant to create config entries.
|
||||
|
||||
## Flow Manager
|
||||
|
||||
This is the class that manages the flows that are in progress. When instantiating one, you pass in two async callbacks:
|
||||
|
||||
```python
|
||||
async def async_create_flow(handler, context=context, data=data)
|
||||
```
|
||||
|
||||
The manager delegates instantiating of config flow handlers to this async callback. This allows the parent of the manager to define their own way of finding handlers and preparing a handler for instantiation. For example, in the case of the config entry manager, it will make sure that the dependencies and requirements are setup.
|
||||
|
||||
```python
|
||||
async def async_finish_flow(flow, result)
|
||||
```
|
||||
|
||||
This async callback is called when a flow is finished or aborted. i.e. `result['type'] in [RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_ABORT]`. The callback function can modify result and return it back, if the result type changed to `RESULT_TYPE_FORM`, the flow will continue running, display another form.
|
||||
|
||||
If the result type is `RESULT_TYPE_FORM`, the result should like:
|
||||
```python
|
||||
{
|
||||
# The result type of the flow
|
||||
'type': RESULT_TYPE_FORM,
|
||||
# the id of the flow
|
||||
'flow_id': 'abcdfgh1234,
|
||||
# handler name
|
||||
'handler': 'hue',
|
||||
# name of the step, flow.async_step_[step_id] will be called when form submitted
|
||||
'step_id': 'init',
|
||||
# a voluptuous schema to build and validate user input
|
||||
'data_schema': vol.Schema(),
|
||||
# an errors dict, None if no errors
|
||||
'errors': errors,
|
||||
# a detail information about the step
|
||||
'description_placeholders': description_placeholders,
|
||||
}
|
||||
```
|
||||
|
||||
If the result type is `RESULT_TYPE_CREATE_ENTRY`, the result should like:
|
||||
```python
|
||||
{
|
||||
# Data schema version of the entry
|
||||
'version': 2,
|
||||
# The result type of the flow
|
||||
'type': RESULT_TYPE_CREATE_ENTRY,
|
||||
# the id of the flow
|
||||
'flow_id': 'abcdfgh1234,
|
||||
# handler name
|
||||
'handler': 'hue',
|
||||
# title and data as created by the handler
|
||||
'title': 'Some title',
|
||||
'result': {
|
||||
'some': 'data'
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
If the result type is `RESULT_TYPE_ABORT`, the result should like:
|
||||
```python
|
||||
{
|
||||
# The result type of the flow
|
||||
'type': RESULT_TYPE_ABORT,
|
||||
# the id of the flow
|
||||
'flow_id': 'abcdfgh1234,
|
||||
# handler name
|
||||
'handler': 'hue',
|
||||
# the abort reason
|
||||
'reason': 'already_configured',
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Flow Handler
|
||||
|
||||
Flow handlers will handle a single flow. A flow contains one or more steps. When a flow is instantiated, the `FlowHandler.init_step` step will be called. Each step has three different possible results: "Show Form", "Abort" and "Create Entry".
|
||||
|
||||
At a minimum, each flow handler will have to define a version number and a step. This doens't have to be `init`, as `async_create_flow` can assign `init_step` depends on diffreent workflow, for example in configuration, `context.source` will be use as `init_step`.
|
||||
|
||||
The bare minimum config flow:
|
||||
|
||||
```python
|
||||
from homeassistant import data_entry_flow
|
||||
|
||||
@config_entries.HANDLERS.register(DOMAIN)
|
||||
class ExampleConfigFlow(data_entry_flow.FlowHandler):
|
||||
|
||||
# The schema version of the entries that it creates
|
||||
# Home Assistant will call your migrate method if the version changes
|
||||
# (this is not implemented yet)
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
# Do something
|
||||
```
|
||||
|
||||
### Show Form
|
||||
|
||||
This result type will show a form to the user to fill in. You define the current step, the schema of the data (using voluptuous) and optionally a dictionary of errors. Title and description of the step will be provided via the translation file. Where this is defined depends on the context of the data entry flow.
|
||||
|
||||
```python
|
||||
class ExampleConfigFlow(data_entry_flow.FlowHandler):
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
# Use OrderedDict to guarantee order of the form shown to the user
|
||||
data_schema = OrderedDict()
|
||||
data_schema[vol.Required('username')] = str
|
||||
data_schema[vol.Required('password')] = str
|
||||
|
||||
return self.async_show_form(
|
||||
step_id='init',
|
||||
data_schema=vol.Schema(data_schema)
|
||||
)
|
||||
```
|
||||
|
||||
After the user has filled in the form, the step method will be called again and the user input is passed in. Your step will only be called if the user input passes your data schema. When the user passes in data, you will have to do extra validation of the data. For example, you can verify that the passed in username and password are valid.
|
||||
|
||||
If something is wrong, you can return a dictionary with errors. Each key in the error dictionary refers to a field name that contains the error. Use the key `base` if you want to show an error unrelated to a specific field. The specified errors need to refer to a key in a translation file.
|
||||
|
||||
```python
|
||||
class ExampleConfigFlow(data_entry_flow.FlowHandler):
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
# Validate user input
|
||||
valid = await is_valid(user_input)
|
||||
if valid:
|
||||
# See next section on create entry usage
|
||||
return self.create_entry(...)
|
||||
|
||||
errors['base'] = 'auth_error'
|
||||
|
||||
# Use OrderedDict to guarantee order of the form shown to the user
|
||||
data_schema = OrderedDict()
|
||||
data_schema[vol.Required('username')] = str
|
||||
data_schema[vol.Required('password')] = str
|
||||
|
||||
return self.async_show_form(
|
||||
step_id='init',
|
||||
data_schema=vol.Schema(data_schema),
|
||||
errors=errors
|
||||
)
|
||||
```
|
||||
|
||||
#### Multi-step flows
|
||||
|
||||
If the user input passes validation, you can again return one of the three return values. If you want to navigate the user to the next step, return the return value of that step:
|
||||
|
||||
```python
|
||||
class ExampleConfigFlow(data_entry_flow.FlowHandler):
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
# Validate user input
|
||||
valid = await is_valid(user_input)
|
||||
if valid:
|
||||
# Store info to use in next step
|
||||
self.init_info = user_input
|
||||
# Return the form of the next step
|
||||
return await self.async_step_account()
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
### Create Entry
|
||||
|
||||
When the result is "Create Entry", an entry will be created and passed to the parent of the flow manager. A success message is shown to the user and the flow is finished. You create an entry by passing a title and data. The title can be used in the UI to indicate to the user which entry it is. Data can be any data type, as long as it is JSON serializable.
|
||||
|
||||
```python
|
||||
class ExampleConfigFlow(data_entry_flow.FlowHandler):
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
return self.create_entry(
|
||||
title='Title of the entry',
|
||||
data={
|
||||
'something_special': user_input['username']
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Abort
|
||||
|
||||
When a flow cannot be finished, you need to abort it. This will finish the flow and inform the user that the flow has finished. Reasons for a flow to not be able to finish can be that a device is already configured or not compatible with Home Assistant.
|
||||
|
||||
```python
|
||||
class ExampleConfigFlow(data_entry_flow.FlowHandler):
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
return self.async_abort(
|
||||
reason='not_supported'
|
||||
)
|
||||
```
|
||||
|
||||
### External Step & External Step Done
|
||||
|
||||
It is possible that a user needs to finish a config flow by doing actions on an external website. For example, setting up an integration by being redirected to an external webpage. This is commonly used by integrations that use OAuth2 to authorize a user.
|
||||
|
||||
_The example is about config entries, but works with other parts that use data entry flows too._
|
||||
|
||||
The flow works as follows:
|
||||
|
||||
1. User starts config flow in Home Assistant
|
||||
2. Config flow prompts user to finish the flow on an external website
|
||||
3. User opens the external website
|
||||
4. Upon completion of the external step, the user's browser will be redirected to a Home Assistant endpoint to deliver the response.
|
||||
5. The endpoint validates the response, and upon validation, marks the external step as done and returns JavaScript code to close the window: `<script>window.close()</script>`.
|
||||
|
||||
To be able to route the result of the external step to the Home Assistant endpoint, you will need to make sure the config flow ID is included. If your external step is an OAuth2 flow, you can leverage the oauth2 state for this. This is a variable that is not interpreted by the authorization page but is passed as-is to the Home Assistant endpoint.
|
||||
6. The window closes and the Home Assistant user interface with the config flow will be visible to the user again.
|
||||
7. The config flow has automatically advanced to the next step when the external step was marked as done. The user is prompted with the next step.
|
||||
|
||||
Example configuration flow that includes an external step.
|
||||
|
||||
```python
|
||||
from homeassistant import config_entries
|
||||
|
||||
@config_entries.HANDLERS.register(DOMAIN)
|
||||
class ExampleConfigFlow(data_entry_flow.FlowHandler):
|
||||
VERSION = 1
|
||||
data = None
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
if not user_input:
|
||||
return self.async_external_step(
|
||||
step_id='user',
|
||||
url='https://example.com/?config_flow_id={}'.format(
|
||||
self.flow_id
|
||||
),
|
||||
)
|
||||
|
||||
self.data = user_input
|
||||
return self.async_external_step_done(next_step_id='finish')
|
||||
|
||||
async def async_step_finish(self, user_input=None):
|
||||
return self.async_create_entry(
|
||||
title=self.data['title'],
|
||||
data=self.data
|
||||
)
|
||||
```
|
||||
|
||||
Avoid doing work based on the external step data before you return an `async_mark_external_step_done`. Instead, do the work in the step that you refer to as `next_step_id` when marking the external step done. This will give the user a better user experience by showing a spinner in the UI while the work is done.
|
||||
|
||||
If you do the work inside the authorize callback, the user will stare at a blank screen until that all of a sudden closes because the data has forwarded. If you do the work before marking the external step as done, the user will still see the form with the "Open external website" button while the background work is being done. That too is undesirable.
|
||||
|
||||
Example code to mark an external step as done:
|
||||
|
||||
```python
|
||||
from homeassistant import data_entry_flow
|
||||
|
||||
async def handle_result(hass, flow_id, data):
|
||||
result = await hass.config_entries.async_configure(flow_id, data)
|
||||
|
||||
if result['type'] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP_DONE:
|
||||
return "success!"
|
||||
else:
|
||||
return "Invalid config flow specified"
|
||||
```
|
||||
|
||||
## Translations
|
||||
|
||||
Data entry flows depend on translations for showing the text in the forms. It depends on the parent of a data entry flow manager where this is stored.
|
||||
|
||||
## Initializing a config flow from an external source
|
||||
|
||||
You might want to initialize a config flow programmatically. For example, if we discover a device on the network that requires user interaction to finish setup. To do so, pass a source parameter and optional user input when initializing a flow:
|
||||
|
||||
```python
|
||||
await flow_mgr.async_init('hue', context={'source': data_entry_flow.SOURCE_DISCOVERY}, data=discovery_info)
|
||||
```
|
||||
|
||||
The config flow handler will not start with the `init` step. Instead, it will be instantiated with a step name equal to the source. The step should follow the same return values as a normal step.
|
||||
|
||||
```python
|
||||
class ExampleConfigFlow(data_entry_flow.FlowHandler):
|
||||
|
||||
async def async_step_discovery(self, info):
|
||||
# Handle discovery info
|
||||
```
|
@ -0,0 +1,137 @@
|
||||
---
|
||||
title: Create a new page
|
||||
id: version-0.101.0-documentation_create_page
|
||||
original_id: documentation_create_page
|
||||
---
|
||||
|
||||
For a platform or integration page, the fastest way is to make a copy of an existing page and edit it. The [Integration overview](https://www.home-assistant.io/components/) and the [Examples section](https://www.home-assistant.io/cookbook/) are generated automatically, so there is no need to add a link to those pages.
|
||||
|
||||
Please honor the [Standards](documentation_standards.md) we have for the documentation.
|
||||
|
||||
If you start from scratch with a page, you need to add a header. Different sections of the documentation may need different headers.
|
||||
|
||||
```text
|
||||
---
|
||||
title: "Awesome Sensor"
|
||||
description: "home-assistant.io web presence"
|
||||
ha_release: "0.38"
|
||||
ha_category: Sensor
|
||||
ha_iot_class: "Local Polling"
|
||||
ha_qa_scale: silver
|
||||
ha_config_flow: true
|
||||
---
|
||||
|
||||
Content... Written in markdown.
|
||||
|
||||
### Title Header
|
||||
...
|
||||
```
|
||||
|
||||
Additional keys for the file header:
|
||||
|
||||
- `logo`: Please check the separate section below.
|
||||
- `ha_release`: The release when the integration was included, e.g., "0.38". If the current release is 0.37, make `ha_release` 0.38. If it's 0.30 or 0.40 please quote it with `" "`.
|
||||
- `ha_category`: This entry is used to group the integration on the [Integration overview](https://www.home-assistant.io/components/).
|
||||
- `ha_iot_class`: [IoT class](https://www.home-assistant.io/blog/2016/02/12/classifying-the-internet-of-things) is the classifier for the device's behavior.
|
||||
- `ha_qa_scale`: [Quality scale](https://www.home-assistant.io/docs/quality_scale/) is the representation of the integration's quality.
|
||||
- `ha_config_flow`: Set to `true` if the integration has a [Data Entry Flow](https://developers.home-assistant.io/docs/en/data_entry_flow_index.html), omit otherwise.
|
||||
|
||||
There are [pre-defined variables](https://jekyllrb.com/docs/variables/) available but usually, it's not necessary to use them when writing documentation.
|
||||
|
||||
A couple of points to remember:
|
||||
|
||||
- Document the needed steps to retrieve API keys or access token for the third party service or device if needed.
|
||||
- Add screenshots to support the user where it makes sense.
|
||||
- Add the type of the device(s) (incl. firmware) you have tested when you know that there are multiple out there.
|
||||
|
||||
### Configuration
|
||||
|
||||
Every platform page should contain a configuration sample. This sample must contain only the **required** variables to make it easy to copy and paste it for users into their `configuration.yaml` file.
|
||||
|
||||
The **Configuration Variables** section must use the `{% configuration %} ... {% endconfiguration %}` tag.
|
||||
|
||||
```text
|
||||
{% configuration %}
|
||||
api_key:
|
||||
description: The API key to access the service.
|
||||
required: true
|
||||
type: string
|
||||
name:
|
||||
description: Name to use in the frontend.
|
||||
required: false
|
||||
default: The default name to use in the frontend.
|
||||
type: string
|
||||
monitored_conditions:
|
||||
description: Conditions to display in the frontend.
|
||||
required: true
|
||||
type: map
|
||||
keys:
|
||||
weather:
|
||||
description: A human-readable text summary.
|
||||
temperature:
|
||||
description: The current temperature.
|
||||
{% endconfiguration %}
|
||||
```
|
||||
|
||||
Available keys:
|
||||
|
||||
- **`description:`**: That the variable is about.
|
||||
- **`required:`**: If the variable is required.
|
||||
|
||||
```text
|
||||
required: true #=> Required
|
||||
required: false #=> Optional
|
||||
required: inclusive #=> Inclusive
|
||||
required: exclusive #=> Exclusive
|
||||
required: any string here #=> Any string here
|
||||
```
|
||||
- **`type:`**: The type of the variable. Allowed entries: `boolean`, `string`, `integer`, `float`, `time`, `template`, `device_class`, `icon` or `map`/`list` (for a list of entries). For multiple possibilities use `[string, integer]`. If you use `map`/`list` then should define `keys:` (see the [`template` sensor](https://www.home-assistant.io/components/sensor.template/) for an example). If you use `boolean`, then `default:` must be defined.
|
||||
- **`default:`**: The default value for the variable.
|
||||
|
||||
### Embedding Code
|
||||
|
||||
You can use the [default markdown syntax](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#code) to generate syntax highlighted code. For inline code wrap your code in back-ticks.
|
||||
|
||||
When you're writing code that is to be executed on the terminal, do not prefix them with `$`, since this makes it hard to copy and paste the commands. However, an exception is made when there is a need to distinguish between typed commands and command output. In those cases, prefixing the commands with a `$` is required.
|
||||
|
||||
### Templates
|
||||
|
||||
For the [configuration templating](https://www.home-assistant.io/docs/configuration/templating/) [Jinja](http://jinja.pocoo.org/) is used. Check the [Documentation Standards](documentation_standards.md) for further details.
|
||||
|
||||
If you are don't escape templates then they will be rendered and appear blank on the website.
|
||||
|
||||
### HTML
|
||||
|
||||
The direct usage of HTML is supported but not recommended. The note boxes are an exception.
|
||||
|
||||
```html
|
||||
<div class='note warning'>
|
||||
You need to enable telnet on your router.
|
||||
</div>
|
||||
```
|
||||
|
||||
Please note, if you want to use Markdown inside an HTML block, it has to be surrounded by a new line.
|
||||
|
||||
```html
|
||||
<div class='note warning'>
|
||||
|
||||
You need to enable [**telnet**](https://en.wikipedia.org/wiki/Telnet) on your router.
|
||||
|
||||
</div>
|
||||
```
|
||||
|
||||
### Images, icons and logos
|
||||
|
||||
The images which are displayed on the pages are stored in various directories according to their purpose. If you want to use a logo and placed `logo:` in the file header then this image should be stored in `source/images/supported_brands`. The background must be transparent.
|
||||
|
||||
| Type | Location |
|
||||
| :----------- |:----------------------------------------------|
|
||||
| logos | source/images/supported_brands |
|
||||
| blog | source/images/blog |
|
||||
| screenshots | source/images/components |
|
||||
|
||||
Not everything (product, integration, etc.) should have a logo. To show something for internal parts of Home Assistant we are using the [Material Design Icons](https://materialdesignicons.com/).
|
||||
|
||||
### Linking From The Sidebar
|
||||
|
||||
If you are adding a new page that requires linking from the sidebar, you need to edit the `docs_navigation.html` file in `source/_includes/asides/docs_navigation.html`.
|
@ -0,0 +1,54 @@
|
||||
---
|
||||
title: Documentation
|
||||
id: version-0.101.0-documentation_index
|
||||
original_id: documentation_index
|
||||
---
|
||||
|
||||
The user documentation is located at [https://www.home-assistant.io](https://www.home-assistant.io). This section here is the place where we provide documentation and additional details about creating or modifying content.
|
||||
|
||||
The [home-assistant.io](https://home-assistant.io) website is built using [Jekyll](http://github.com/mojombo/jekyll) and [these dependencies](https://pages.github.com/versions/). The pages are written in [Markdown](http://daringfireball.net/projects/markdown/). To add a page, you don't need to know HTML.
|
||||
|
||||
You can use the "**Edit this page on GitHub**" link to edit pages without creating a fork. Keep in mind that you can't upload images while working this way. You work on your change and propose it via a Pull Request (PR).
|
||||
|
||||
Once you've created a Pull Request (PR), you can see a preview of the proposed changes by clicking *Details* against Netlify checker in the checkers section of the PR as soon as deployment is complete.
|
||||
|
||||
For larger changes, we suggest that you clone the website repository. This way, you can review your changes locally. The process of working on the website is no different from working on Home Assistant itself.
|
||||
|
||||
To test your changes locally, you need to install **Ruby** and its dependencies (gems):
|
||||
|
||||
- [Install Ruby](https://www.ruby-lang.org/en/documentation/installation/) if you don't have it already. Ruby version 2.5.0 or higher is required.
|
||||
- Install `bundler`, a dependency manager for Ruby: `$ gem install bundler` (You might have to run this command as `sudo`).
|
||||
|
||||
- Shortcut for Fedora: `$ sudo dnf -y install gcc-c++ ruby ruby-devel rubygem-bundler rubygem-json && bundle`
|
||||
- Shortcut for Debian/Ubuntu: `$ sudo apt-get install ruby ruby-dev ruby-bundler ruby-json g++ zlib1g-dev && bundle`
|
||||
|
||||
- Fork the home-assistant.io [git repository](https://github.com/home-assistant/home-assistant.io).
|
||||
- In your home-assistant.io root directory, run `$ bundle` to install the gems you need.
|
||||
|
||||
Then you can work on the documentation:
|
||||
|
||||
- Run `bundle exec rake generate` to generate the very first preview. This will take a minute.
|
||||
- Create/edit/update a page. The integration/platforms documentation is located in `source/_integrations/`. `source/_docs/` contains the Home Assistant documentation itself.
|
||||
- Test your changes to home-assistant.io locally: run `bundle exec rake preview` and navigate to [http://127.0.0.1:4000](http://127.0.0.1:4000). While this command is working, any changes to a file are automatically detected and will update the affected pages. You will have to manually reload them in the browser though.
|
||||
- Create a Pull Request (PR) against the **next** branch of home-assistant.io if your documentation is a new feature, platform, or integration.
|
||||
- Create a Pull Request (PR) against the **current** branch of home-assistant.io if you fix stuff, create Cookbook entries, or expand existing documentation.
|
||||
|
||||
The site generated by `bundle exec rake` is only available locally. If you are developing on a headless machine, use port forwarding:
|
||||
|
||||
```bash
|
||||
$ ssh -L 4000:localhost:4000 user_on_headless_machine@ip_of_headless_machine
|
||||
```
|
||||
|
||||
## Speeding up site generation
|
||||
|
||||
Every release we post long changelogs to the website. This slows down generation of the website a bit. We've include some tools to temporarily exclude integrations and blog posts that you're not working on out of the way.
|
||||
|
||||
```bash
|
||||
bundle exec rake isolate[filename-of-blogpost-or-integration]
|
||||
```
|
||||
|
||||
When you're done working on the site, run the following command to move the pages back again:
|
||||
|
||||
```bash
|
||||
bundle exec rake integrate
|
||||
```
|
@ -0,0 +1,123 @@
|
||||
---
|
||||
title: Standards
|
||||
id: version-0.101.0-documentation_standards
|
||||
original_id: documentation_standards
|
||||
---
|
||||
|
||||
To ensure that the documentation for Home Assistant is consistent and easy to follow for both novice and expert users, we ask that you follow a very strict set of standards for developing the documentation.
|
||||
|
||||
## General Documentation
|
||||
|
||||
* The language of the documentation should be American-English.
|
||||
* Don't put two spaces after a period and avoid the "Oxford comma".
|
||||
* There is no limit for the line length. You are allowed to write in a flowing text style. This will make it easier to use the GitHub online editor in the future.
|
||||
* Be objective and not gender favoring, polarizing, race related or religion inconsiderate.
|
||||
* The case of brand names, services, protocols, integrations and platforms must match its respective counterpart. e.g., "Z-Wave" **not** "Zwave", "Z-wave", "Z Wave" or "ZWave". Also, "Input Select" **not** "input select" or "Input select".
|
||||
* Do not use ALL CAPITALS for emphasis - use italics instead.
|
||||
|
||||
## Integration and Platform Pages
|
||||
|
||||
* The **Configuration Variables** section must use the `{% configuration %}` tag.
|
||||
* Configuration variables must document the requirement status (`false` or `true`).
|
||||
* Configuration variables must document the default value, if any.
|
||||
* Configuration variables must document the accepted value types (see [Configuration variables details](documentation_create_page.md#configuration)).
|
||||
* For configuration variables that accept multiple types, separate the types with a comma (i.e. `string, int`).
|
||||
* Use YAML sequence syntax in the sample code if it is supported.
|
||||
* All examples should be formatted to be included in `configuration.yaml` unless explicitly stated.
|
||||
* Use capital letters and `_` to indicate that the value needs to be replaced. E.g., `api_key: YOUR_API_KEY` or `api_key: REPLACE_ME`.
|
||||
* If you know that the API key or value contains [control characters](https://en.wikipedia.org/wiki/YAML#Syntax), e.g., `#`, `[`, `?`, etc., wrap it in quotes and add a note.
|
||||
* Integration and platform names should be a link to their respective documentation pages.
|
||||
|
||||
Example configuration block
|
||||
|
||||
```yaml
|
||||
{% configuration %}
|
||||
some_key:
|
||||
description: This is a description of what this key is for.
|
||||
required: false
|
||||
type: string
|
||||
default: Optional default value - leave out if there isn't one
|
||||
{% endconfiguration %}
|
||||
```
|
||||
|
||||
## Templates
|
||||
|
||||
* All examples containing Jinja2 templates should be wrapped **outside** of the code markdown with the `{% raw %}` tag.
|
||||
* Do not use `states.switch.source.state` in templates. Instead use `states()` and `is_state()`.
|
||||
* Use double quotes (`"`) for ([more information](#single-vs-double-quotation-marks)):
|
||||
* `friendly_name`
|
||||
* Single-line templates:
|
||||
* `value_template`
|
||||
* `level_template`
|
||||
* `icon_template`
|
||||
* Children of `data_template`
|
||||
* Use single quotes (`'`) for ([more information](#single-vs-double-quotation-marks):
|
||||
* Strings inside of templates:
|
||||
* States
|
||||
* Entity IDs
|
||||
* `unit_of_measurement`
|
||||
* No whitespace around pipe character (`|`) for Jinja2 filters.
|
||||
* Single whitespace after Jinja2 opening delimiters ({% raw %}`{{`{% endraw %}).
|
||||
* Single whitespace before Jinja2 closing delimiters ({% raw %}`}}`{% endraw %}).
|
||||
* Do not quote values for:
|
||||
* `device_class`
|
||||
* `platform`
|
||||
* `condition`
|
||||
* `service`
|
||||
|
||||
## Renaming Pages
|
||||
|
||||
It can happen that a integration or platform is renamed, in this case the documentation needs to be updated as well. If you rename a page, add `redirect_from:` to the file header and let it point to the old location/name of the page. Please consider to add details, like release number or old integration/platform name, to the page in a [note](/developers/documentation/create_page/#html).
|
||||
|
||||
```text
|
||||
---
|
||||
...
|
||||
redirect_from: /getting-started/android/
|
||||
---
|
||||
```
|
||||
|
||||
Adding a redirect also applies if you move content around in the [documentation](https://www.home-assistant.io/docs/).
|
||||
|
||||
## Single vs. Double Quotation Marks
|
||||
|
||||
Use single quotes (`'`) for strings inside of a template. It is more obvious to escape a single quote when necessary (i.e. `name` is a possessive noun), because the single quotes that wrap the string are closer in position to the apostrophe inside the string. Use double quotes (`"`) outside of a template (unless it is a multi-line template, in which case outside quotes are not required).
|
||||
|
||||
### Examples
|
||||
|
||||
#### Double Quotes Outside, Single Quotes Inside (Valid)
|
||||
|
||||
```yaml
|
||||
automation:
|
||||
...
|
||||
action:
|
||||
- service: notify.notify
|
||||
data_template:
|
||||
message: "{% if trigger.to_state.name == 'Dale\'s Bedroom' %}Someone's in your base, killing your noobs!{% else %}It's just another door.{% endif %}"
|
||||
```
|
||||
|
||||
#### Single Quotes Outside, Double Quotes Inside (Invalid)
|
||||
|
||||
```yaml
|
||||
automation:
|
||||
...
|
||||
action:
|
||||
- service: notify.notify
|
||||
data_template:
|
||||
message: '{% if trigger.to_state.name == "Dale's Bedroom" %}Someone's in your base, killing your noobs!{% else %}It's just another door.{% endif %}'
|
||||
```
|
||||
|
||||
#### Multi-Line Template (Valid)
|
||||
|
||||
```yaml
|
||||
automation:
|
||||
...
|
||||
action:
|
||||
- service: notify.notify
|
||||
data_template:
|
||||
message: >-
|
||||
{% if trigger.to_state.name == 'Dale\'s Bedroom' %}
|
||||
Someone's in your base, killing your noobs!
|
||||
{% else %}
|
||||
It's just another door.
|
||||
{% endif %}
|
||||
```
|
221
website/versioned_docs/version-0.101.0/entity_climate.md
Normal file
221
website/versioned_docs/version-0.101.0/entity_climate.md
Normal file
@ -0,0 +1,221 @@
|
||||
---
|
||||
title: Climate Entity
|
||||
sidebar_label: Climate
|
||||
id: version-0.101.0-entity_climate
|
||||
original_id: entity_climate
|
||||
---
|
||||
|
||||
A climate entity is a device that controls temperature, humidity, or fans, such as A/C systems and humidifiers. Derive entity platforms from [`homeassistant.components.climate.ClimateDevice`](https://github.com/home-assistant/home-assistant/blob/master/homeassistant/components/climate/__init__.py)
|
||||
|
||||
## Properties
|
||||
|
||||
> Properties should always only return information from memory and not do I/O (like network requests). Implement `update()` or `async_update()` to fetch data.
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| ----------------------- | ------ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
|
||||
| temperature_unit | string | `NotImplementedError` | The unit of temperature measurement for the system (`TEMP_CELSIUS` or `TEMP_FAHRENHEIT`). |
|
||||
| precision | float | Based on `temperature_unit` | The precision of the temperature in the system. Defaults to tenths for TEMP_CELSIUS, whole number otherwise. |
|
||||
| current_temperature | float | None | The current temperature. |
|
||||
| current_humidity | float | None | The current humidity. |
|
||||
| target_temperature | float | None | The temperature currently set to be reached. |
|
||||
| target_temperature_high | float | None | The upper bound target temperature |
|
||||
| target_temperature_low | float | None | The lower bound target temperature |
|
||||
| target_temperature_step | float | None | The supported step size a target temperature can be increased/decreased |
|
||||
| target_humidity | float | None | The target humidity the device is trying to reach. Requires `SUPPORT_TARGET_HUMIDITY`. |
|
||||
| max_temp | int | `DEFAULT_MAX_TEMP` (value == 35) | Returns the maximum temperature. |
|
||||
| min_temp | int | `DEFAULT_MIN_TEMP` (value == 7) | Returns the minimum temperature. |
|
||||
| max_humidity | int | `DEFAULT_MAX_HUMIDITY` (value == 99) | Returns the maximum humidity. Requires `SUPPORT_TARGET_HUMIDITY`. |
|
||||
| min_humidity | int | `DEFAULT_MIN_HUMIDITY` (value == 30) | Returns the minimum humidity. Requires `SUPPORT_TARGET_HUMIDITY`. |
|
||||
| hvac_mode | string | `NotImplementedError()` | The current operation (e.g. heat, cool, idle). Used to determine `state`. |
|
||||
| hvac_action | string | None | The current HVAC action (heating, cooling) |
|
||||
| hvac_modes | list | `NotImplementedError()` | List of available operation modes. See below. |
|
||||
| preset_mode | string | `NotImplementedError()` | The current active preset. Requires `SUPPORT_PRESET_MODE`. |
|
||||
| preset_modes | list | `NotImplementedError()` | The available presets. Requires `SUPPORT_PRESET_MODE`. |
|
||||
| fan_mode | string | `NotImplementedError()` | Returns the current fan mode. Requires `SUPPORT_FAN_MODE`. |
|
||||
| fan_modes | list | `NotImplementedError()` | Returns the list of available fan modes. Requires `SUPPORT_FAN_MODE`. |
|
||||
| swing_mode | string | `NotImplementedError()` | Returns the fan setting. |
|
||||
| swing_modes | list | `NotImplementedError()` | Returns the list of available swing modes. |
|
||||
| is_aux_heat | bool | None | Returns True if an auxiliary heater is on. Requires `SUPPORT_AUX_HEAT`. |
|
||||
| supported_features | int | `NotImplementedError()` | Bitmap of supported features. See below. |
|
||||
|
||||
### HVAC modes
|
||||
|
||||
You are only allowed to use the built-in HVAC modes. If you want another mode, add a preset instead.
|
||||
|
||||
| Name | Description |
|
||||
| --------------------- | ------------------------------------------------------------------- |
|
||||
| `HVAC_MODE_OFF` | The device is turned off. |
|
||||
| `HVAC_MODE_HEAT` | The device is set to heat to a target temperature. |
|
||||
| `HVAC_MODE_COOL` | The device is set to cool to a target temperature. |
|
||||
| `HVAC_MODE_HEAT_COOL` | The device supports heating/cooling to a range |
|
||||
| `HVAC_MODE_AUTO` | The device is set to a schedule, learned behavior, AI. |
|
||||
| `HVAC_MODE_DRY` | The device is set to dry/humidity mode. |
|
||||
| `HVAC_MODE_FAN_ONLY` | The device only has the fan on. No heating or cooling taking place. |
|
||||
|
||||
### HVAC Action
|
||||
|
||||
The HVAC action describes the _current_ action. This is different from the mode, because if a device is set to heat, and the target temperature is already achieved, the device will not be actively heating anymore.
|
||||
|
||||
| Name | Description |
|
||||
| ------------------- | --------------------- |
|
||||
| `CURRENT_HVAC_OFF` | Device is turned off. |
|
||||
| `CURRENT_HVAC_HEAT` | Device is heating. |
|
||||
| `CURRENT_HVAC_COOL` | Device is cooling. |
|
||||
| `CURRENT_HVAC_DRY` | Device is dring. |
|
||||
| `CURRENT_HVAC_IDLE` | Device is idle. |
|
||||
|
||||
### Presets
|
||||
|
||||
A device can have different presets that it might want to show to the user. Common presets are "Away" or "Eco". There are a couple of built-in presets that will offer translations, but you're also allowed to add custom presets.
|
||||
|
||||
| Name | Description |
|
||||
| ---------- | ------------------------------------------------------ |
|
||||
| `ECO` | Device is running an energy-saving mode |
|
||||
| `AWAY` | Device is in away mode |
|
||||
| `BOOST` | Device turn all valve full up |
|
||||
| `COMFORT` | Device is in comfort mode |
|
||||
| `HOME` | Device is in home mode |
|
||||
| `SLEEP` | Device is prepared for sleep |
|
||||
| `ACTIVITY` | Device is reacting to activity (e.g. movement sensors) |
|
||||
|
||||
### Fan modes
|
||||
|
||||
A device's fan can have different states. There are a couple of built-in fan modes, but you're also allowed to use custom fan modes.
|
||||
|
||||
| Name |
|
||||
| ------------- |
|
||||
| `FAN_ON` |
|
||||
| `FAN_OFF` |
|
||||
| `FAN_AUTO` |
|
||||
| `FAN_LOW` |
|
||||
| `FAN_MEDIUM` |
|
||||
| `FAN_HIGH` |
|
||||
| `FAN_MIDDLE` |
|
||||
| `FAN_FOCUS` |
|
||||
| `FAN_DIFFUSE` |
|
||||
|
||||
### Swing modes
|
||||
|
||||
The device fan can have different swing modes that it wants the user to know about/control.
|
||||
|
||||
| Name | Description |
|
||||
| ------------------ | ------------------------------------------------ |
|
||||
| `SWING_OFF` | The fan is not swinging. |
|
||||
| `SWING_VERTICAL` | The fan is swinging vertical. |
|
||||
| `SWING_HORIZONTAL` | The fan is swinging horizontal. |
|
||||
| `SWING_BOTH` | The fan is swinging both horizontal and vertical. |
|
||||
|
||||
### Supported features
|
||||
|
||||
Supported features constants are combined using the bitwise or (`|`) operator.
|
||||
|
||||
| Name | Description |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| `SUPPORT_TARGET_TEMPERATURE` | The device supports a target temperature. |
|
||||
| `SUPPORT_TARGET_TEMPERATURE_RANGE` | The device supports a ranged target temperature. Used for HVAC modes `heat_cool` and `auto` |
|
||||
| `SUPPORT_TARGET_HUMIDITY` | The device supports a target humidity. |
|
||||
| `SUPPORT_FAN_MODE` | The device supports fan modes. |
|
||||
| `SUPPORT_PRESET_MODE` | The device supports presets. |
|
||||
| `SUPPORT_SWING_MODE` | The device supports swing modes. |
|
||||
| `SUPPORT_AUX_HEAT` | The device supports auxiliary heaters. |
|
||||
|
||||
## Methods
|
||||
|
||||
### Set hvac mode
|
||||
|
||||
```python
|
||||
class MyClimateDevice(ClimateDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
"""Set new target hvac mode."""
|
||||
```
|
||||
|
||||
### Set preset mode
|
||||
|
||||
```python
|
||||
class MyClimateDevice(ClimateDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def set_preset_mode(self, preset_mode):
|
||||
"""Set new target preset mode."""
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode):
|
||||
"""Set new target preset mode."""
|
||||
```
|
||||
|
||||
### Set fan mode
|
||||
|
||||
```python
|
||||
class MyClimateDevice(ClimateDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def set_fan_mode(self, fan_mode):
|
||||
"""Set new target fan mode."""
|
||||
|
||||
async def async_set_fan_mode(self, fan_mode):
|
||||
"""Set new target fan mode."""
|
||||
```
|
||||
|
||||
### Set humidity
|
||||
|
||||
```python
|
||||
class MyClimateDevice(ClimateDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def set_humidity(self, humidity):
|
||||
"""Set new target humidity."""
|
||||
|
||||
async def async_set_humidity(self, humidity):
|
||||
"""Set new target humidity."""
|
||||
```
|
||||
|
||||
### Set swing mode
|
||||
|
||||
```python
|
||||
class MyClimateDevice(ClimateDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def set_swing_mode(self, swing_mode):
|
||||
"""Set new target swing operation."""
|
||||
|
||||
async def async_set_swing_mode(self, swing_mode):
|
||||
"""Set new target swing operation."""
|
||||
```
|
||||
|
||||
### Set temperature
|
||||
|
||||
```python
|
||||
class MyClimateDevice(ClimateDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
```
|
||||
|
||||
### Control auxiliary heater
|
||||
|
||||
```python
|
||||
class MyClimateDevice(ClimateDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def turn_aux_heat_on(self):
|
||||
"""Turn auxiliary heater on."""
|
||||
|
||||
async def async_turn_aux_heat_on(self):
|
||||
"""Turn auxiliary heater on."""
|
||||
|
||||
# Implement one of these methods.
|
||||
|
||||
def turn_aux_heat_off(self):
|
||||
"""Turn auxiliary heater off."""
|
||||
|
||||
async def async_turn_aux_heat_off(self):
|
||||
"""Turn auxiliary heater off."""
|
||||
```
|
232
website/versioned_docs/version-0.101.0/hassio_addon_config.md
Normal file
232
website/versioned_docs/version-0.101.0/hassio_addon_config.md
Normal file
@ -0,0 +1,232 @@
|
||||
---
|
||||
title: Add-On Configuration
|
||||
id: version-0.101.0-hassio_addon_config
|
||||
original_id: hassio_addon_config
|
||||
---
|
||||
|
||||
Each add-on is stored in a folder. The file structure looks like this:
|
||||
|
||||
```text
|
||||
addon_name/
|
||||
apparmor.txt
|
||||
build.json
|
||||
CHANGELOG.md
|
||||
config.json
|
||||
Dockerfile
|
||||
icon.png
|
||||
logo.png
|
||||
README.md
|
||||
run.sh
|
||||
```
|
||||
|
||||
## Add-on script
|
||||
|
||||
As with every Docker container, you will need a script to run when the container is started. A user might run many add-ons, so it is encouraged to try to stick to Bash scripts if you're doing simple things.
|
||||
|
||||
All our Images have also [bashio][bashio] installed. It contains a set of commonly used operations and can be used to be included in add-ons to reduce code duplication across add-ons and therefore making it easier to develop and maintain add-ons.
|
||||
|
||||
When developing your script:
|
||||
|
||||
- `/data` is a volume for persistent storage.
|
||||
- `/data/options.json` contains the user configuration. You can use bashio or `jq` inside your shell script to parse this data.
|
||||
|
||||
```bash
|
||||
CONFIG_PATH=/data/options.json
|
||||
|
||||
TARGET="$(jq --raw-output '.target' $CONFIG_PATH)"
|
||||
```
|
||||
|
||||
So if your `options` contain
|
||||
```json
|
||||
{ "target": "beer" }
|
||||
```
|
||||
then there will be a variable `TARGET` containing `beer` in the environment of your bash file afterwards.
|
||||
|
||||
[bashio]: https://github.com/hassio-addons/bashio
|
||||
|
||||
## Add-on Docker file
|
||||
|
||||
All add-ons are based on latest Alpine Linux. Hass.io will automatically substitute the right base image based on the machine architecture. Add `tzdata` if you need run in a different timezone. `tzdata` Is is already added to our base images.
|
||||
|
||||
```
|
||||
ARG BUILD_FROM
|
||||
FROM $BUILD_FROM
|
||||
|
||||
ENV LANG C.UTF-8
|
||||
|
||||
# Install requirements for add-on
|
||||
RUN apk add --no-cache jq
|
||||
|
||||
# Copy data for add-on
|
||||
COPY run.sh /
|
||||
RUN chmod a+x /run.sh
|
||||
|
||||
CMD [ "/run.sh" ]
|
||||
```
|
||||
|
||||
If you don't use local build on device or our build script, make sure that the Dockerfile have also a set of labels include:
|
||||
```
|
||||
LABEL io.hass.version="VERSION" io.hass.type="addon" io.hass.arch="armhf|aarch64|i386|amd64"
|
||||
```
|
||||
|
||||
It is possible to use own base image with `build.json` or if you do not need support for automatic multi-arch building you can also use a simple docker `FROM`.
|
||||
|
||||
### Build Args
|
||||
|
||||
We support the following build arguments by default:
|
||||
|
||||
| ARG | Description |
|
||||
|-----|-------------|
|
||||
| BUILD_FROM | Hold image for dynamic builds or buildings over our systems.
|
||||
| BUILD_VERSION | Add-on version (read from `config.json`).
|
||||
| BUILD_ARCH | Hold current build arch inside.
|
||||
|
||||
## Add-on config
|
||||
|
||||
The config for an add-on is stored in `config.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "xy",
|
||||
"version": "1.2",
|
||||
"slug": "folder",
|
||||
"description": "long description",
|
||||
"arch": ["amd64"],
|
||||
"url": "website with more information about add-on (ie a forum thread for support)",
|
||||
"startup": "application",
|
||||
"boot": "auto",
|
||||
"ports": {
|
||||
"123/tcp": 123
|
||||
},
|
||||
"map": ["config:rw", "ssl"],
|
||||
"options": {},
|
||||
"schema": {},
|
||||
"image": "repo/{arch}-my-custom-addon"
|
||||
}
|
||||
```
|
||||
|
||||
| Key | Type | Required | Description |
|
||||
| --- | ---- | -------- | ----------- |
|
||||
| name | string | yes | Name of the add-on
|
||||
| version | string | yes | Version of the add-on
|
||||
| slug | string | yes | Slug of the add-on
|
||||
| description | string | yes | Description of the add-on
|
||||
| arch | list | yes | List of supported arch: `armhf`, `armv7`, `aarch64`, `amd64`, `i386`.
|
||||
| machine | list | no | Default it support any machine type. You can select that this add-on run only on specific machines.
|
||||
| url | url | no | Homepage of the addon. Here you can explain the add-ons and options.
|
||||
| startup | bool | yes | `initialize` will start addon on setup of Hass.io. `system` is for things like databases and not dependent on other things. `services` will start before Home Assistant, while `application` is started afterwards. Finally `once` is for applications that don't run as a daemon.
|
||||
| webui | string | no | A URL for web interface of this add-on. Like `http://[HOST]:[PORT:2839]/dashboard`, the port needs the internal port, which will be replaced with the effective port. It is also possible to bind the proto part to a config options with: `[PROTO:option_name]://[HOST]:[PORT:2839]/dashboard` and he lookup if they is True and going to `https`.
|
||||
| boot | string | yes | `auto` by system and manual or only `manual`
|
||||
| ports | dict | no | Network ports to expose from the container. Format is `"container-port/type": host-port`. If host-port is `null`, the mapping is disabled.
|
||||
| ports_description | dict | no | Network ports description mapping. Format is `"container-port/type": "description of this port"`.
|
||||
| host_network | bool | no | If that is True, the add-on run on host network.
|
||||
| host_ipc | bool | no | Default False. Allow to share the IPC namespace with others.
|
||||
| host_dbus | bool | no | Default False. Map Host dbus service into add-on.
|
||||
| host_pid | bool | no | Default False. Allow to run container on host PID namespace. Work only for not protected add-ons.
|
||||
| devices | list | no | Device list to map into the add-on. Format is: `<path_on_host>:<path_in_container>:<cgroup_permissions>`. i.e. `/dev/ttyAMA0:/dev/ttyAMA0:rwm`
|
||||
| udev | bool | no | Default False. Set this True, if your container run a own udev process.
|
||||
| auto_uart | bool | no | Default False. Auto mapping all UART/Serial device from host into add-on.
|
||||
| homeassistant | string | no | Pin a minimum required Home Assistant version for such Add-on. Value is a version string like `0.91.2`.
|
||||
| hassio_role | str | no | Default `default`. Role based access to Hass.io API. Available: `default`, `homeassistant`, `backup`, `manager`, `admin`.
|
||||
| hassio_api | bool | no | This add-on can access to Hass.io REST API. It set the host alias `hassio`.
|
||||
| homeassistant_api | bool | no | This add-on can access to Hass.io Home-Assistant REST API proxy. Use `http://hassio/homeassistant/api`.
|
||||
| docker_api | bool | no | Allow read-oly access to docker API for add-on. Work only for not protected add-ons.
|
||||
| privileged | list | no | Privilege for access to hardware/system. Available access: `NET_ADMIN`, `SYS_ADMIN`, `SYS_RAWIO`, `SYS_TIME`, `SYS_NICE`, `SYS_RESOURCE`, `SYS_PTRACE`, `SYS_MODULE`, `DAC_READ_SEARCH`.
|
||||
| full_access | bool | no | Give full access to hardware like the privileged mode in docker. Work only for not protected add-ons.
|
||||
| apparmor | bool/string | no | Enable or disable AppArmor support. If it is enable, you can also use custom profiles with the name of the profile.
|
||||
| map | list | no | List of maps for additional Hass.io folders. Possible values: `config`, `ssl`, `addons`, `backup`, `share`. Defaults to `ro`, which you can change by adding `:rw` to the end of the name.
|
||||
| environment | dict | no | A dict of environment variable to run add-on.
|
||||
| audio | bool | no | Boolean. Mark this add-on to use internal an audio system. The ALSA configuration for this add-on will be mount automatic.
|
||||
| gpio | bool | no | Boolean. If this is set to True, `/sys/class/gpio` will map into add-on for access to GPIO interface from kernel. Some library need also `/dev/mem` and `SYS_RAWIO` for read/write access to this device. On system with AppArmor enabled, you need disable AppArmor or better for security, provide you own profile for the add-on.
|
||||
| devicetree | bool | no | Boolean. If this is set to True, `/device-tree` will map into add-on.
|
||||
| kernel_modules | bool | no | Map host kernel modules and config into add-on (readonly).
|
||||
| stdin | bool | no | Boolean. If that is enable, you can use the STDIN with Hass.io API.
|
||||
| legacy | bool | no | Boolean. If the docker image have no hass.io labels, you can enable the legacy mode to use the config data.
|
||||
| options | dict | yes | Default options value of the add-on
|
||||
| schema | dict | yes | Schema for options value of the add-on. It can be `False` to disable schema validation and use custom options.
|
||||
| image | string | no | For use with Docker Hub and other container registries.
|
||||
| timeout | integer | no | Default 10 (second). The timeout to wait until the docker is done or will be killed.
|
||||
| tmpfs | string | no | Mount a tmpfs file system in `/tmpfs`. Valide format for this option is : `size=XXXu,uid=N,rw`. Size is mandatory, valid units (`u`) are `k`, `m` and `g` and `XXX` has to be replaced by a number. `uid=N` (with `N` the uid number) and `rw` are optional.
|
||||
| discovery | list | no | A list of services they this Add-on allow to provide for Home Assistant. Currently supported: `mqtt`
|
||||
| services | list | no | A list of services they will be provided or consumed with this Add-on. Format is `service`:`function` and functions are: `provide` (this add-on can provide this service), `want` (this add-on can use this service) or `need` (this add-on need this service to work correctly).
|
||||
| auth_api | bool | no | Allow access to Home Assistent user backend.
|
||||
| ingress | bool | no | Enable the ingress feature for the Add-on
|
||||
| ingress_port | integer | no | Default `8099`. For Add-ons they run on host network, you can use `0` and read the port later on API.
|
||||
| ingress_entry | string | no | Modify the URL entry point from `/`.
|
||||
| panel_icon | string | no | Default: mdi:puzzle. MDI icon for the menu panel integration.
|
||||
| panel_title | string | no | Default add-on name, but can Modify with this options.
|
||||
| panel_admin | bool | no | Default True. Make menu entry only available with admin privileged.
|
||||
| snapshot_exlude | list | no | List of file/path with glob support they are excluded from snapshots.
|
||||
|
||||
|
||||
### Options / Schema
|
||||
|
||||
The `options` dictionary contains all available options and their default value. Set the default value to `null` if the value is required to be given by the user before the add-on can start, and it show it inside default values. Only nested arrays and dictionaries are supported with a deep of two size. If you want make an option optional, put `?` to the end of data type, otherwise it will be a required value.
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "custom things",
|
||||
"logins": [
|
||||
{ "username": "beer", "password": "123456" },
|
||||
{ "username": "cheep", "password": "654321" }
|
||||
],
|
||||
"random": ["haha", "hihi", "huhu", "hghg"],
|
||||
"link": "http://example.com/",
|
||||
"size": 15,
|
||||
"count": 1.2
|
||||
}
|
||||
```
|
||||
|
||||
The `schema` looks like `options` but describes how we should validate the user input. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "str",
|
||||
"logins": [
|
||||
{ "username": "str", "password": "str" }
|
||||
],
|
||||
"random": ["match(^\w*$)"],
|
||||
"link": "url",
|
||||
"size": "int(5,20)",
|
||||
"count": "float",
|
||||
"not_need": "str?"
|
||||
}
|
||||
```
|
||||
|
||||
We support:
|
||||
- str / str(min,) / str(,max) / str(min,max)
|
||||
- bool
|
||||
- int / int(min,) / int(,max) / int(min,max)
|
||||
- float / float(min,) / float(,max) / float(min,max)
|
||||
- email
|
||||
- url
|
||||
- port
|
||||
- match(REGEX)
|
||||
- list(val1|val2|...)
|
||||
|
||||
## Add-on extended build
|
||||
|
||||
Additional build options for an add-on is stored in `build.json`. This file will be read from our build systems.
|
||||
You need this only, if you not use the default images or need additionals things.
|
||||
|
||||
```json
|
||||
{
|
||||
"build_from": {
|
||||
"armhf": "mycustom/base-image:latest"
|
||||
},
|
||||
"squash": false,
|
||||
"args": {
|
||||
"my_build_arg": "xy"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Key | Required | Description |
|
||||
| --- | -------- | ----------- |
|
||||
| build_from | no | A dictionary with the hardware architecture as the key and the base Docker image as value.
|
||||
| squash | no | Default `False`. Be carfully with this option, you can not use the image for caching stuff after that!
|
||||
| args | no | Allow to set additional Docker build arguments as a dictionary.
|
||||
|
||||
We provide a set of [Base-Images][hassio-base] which should cover a lot of needs. If you don't want use the Alpine based version or need a specific Image tag, feel free to pin this requirements for you build with `build_from` option.
|
||||
|
||||
[hassio-base]: https://github.com/home-assistant/hassio-base
|
@ -0,0 +1,56 @@
|
||||
---
|
||||
title: Integration Quality Scale
|
||||
id: version-0.101.0-integration_quality_scale_index
|
||||
original_id: integration_quality_scale_index
|
||||
---
|
||||
|
||||
The Integration Quality Scale scores each integration based on the code quality and user experience. Each level of the quality scale consists of a list of requirements. If an integration matches all requirements, it's considered to have reached that level.
|
||||
|
||||
> Suggestions for changes can be done by creating an issue in the [architecture repo](https://github.com/home-assistant/architecture/issues/).
|
||||
|
||||
# No score
|
||||
|
||||
This integration passes the bare minimum requirements to become part of the index.
|
||||
|
||||
- Satisfy all requirements for [creating components](creating_component_code_review.md) and [creating platforms](creating_platform_code_review.md).
|
||||
- Configurable via `configuration.yaml`
|
||||
|
||||
# Silver 🥈
|
||||
|
||||
This integration is able to cope when things go wrong. It will not print any exceptions nor will it fill the log with retry attempts.
|
||||
|
||||
- Connection/configuration is handled via a component.
|
||||
- Set an appropriate `SCAN_INTERVAL` (if a polling integration)
|
||||
- Raise `PlatformNotReady` if unable to connect during platform setup (if appropriate)
|
||||
- Handles expiration of auth credentials. Refresh if possible or print correct error and fail setup. If based on a config entry, should trigger a new config entry flow to re-authorize.
|
||||
- Handles internet unavailable. Log a warning once when unavailable, log once when reconnected.
|
||||
- Handles device/service unavailable. Log a warning once when unavailable, log once when reconnected.
|
||||
- Set `available` property to `False` if appropriate ([docs](entity_index.md#generic-properties))
|
||||
- Entities have unique ID (if available) ([docs](entity_registry_index.md#unique-id-requirements))
|
||||
|
||||
# Gold 🥇
|
||||
|
||||
This is a solid integration that is able to survive poor conditions and can be configured via the user interface.
|
||||
|
||||
- Configurable via config entries.
|
||||
- Don't allow configuring already configured device/service (example: no 2 entries for same hub)
|
||||
- Tests for the config flow
|
||||
- Discoverable (if available)
|
||||
- Entities have device info (if available) ([docs](device_registry_index.md#defining-devices))
|
||||
- States are translated in the frontend (text-based sensors only, [docs](internationalization_index.md))
|
||||
- Tests for reading data from/controlling the integration ([docs](development_testing.md))
|
||||
- Has a code owner ([docs](creating_integration_manifest.md#code-owners))
|
||||
|
||||
# Platinum 🏆
|
||||
|
||||
Best of the best. The integration is completely async, meaning it's super fast. Integrations that reach platinum level will require approval by the code owner for each PR.
|
||||
|
||||
- Set appropriate `PARALLEL_UPDATES` constant
|
||||
- Support config entry unloading (called when config entry is removed)
|
||||
- Integration + dependency are async
|
||||
- Uses aiohttp and allows passing in websession (if making HTTP requests)
|
||||
|
||||
# Internal 🏠
|
||||
|
||||
Integrations which are part of Home Assistant are not rated but marked as **internal**.
|
||||
|
243
website/versioned_docs/version-0.101.0/lovelace_custom_card.md
Normal file
243
website/versioned_docs/version-0.101.0/lovelace_custom_card.md
Normal file
@ -0,0 +1,243 @@
|
||||
---
|
||||
title: Lovelace: Custom Cards
|
||||
id: version-0.101.0-lovelace_custom_card
|
||||
original_id: lovelace_custom_card
|
||||
---
|
||||
|
||||
[Lovelace](https://www.home-assistant.io/lovelace/) is our new approach to defining your user interface for Home Assistant. We offer a lot of built-in cards, but you're not just limited to the ones that we decided to include in the Lovelace UI. You can build and use your own!
|
||||
|
||||
## API
|
||||
|
||||
You define your custom card as a [custom element](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements). It's up to you to decide how to render your DOM inside your element. You can use Polymer, Angular, Preact or any other popular framework (except for React – [more info on React here](https://custom-elements-everywhere.com/#react)).
|
||||
|
||||
```js
|
||||
const element = document.createElement('some-custom-card');
|
||||
```
|
||||
|
||||
Home Assistant will call `setConfig(config)` when the configuration changes (rare). If you throw an exception if the configuration is invalid, Lovelace will render an error card to notify the user.
|
||||
|
||||
```js
|
||||
try {
|
||||
element.setConfig(config);
|
||||
} catch (err) {
|
||||
showErrorCard(err.message, config);
|
||||
}
|
||||
```
|
||||
|
||||
Home Assistant will set the `hass` property when the state of Home Assistant changes (frequent). Whenever the state changes, the component will have to update itself to represent the latest state.
|
||||
|
||||
```js
|
||||
element.hass = hass;
|
||||
```
|
||||
|
||||
Your card can define a `getCardSize` method that returns the size of your card as a number. A height of 1 is equivalent to 50 pixels. This will help Home Assistant distribute the cards evenly over the columns. A card size of `1` will be assumed if the method is not defined.
|
||||
|
||||
```js
|
||||
if ('getCardSize' in element) {
|
||||
return element.getCardSize();
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
```
|
||||
|
||||
## Defining your card
|
||||
|
||||
Create a new file in your Home Assistant config dir as `<config>/www/content-card-example.js` and put in the following contents:
|
||||
|
||||
```js
|
||||
class ContentCardExample extends HTMLElement {
|
||||
set hass(hass) {
|
||||
if (!this.content) {
|
||||
const card = document.createElement('ha-card');
|
||||
card.header = 'Example card';
|
||||
this.content = document.createElement('div');
|
||||
this.content.style.padding = '0 16px 16px';
|
||||
card.appendChild(this.content);
|
||||
this.appendChild(card);
|
||||
}
|
||||
|
||||
const entityId = this.config.entity;
|
||||
const state = hass.states[entityId];
|
||||
const stateStr = state ? state.state : 'unavailable';
|
||||
|
||||
this.content.innerHTML = `
|
||||
The state of ${entityId} is ${stateStr}!
|
||||
<br><br>
|
||||
<img src="http://via.placeholder.com/350x150">
|
||||
`;
|
||||
}
|
||||
|
||||
setConfig(config) {
|
||||
if (!config.entity) {
|
||||
throw new Error('You need to define an entity');
|
||||
}
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
// The height of your card. Home Assistant uses this to automatically
|
||||
// distribute all cards over the available columns.
|
||||
getCardSize() {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('content-card-example', ContentCardExample);
|
||||
```
|
||||
|
||||
## Referencing your new card
|
||||
|
||||
In our example card we defined a card with the tag `content-card-example` (see last line), so our card type will be `custom:content-card-example`. And because you created the file in your `<config>/www` directory, it will be accessible in your browser via the url `/local/` (if you have recently added the www folder you will need to re-start home assistant for files to be picked up).
|
||||
|
||||
```yaml
|
||||
# Example Lovelace configuration
|
||||
resources:
|
||||
- url: /local/content-card-example.js
|
||||
type: js
|
||||
views:
|
||||
- name: Example
|
||||
cards:
|
||||
- type: "custom:content-card-example"
|
||||
entity: input_boolean.switch_tv
|
||||
```
|
||||
|
||||
## Advanced example
|
||||
|
||||
Resources to load in Lovelace can be imported as a JS script, an HTML import or as a JS module import. Below is an example of a custom card using JS modules that does all the fancy things.
|
||||
|
||||

|
||||
|
||||
Create a new file in your Home Assistant config dir as `<config>/www/wired-cards.js` and put in the following contents:
|
||||
|
||||
```js
|
||||
import "https://unpkg.com/wired-card@0.8.1/wired-card.js?module";
|
||||
import "https://unpkg.com/wired-toggle@0.8.0/wired-toggle.js?module";
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
css
|
||||
} from "https://unpkg.com/lit-element@2.0.1/lit-element.js?module";
|
||||
|
||||
function loadCSS(url) {
|
||||
const link = document.createElement("link");
|
||||
link.type = "text/css";
|
||||
link.rel = "stylesheet";
|
||||
link.href = url;
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
loadCSS("https://fonts.googleapis.com/css?family=Gloria+Hallelujah");
|
||||
|
||||
class WiredToggleCard extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {},
|
||||
config: {}
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<wired-card elevation="2">
|
||||
${this.config.entities.map(ent => {
|
||||
const stateObj = this.hass.states[ent];
|
||||
return stateObj
|
||||
? html`
|
||||
<div class="state">
|
||||
${stateObj.attributes.friendly_name}
|
||||
<wired-toggle
|
||||
.checked="${stateObj.state === "on"}"
|
||||
@change="${ev => this._toggle(stateObj)}"
|
||||
></wired-toggle>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div class="not-found">Entity ${ent} not found.</div>
|
||||
`;
|
||||
})}
|
||||
</wired-card>
|
||||
`;
|
||||
}
|
||||
|
||||
setConfig(config) {
|
||||
if (!config.entities) {
|
||||
throw new Error("You need to define entities");
|
||||
}
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
// The height of your card. Home Assistant uses this to automatically
|
||||
// distribute all cards over the available columns.
|
||||
getCardSize() {
|
||||
return this.config.entities.length + 1;
|
||||
}
|
||||
|
||||
_toggle(state) {
|
||||
this.hass.callService("homeassistant", "toggle", {
|
||||
entity_id: state.entity_id
|
||||
});
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
font-family: "Gloria Hallelujah", cursive;
|
||||
}
|
||||
wired-card {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
}
|
||||
.state {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
.not-found {
|
||||
background-color: yellow;
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
padding: 8px;
|
||||
}
|
||||
wired-toggle {
|
||||
margin-left: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define("wired-toggle-card", WiredToggleCard);
|
||||
```
|
||||
|
||||
And for your configuration:
|
||||
|
||||
```yaml
|
||||
# Example Lovelace configuration
|
||||
resources:
|
||||
- url: /local/wired-cards.js
|
||||
type: module
|
||||
views:
|
||||
- name: Example
|
||||
cards:
|
||||
- type: "custom:wired-toggle-card"
|
||||
entities:
|
||||
- input_boolean.switch_ac_kitchen
|
||||
- input_boolean.switch_ac_livingroom
|
||||
- input_boolean.switch_tv
|
||||
```
|
||||
|
||||
## Recommended Design Elements
|
||||
We are currently migrating from using Paper Elements to MWC (Material Web Component) Elements.
|
||||
|
||||
If an element exists in the below repository for MWC. We recommended using it.
|
||||
- [MWC (Material Web Components)](https://material-components.github.io/material-components-web-components/demos/index.html)
|
||||
|
||||
If an element does not exist in MWC, we default to using Paper Elements.
|
||||
- [Paper Elements](https://www.webcomponents.org/collection/PolymerElements/paper-elements)
|
||||
|
||||
## Advanced Resources
|
||||
Community Maintained Boilerplate Card - Advanced Template (Typescript, Rollup, Linting, etc.)
|
||||
- [Boilerplate Card](https://github.com/custom-cards/boilerplate-card)
|
||||
|
||||
Developer Documentation for [HACS](https://hacs.netlify.com/) (Home Assistant Community Store).
|
||||
- [HACS Plugin Docs](https://hacs.netlify.com/developer/plugin/)
|
182
website/versioned_sidebars/version-0.101.0-sidebars.json
Normal file
182
website/versioned_sidebars/version-0.101.0-sidebars.json
Normal file
@ -0,0 +1,182 @@
|
||||
{
|
||||
"version-0.101.0-Architecture": {
|
||||
"Architecture": [
|
||||
"version-0.101.0-architecture_index",
|
||||
"version-0.101.0-architecture_components",
|
||||
"version-0.101.0-architecture_entities",
|
||||
"version-0.101.0-architecture_hassio"
|
||||
],
|
||||
"Entities": [
|
||||
"version-0.101.0-entity_index",
|
||||
"version-0.101.0-entity_air_quality",
|
||||
"version-0.101.0-entity_alarm_control_panel",
|
||||
"version-0.101.0-entity_binary_sensor",
|
||||
"version-0.101.0-entity_climate",
|
||||
"version-0.101.0-entity_cover",
|
||||
"version-0.101.0-entity_fan",
|
||||
"version-0.101.0-entity_light",
|
||||
"version-0.101.0-entity_lock",
|
||||
"version-0.101.0-entity_media_player",
|
||||
"version-0.101.0-entity_remote",
|
||||
"version-0.101.0-entity_sensor",
|
||||
"version-0.101.0-entity_switch",
|
||||
"version-0.101.0-entity_vacuum",
|
||||
"version-0.101.0-entity_water_heater",
|
||||
"version-0.101.0-entity_weather"
|
||||
],
|
||||
"Authentication": [
|
||||
"version-0.101.0-auth_index",
|
||||
"version-0.101.0-auth_permissions",
|
||||
"version-0.101.0-auth_api",
|
||||
"version-0.101.0-auth_auth_provider",
|
||||
"version-0.101.0-auth_auth_module"
|
||||
],
|
||||
"Config Entries": [
|
||||
"version-0.101.0-config_entries_index"
|
||||
],
|
||||
"Data Entry Flow": [
|
||||
"version-0.101.0-data_entry_flow_index"
|
||||
],
|
||||
"Entity Registry": [
|
||||
"version-0.101.0-entity_registry_index",
|
||||
"version-0.101.0-entity_registry_disabled_by"
|
||||
],
|
||||
"Device Registry": [
|
||||
"version-0.101.0-device_registry_index"
|
||||
],
|
||||
"Area Registry": [
|
||||
"version-0.101.0-area_registry_index"
|
||||
]
|
||||
},
|
||||
"version-0.101.0-Extending Frontend": {
|
||||
"Frontend": [
|
||||
"version-0.101.0-frontend_index",
|
||||
"version-0.101.0-frontend_architecture",
|
||||
"version-0.101.0-frontend_development",
|
||||
"version-0.101.0-frontend_data",
|
||||
"version-0.101.0-frontend_external_auth",
|
||||
"version-0.101.0-frontend_external_bus"
|
||||
],
|
||||
"Extending the frontend": [
|
||||
"version-0.101.0-frontend_add_card",
|
||||
"version-0.101.0-frontend_add_more_info",
|
||||
"version-0.101.0-frontend_add_websocket_api"
|
||||
],
|
||||
"Custom UI": [
|
||||
"version-0.101.0-lovelace_custom_card",
|
||||
"version-0.101.0-frontend_creating_custom_panels"
|
||||
]
|
||||
},
|
||||
"version-0.101.0-Extending HASS": {
|
||||
"Development Workflow": [
|
||||
"version-0.101.0-development_index",
|
||||
"version-0.101.0-development_environment",
|
||||
"version-0.101.0-development_submitting",
|
||||
"version-0.101.0-development_guidelines",
|
||||
"version-0.101.0-development_testing",
|
||||
"version-0.101.0-development_catching_up"
|
||||
],
|
||||
"Building Integrations": [
|
||||
"version-0.101.0-creating_integration_file_structure",
|
||||
"version-0.101.0-creating_integration_manifest",
|
||||
"version-0.101.0-creating_component_index",
|
||||
"version-0.101.0-config_entries_config_flow_handler",
|
||||
"version-0.101.0-config_entries_options_flow_handler",
|
||||
"version-0.101.0-configuration_yaml_index",
|
||||
"version-0.101.0-dev_101_services",
|
||||
"version-0.101.0-creating_platform_index",
|
||||
"version-0.101.0-creating_component_generic_discovery",
|
||||
"version-0.101.0-reproduce_state_index"
|
||||
],
|
||||
"Development Checklist": [
|
||||
"version-0.101.0-development_checklist",
|
||||
"version-0.101.0-creating_component_code_review",
|
||||
"version-0.101.0-creating_platform_code_review",
|
||||
"version-0.101.0-integration_quality_scale_index"
|
||||
],
|
||||
"Home Assistant Core 101": [
|
||||
"version-0.101.0-dev_101_index",
|
||||
"version-0.101.0-dev_101_hass",
|
||||
"version-0.101.0-dev_101_events",
|
||||
"version-0.101.0-dev_101_states",
|
||||
"version-0.101.0-dev_101_config"
|
||||
],
|
||||
"Device Automations": [
|
||||
"version-0.101.0-device_automation_index",
|
||||
"version-0.101.0-device_automation_trigger",
|
||||
"version-0.101.0-device_automation_condition",
|
||||
"version-0.101.0-device_automation_action"
|
||||
],
|
||||
"Misc": [
|
||||
"version-0.101.0-development_validation",
|
||||
"version-0.101.0-development_typing"
|
||||
]
|
||||
},
|
||||
"version-0.101.0-Misc": {
|
||||
"Introduction": [
|
||||
"version-0.101.0-misc"
|
||||
],
|
||||
"External API": [
|
||||
"version-0.101.0-external_api_rest",
|
||||
"version-0.101.0-external_api_rest_python",
|
||||
"version-0.101.0-external_api_websocket",
|
||||
"version-0.101.0-external_api_server_sent_events"
|
||||
],
|
||||
"Internationalization": [
|
||||
"version-0.101.0-internationalization_index",
|
||||
"version-0.101.0-internationalization_backend_localization",
|
||||
"version-0.101.0-internationalization_custom_component_localization",
|
||||
"version-0.101.0-internationalization_translation"
|
||||
],
|
||||
"Documentation": [
|
||||
"version-0.101.0-documentation_index",
|
||||
"version-0.101.0-documentation_standards",
|
||||
"version-0.101.0-documentation_create_page"
|
||||
],
|
||||
"Intents": [
|
||||
"version-0.101.0-intent_index",
|
||||
"version-0.101.0-intent_firing",
|
||||
"version-0.101.0-intent_handling",
|
||||
"version-0.101.0-intent_conversation",
|
||||
"version-0.101.0-intent_builtin"
|
||||
],
|
||||
"Native App Integration": [
|
||||
"version-0.101.0-app_integration_index",
|
||||
"version-0.101.0-app_integration_setup",
|
||||
"version-0.101.0-app_integration_sending_data",
|
||||
"version-0.101.0-app_integration_sensors",
|
||||
"version-0.101.0-app_integration_notifications",
|
||||
"version-0.101.0-app_integration_webview"
|
||||
],
|
||||
"Building a Python library": [
|
||||
"version-0.101.0-api_lib_index",
|
||||
"version-0.101.0-api_lib_auth",
|
||||
"version-0.101.0-api_lib_data_models"
|
||||
],
|
||||
"asyncio": [
|
||||
"version-0.101.0-asyncio_index",
|
||||
"version-0.101.0-asyncio_101",
|
||||
"version-0.101.0-asyncio_categorizing_functions",
|
||||
"version-0.101.0-asyncio_working_with_async"
|
||||
],
|
||||
"Hass.io": [
|
||||
"version-0.101.0-hassio_debugging",
|
||||
"version-0.101.0-hassio_hass"
|
||||
],
|
||||
"Hass.io Add-Ons": [
|
||||
"version-0.101.0-hassio_addon_index",
|
||||
"version-0.101.0-hassio_addon_tutorial",
|
||||
"version-0.101.0-hassio_addon_config",
|
||||
"version-0.101.0-hassio_addon_communication",
|
||||
"version-0.101.0-hassio_addon_testing",
|
||||
"version-0.101.0-hassio_addon_publishing",
|
||||
"version-0.101.0-hassio_addon_presentation",
|
||||
"version-0.101.0-hassio_addon_repository",
|
||||
"version-0.101.0-hassio_addon_security"
|
||||
],
|
||||
"Maintainer docs": [
|
||||
"version-0.101.0-maintenance",
|
||||
"version-0.101.0-releasing"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
[
|
||||
"0.101.0",
|
||||
"0.100.0",
|
||||
"0.99.3",
|
||||
"0.99.0",
|
||||
|
Loading…
x
Reference in New Issue
Block a user