mirror of
https://github.com/home-assistant/developers.home-assistant.git
synced 2025-07-24 01:36:32 +00:00
0.103
This commit is contained in:
parent
d6e780b17f
commit
58573bf449
@ -541,6 +541,196 @@
|
||||
"version-0.103.0/version-0.103.0-maintenance": {
|
||||
"title": "Maintenance"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-api_lib_auth": {
|
||||
"title": "Python Library: Authentication",
|
||||
"sidebar_label": "Authentication"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-api_lib_data_models": {
|
||||
"title": "Python Library: Modelling Data",
|
||||
"sidebar_label": "Modelling Data"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-api_lib_index": {
|
||||
"title": "Building a Python library for an API",
|
||||
"sidebar_label": "Introduction"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-app_integration_setup": {
|
||||
"title": "Connecting to an instance"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-asyncio_categorizing_functions": {
|
||||
"title": "Categorizing Functions"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-asyncio_working_with_async": {
|
||||
"title": "Working with Async"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-auth_api": {
|
||||
"title": "Authentication API",
|
||||
"sidebar_label": "API"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-auth_auth_provider": {
|
||||
"title": "Authentication Providers"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-auth_permissions": {
|
||||
"title": "Permissions"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-config_entries_config_flow_handler": {
|
||||
"title": "Integration Configuration",
|
||||
"sidebar_label": "Configuration"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-config_entries_index": {
|
||||
"title": "Config Entries",
|
||||
"sidebar_label": "Introduction"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-config_entries_options_flow_handler": {
|
||||
"title": "Integration Configuration Options",
|
||||
"sidebar_label": "Configuration Options"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-configuration_yaml_index": {
|
||||
"title": "Integration Configuration via YAML"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-creating_component_code_review": {
|
||||
"title": "Checklist for creating a component",
|
||||
"sidebar_label": "Component Checklist"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-creating_component_index": {
|
||||
"title": "Creating your first integration"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-creating_integration_manifest": {
|
||||
"title": "Integration Manifest",
|
||||
"sidebar_label": "Manifest"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-creating_platform_code_review": {
|
||||
"title": "Checklist for creating a platform",
|
||||
"sidebar_label": "Platform Checklist"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-data_entry_flow_index": {
|
||||
"title": "Data Entry Flow",
|
||||
"sidebar_label": "Introduction"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-dev_101_config": {
|
||||
"title": "Using Config"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-dev_101_events": {
|
||||
"title": "Using Events"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-dev_101_index": {
|
||||
"title": "Development 101",
|
||||
"sidebar_label": "Introduction"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-dev_101_services": {
|
||||
"title": "Integration Services",
|
||||
"sidebar_label": "Custom Services"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-dev_101_states": {
|
||||
"title": "Using States"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-development_catching_up": {
|
||||
"title": "Catching up with Reality"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-development_environment": {
|
||||
"title": "Set up Development Environment"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-development_guidelines": {
|
||||
"title": "Style guidelines"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-development_testing": {
|
||||
"title": "Testing your code"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-development_validation": {
|
||||
"title": "Validate the input"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-device_registry_index": {
|
||||
"title": "Device Registry",
|
||||
"sidebar_label": "Introduction"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-documentation_create_page": {
|
||||
"title": "Create a new page"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-documentation_index": {
|
||||
"title": "Documentation"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-entity_cover": {
|
||||
"title": "Cover Entity",
|
||||
"sidebar_label": "Cover"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-entity_fan": {
|
||||
"title": "Fan Entity",
|
||||
"sidebar_label": "Fan"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-entity_index": {
|
||||
"title": "Entity",
|
||||
"sidebar_label": "Introduction"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-entity_light": {
|
||||
"title": "Light Entity",
|
||||
"sidebar_label": "Light"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-entity_media_player": {
|
||||
"title": "Media Player Entity",
|
||||
"sidebar_label": "Media Player"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-entity_switch": {
|
||||
"title": "Switch Entity",
|
||||
"sidebar_label": "Switch"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-external_api_rest": {
|
||||
"title": "REST API"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-external_api_server_sent_events": {
|
||||
"title": "Server-sent events"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-frontend_add_websocket_api": {
|
||||
"title": "Extending the WebSocket API"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-frontend_creating_custom_ui": {
|
||||
"title": "Creating custom UI"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-frontend_development": {
|
||||
"title": "Frontend development",
|
||||
"sidebar_label": "Development"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-frontend_external_auth": {
|
||||
"title": "External Authentication"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-hassio_addon_config": {
|
||||
"title": "Add-On Configuration"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-hassio_addon_publishing": {
|
||||
"title": "Publishing your add-on"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-hassio_addon_tutorial": {
|
||||
"title": "Tutorial: Making your first add-on"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-hassio_debugging": {
|
||||
"title": "Debugging Hass.io"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-hassio_hass": {
|
||||
"title": "Hass.io <> Home Assistant integration development",
|
||||
"sidebar_label": "Integration development"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-intent_conversation": {
|
||||
"title": "Registering sentences"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-intent_firing": {
|
||||
"title": "Firing intents"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-intent_handling": {
|
||||
"title": "Handling intents"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-internationalization_backend_localization": {
|
||||
"title": "Backend Localization"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-internationalization_translation": {
|
||||
"title": "Translation"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-lovelace_custom_card": {
|
||||
"title": "Lovelace: Custom Cards"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-maintenance": {
|
||||
"title": "Maintenance"
|
||||
},
|
||||
"version-0.104.0/version-0.104.0-reproduce_state_index": {
|
||||
"title": "Reproduce State / Scene support"
|
||||
},
|
||||
"version-0.72/version-0.72-architecture_components": {
|
||||
"title": "Components Architecture",
|
||||
"sidebar_label": "Components"
|
||||
|
257
website/versioned_docs/version-0.104.0/api_lib_auth.md
Normal file
257
website/versioned_docs/version-0.104.0/api_lib_auth.md
Normal file
@ -0,0 +1,257 @@
|
||||
---
|
||||
title: Python Library: Authentication
|
||||
sidebar_label: Authentication
|
||||
id: version-0.104.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
|
||||
|
||||
extra = {"client_id": self.client_id, "client_secret": self.client_secret}
|
||||
|
||||
self._oauth = OAuth2Session(
|
||||
auto_refresh_kwargs=extra,
|
||||
client_id=client_id,
|
||||
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
|
||||
```
|
145
website/versioned_docs/version-0.104.0/api_lib_data_models.md
Normal file
145
website/versioned_docs/version-0.104.0/api_lib_data_models.md
Normal file
@ -0,0 +1,145 @@
|
||||
---
|
||||
title: Python Library: Modelling Data
|
||||
sidebar_label: Modelling Data
|
||||
id: version-0.104.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.104.0/api_lib_index.md
Normal file
46
website/versioned_docs/version-0.104.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.104.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:
|
||||
|
||||
```shell
|
||||
pip3 install -e ../my_lib_folder
|
||||
```
|
||||
|
||||
Now run Home Assistant without installing dependencies from PyPI to avoid overriding your package.
|
||||
|
||||
```shell
|
||||
hass --skip-pip
|
||||
```
|
@ -0,0 +1,89 @@
|
||||
---
|
||||
title: Connecting to an instance
|
||||
id: version-0.104.0-app_integration_setup
|
||||
original_id: app_integration_setup
|
||||
---
|
||||
|
||||
When a user first opens the app, they will need to connect to their local instance to authenticate and register the device.
|
||||
|
||||
## Authenticating the user
|
||||
|
||||
The local instance can be discovered if Home Assistant has the [zeroconf component] configured by searching for `_home-assistant._tcp.local.`. If not configured, the user will need to be asked for the local address of their instance.
|
||||
|
||||
When the address of the instance is known, the app will ask the user to authenticate via [OAuth2 with Home Assistant]. Home Assistant uses IndieAuth, which means that to be able to redirect to a url that triggers your app, you need to take some extra steps. Make sure to read the last paragraph of the "Clients" section thoroughly.
|
||||
|
||||
[zeroconf component]: https://www.home-assistant.io/components/zeroconf
|
||||
[OAuth2 with Home Assistant]: auth_api.md
|
||||
|
||||
## Registering the device
|
||||
|
||||
_This requires Home Assistant 0.90 or later._
|
||||
|
||||
Home Assistant has a `mobile_app` component that allows applications to register themselves and interact with the instance. This is a generic component to handle most common mobile application tasks. This component is extendable with custom interactions if your app needs more types of interactions than are offered by this component.
|
||||
|
||||
Once you have tokens to authenticate as a user, it's time to register the app with the mobile app component in Home Assistant.
|
||||
|
||||
### Getting Ready
|
||||
|
||||
First, you must ensure that the `mobile_app` component is loaded. There are two ways to do this:
|
||||
|
||||
- You can publish a Zeroconf/Bonjour record `_hass-mobile-app._tcp.local.` to trigger the automatic load of the `mobile_app` component. You should wait at least 60 seconds after publishing the record before continuing.
|
||||
- You can ask the user to add `mobile_app` to their configuration.yaml and restart Home Assistant. If the user already has `default_config` in their configuration, then `mobile_app` will have been already loaded.
|
||||
|
||||
You can confirm the `mobile_app` component has been loaded by checking the `components` array of the [`/api/config` REST API call](external_api_rest.md#get-api-config). If you continue to device registration and receive a 404 status code, then it most likely hasn't been loaded yet.
|
||||
|
||||
### Registering the device
|
||||
|
||||
To register the device, make an authenticated POST request to `/api/mobile_app/registrations`. [More info on making authenticated requests.](auth_api.md#making-authenticated-requests)
|
||||
|
||||
Example payload to send to the registration endpoint:
|
||||
|
||||
```json
|
||||
{
|
||||
"device_id": "ABCDEFGH",
|
||||
"app_id": "awesome_home",
|
||||
"app_name": "Awesome Home",
|
||||
"app_version": "1.2.0",
|
||||
"device_name": "Robbies iPhone",
|
||||
"manufacturer": "Apple, Inc.",
|
||||
"model": "iPhone X",
|
||||
"os_name": "iOS",
|
||||
"os_version": "iOS 10.12",
|
||||
"supports_encryption": true,
|
||||
"app_data": {
|
||||
"push_notification_key": "abcdef"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Key | Required | Type | Description |
|
||||
| --------------------- | -------- | ------ | --------------------------------------------------------------------------------------------------- |
|
||||
| `device_id` | V | string | A unique identifier for this device. New in Home Assistant 0.104 |
|
||||
| `app_id` | V | string | A unique identifier for this app. |
|
||||
| `app_name` | V | string | Name of the mobile app. |
|
||||
| `app_version` | V | string | Version of the mobile app. |
|
||||
| `device_name` | V | string | Name of the device running the app. |
|
||||
| `manufacturer` | V | string | The manufacturer of the device running the app. |
|
||||
| `model` | V | string | The model of the device running the app. |
|
||||
| `os_name` | V | string | The name of the OS running the app. |
|
||||
| `os_version` | V | string | The OS version of the device running the app. |
|
||||
| `supports_encryption` | V | bool | If the app supports encryption. See also the [encryption section](#encryption). |
|
||||
| `app_data` | | Dict | App data can be used if the app has a supporting component that extends `mobile_app` functionality. |
|
||||
|
||||
When you get a 200 response, the mobile app is registered with Home Assistant. The response is a JSON document and will contain the URLs on how to interact with the Home Assistant instance. You should permanently store this information.
|
||||
|
||||
```json
|
||||
{
|
||||
"cloudhook_url": "https://hooks.nabu.casa/randomlongstring123",
|
||||
"remote_ui_url": "https://randomlongstring123.ui.nabu.casa",
|
||||
"secret": "qwerty",
|
||||
"webhook_id": "abcdefgh"
|
||||
}
|
||||
```
|
||||
|
||||
| Key | Type | Description |
|
||||
| --------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `cloudhook_url` | string | The cloudhook URL provided by Home Assistant Cloud. Only will be provided if user is actively subscribed to Nabu Casa. |
|
||||
| `remote_ui_url` | string | The remote UI URL provided by Home Assistant Cloud. Only will be provided if user is actively subscribed to Nabu Casa. |
|
||||
| `secret` | string | The secret to use for encrypted communication. Will only be included if encryption is supported by both the app and the Home Assistant instance. [More info](app_integration_sending_data.md#implementing-encryption). |
|
||||
| `webhook_id` | string | The webhook ID that can be used to send data back. |
|
@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Categorizing Functions
|
||||
id: version-0.104.0-asyncio_categorizing_functions
|
||||
original_id: asyncio_categorizing_functions
|
||||
---
|
||||
|
||||
A piece of work within Home Assistant is represented by a function that will be invoked. It will either run inside our event loop or inside our thread pool, depending on if it is async safe.
|
||||
|
||||
Home Assistant uses the convention that all functions that must be run from within the event loop are prefixed with `async_`.
|
||||
|
||||
## The coroutine function
|
||||
|
||||
Coroutines are special functions based on Python’s generators syntax which allows them to suspend execution while waiting on a result.
|
||||
|
||||
Invoking a coroutine function will return a Generator object back, but will not actually begin execution. This object will execute the task when it is either awaited (from within another coroutine) or it is scheduled on the event loop.
|
||||
|
||||
To declare a function a coroutine, add `async` before the `def` of the function definition.
|
||||
|
||||
```python
|
||||
async def async_look_my_coroutine(target):
|
||||
result = await entity.async_turn_on()
|
||||
if result:
|
||||
print(f"hello {target}")
|
||||
|
||||
|
||||
hass.loop.create_task(async_look_my_coroutine("world"))
|
||||
```
|
||||
|
||||
In this example, we schedule the coroutine by calling `hass.loop.create_task`. This will add the coroutine to the queue of tasks to be run. When the event loop is running `async_look_my_coroutine` it will suspend the task when `await entity.async_turn_on()` is called. At that point a new task will be scheduled to execute `entity.async_turn_on()`. When that job has been executed, `async_look_my_coroutine` will resume.
|
||||
|
||||
## The callback function
|
||||
|
||||
This is a normal function that is considered safe to be run from within the event loop. A callback is unable to suspend itself and thus cannot do any I/O or call a coroutine. A callback is capable of scheduling a new task but it will not be able to wait for the results.
|
||||
|
||||
To declare a function as a callback, import the callback annotation from the core package and annotate your function.
|
||||
|
||||
A common use case for a callback in Home Assistant is as a listener for an event or a service call. It can process the incoming information and then schedule the right calls to be made. Example from the automation component.
|
||||
|
||||
```python
|
||||
from homeassistant.core import callback
|
||||
|
||||
|
||||
@callback
|
||||
def async_trigger_service_handler(service_call):
|
||||
"""Handle automation trigger service calls."""
|
||||
vars = service_call.data.get(ATTR_VARIABLES)
|
||||
for entity in component.async_extract_from_service(service_call):
|
||||
hass.loop.create_task(entity.async_trigger(vars, True))
|
||||
```
|
||||
|
||||
In this example, `entity.async_trigger` is a coroutine function. Invoking the coroutine function will return a coroutine task. The passed in parameters will be used when the task gets executed.
|
||||
|
||||
To execute the task we have to schedule it for execution on the event loop. This is done by calling `hass.loop.create_task`.
|
||||
|
||||
### Why even have callbacks?
|
||||
|
||||
You might wonder, if a coroutine can do everything a callback can do, why even have a callback. The reason is performance and better state consistency of the core API objects.
|
||||
|
||||
When coroutine A waits for coroutine B, it will suspend itself and schedule a new task to run B. This means that the event loop is now running A, B and then A again. If B is a callback, A will never have to suspend itself and thus the event loop is just running A. The consistency implication is that other events queued to run on the event loop continue to wait until callbacks complete, but will be interleaved when yielding to another coroutine.
|
||||
|
||||
## Event loop and thread safe
|
||||
|
||||
These are functions that are safe to run both in a thread and inside the event loop. These functions are usually performing a computation or transform data in memory. Anything that does I/O does not fall under this category. Many standard library functions fall in this category. For example generating the sum of a set of numbers using sum or merging two dictionaries.
|
||||
|
||||
There is no special annotation to mark functions as part of this category and care should be taken when using these functions from inside the event loop. When in doubt, look at their implementation.
|
||||
|
||||
## Other functions
|
||||
|
||||
These are all the functions that did not fit in the previous categories. These functions are either thread-safe or not considered safe to be run within the event loop. These are functions that use sleep, or perform I/O.
|
||||
|
||||
There is no special annotation necessary to be considered part of this category.
|
@ -0,0 +1,114 @@
|
||||
---
|
||||
title: Working with Async
|
||||
id: version-0.104.0-asyncio_working_with_async
|
||||
original_id: asyncio_working_with_async
|
||||
---
|
||||
|
||||
Although we have a backwards compatible API, using the async core directly will be a lot faster. Most core components have already been rewritten to leverage the async core. This includes the EntityComponent helper (foundation of light, switch, etc), scripts, groups and automation.
|
||||
|
||||
## Interacting with the core
|
||||
|
||||
[All methods in the Home Assistant core][dev-docs] are implemented in two flavors: an async version and a version to be called from other threads. The versions for other are merely wrappers that call the async version in a threadsafe manner.
|
||||
|
||||
So if you are making calls to the core (the hass object) from within a callback or coroutine, use the methods that start with async_. If you need to call an async_ function that is a coroutine, your task must also be a coroutine.
|
||||
|
||||
## Implementing an async component
|
||||
|
||||
To make a component async, implement an async_setup.
|
||||
|
||||
```python
|
||||
def setup(hass, config):
|
||||
"""Set up component."""
|
||||
# Code for setting up your component outside of the event loop.
|
||||
```
|
||||
|
||||
Will turn into:
|
||||
|
||||
```python
|
||||
async def async_setup(hass, config):
|
||||
"""Set up component."""
|
||||
# Code for setting up your component inside of the event loop.
|
||||
```
|
||||
|
||||
## Implementing an async platform
|
||||
|
||||
For platforms we support async setup. Instead of setup_platform you need to have a coroutine async_setup_platform.
|
||||
|
||||
```python
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up platform."""
|
||||
# Code for setting up your platform outside of the event loop.
|
||||
```
|
||||
|
||||
Will turn into:
|
||||
|
||||
```python
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up platform."""
|
||||
# Code for setting up your platform inside of the event loop.
|
||||
```
|
||||
|
||||
The only difference with the original parameters is that the `add_entities` function has been replaced by the async friendly callback `async_add_entities`.
|
||||
|
||||
## Implementing an async entity
|
||||
|
||||
You can make your entity async friendly by converting your update method to be async. This requires the dependency of your entities to also be async friendly!
|
||||
|
||||
```python
|
||||
class MyEntity(Entity):
|
||||
def update(self):
|
||||
"""Retrieve latest state."""
|
||||
self._state = fetch_state()
|
||||
```
|
||||
|
||||
Will turn into:
|
||||
|
||||
```python
|
||||
class MyEntity(Entity):
|
||||
async def async_update(self):
|
||||
"""Retrieve latest state."""
|
||||
self._state = await async_fetch_state()
|
||||
```
|
||||
|
||||
Make sure that all properties defined on your entity do not result in I/O being done. All data has to be fetched inside the update method and cached on the entity. This is because these properties are read from within the event loop and thus doing I/O will result in the core of Home Assistant waiting until your I/O is done.
|
||||
|
||||
## Calling async functions from threads
|
||||
|
||||
Sometimes it will happen that you’re in a thread and you want to call a function that is only available as async. Home Assistant includes a few async helper utilities to help with this.
|
||||
|
||||
In the following example, `say_hello` will schedule `async_say_hello` and block till the function has run and get the result back.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
|
||||
def say_hello(hass, target):
|
||||
return asyncio.run_coroutine_threadsafe(
|
||||
async_say_hello(hass, target), hass.loop
|
||||
).result()
|
||||
|
||||
|
||||
async def async_say_hello(hass, target):
|
||||
return f"Hello {target}!"
|
||||
```
|
||||
|
||||
## Calling sync functions from async
|
||||
|
||||
If you are running inside an async context, it might sometimes be necessary to call a sync function. Do this like this:
|
||||
|
||||
```python
|
||||
# hub.update() is a sync function.
|
||||
result = await hass.async_add_executor_job(hub.update)
|
||||
```
|
||||
|
||||
## Starting independent task from async
|
||||
|
||||
If you want to spawn a task that will not block the current async context, you can choose to create it as a task on the event loop. It will then be executed in parallel.
|
||||
|
||||
```python
|
||||
hass.async_create_task(async_say_hello(hass, target))
|
||||
```
|
||||
|
||||
|
||||
[dev-docs]: https://dev-docs.home-assistant.io/en/master/api/core.html
|
||||
[dev-docs-async]: https://dev-docs.home-assistant.io/en/dev/api/util.html#module-homeassistant.util.async
|
257
website/versioned_docs/version-0.104.0/auth_api.md
Normal file
257
website/versioned_docs/version-0.104.0/auth_api.md
Normal file
@ -0,0 +1,257 @@
|
||||
---
|
||||
title: Authentication API
|
||||
sidebar_label: API
|
||||
id: version-0.104.0-auth_api
|
||||
original_id: auth_api
|
||||
---
|
||||
|
||||
This page will describe the steps required for your application to authorize against and integrate with Home Assistant instances. [See a demo](https://hass-auth-demo.glitch.me) powered by our helper lib [home-assistant-js-websocket](https://github.com/home-assistant/home-assistant-js-websocket).
|
||||
|
||||
Each user has their own instance of Home Assistant which gives each user control over their own data. However, we also wanted to make it easy for third party developers to create applications that allow users to integrate with Home Assistant. To achieve this, we have adopted the [OAuth 2 specification][oauth2-spec] combined with the [OAuth 2 IndieAuth extension][indieauth-spec] for generating clients.
|
||||
|
||||
## Clients
|
||||
|
||||
Before you can ask the user to authorize their instance with your application, you will need a client. In traditional OAuth2, the server needs to generate a client before a user can authorize. However, as each server belongs to a user, we've adopted a slightly different approach from [IndieAuth][indieauth-clients].
|
||||
|
||||
The client ID you need to use is the website of your application. The redirect url has to be of the same host and port as the client ID. For example:
|
||||
|
||||
- client id: `https://www.my-application.io`
|
||||
- redirect uri: `https://www.my-application.io/hass/auth_callback`
|
||||
|
||||
If you require a different redirect url (ie, if building a native app), you can add an HTML tag to the content of the website of your application (the client ID) with an approved redirect url. For example, add this to your site to whitelist redirect uri `hass://auth`:
|
||||
|
||||
```html
|
||||
<link rel='redirect_uri' href='hass://auth'>
|
||||
```
|
||||
|
||||
Home Assistant will scan the first 10kB of a website for link tags.
|
||||
|
||||
## Authorize
|
||||
|
||||
[](https://www.websequencediagrams.com/?lz=dGl0bGUgQXV0aG9yaXphdGlvbiBGbG93CgpVc2VyIC0-IENsaWVudDogTG9nIGludG8gSG9tZSBBc3Npc3RhbnQKABoGIC0-IFVzZXI6AEMJZSB1cmwgAD4JACgOOiBHbyB0bwAeBWFuZCBhAC0ICgBQDgB1DACBFw5jb2RlAHELAE4RZXQgdG9rZW5zIGZvcgAoBgBBGlQAJQUK&s=qsd)
|
||||
|
||||
> All example URLs here are shown with extra spaces and new lines for display purposes only.
|
||||
|
||||
The authorize url should contain `client_id` and `redirect_uri` as query parameters.
|
||||
|
||||
```
|
||||
http://your-instance.com/auth/authorize?
|
||||
client_id=https%3A%2F%2Fhass-auth-demo.glitch.me&
|
||||
redirect_uri=https%3A%2F%2Fhass-auth-demo.glitch.me%2F%3Fauth_callback%3D1
|
||||
```
|
||||
|
||||
Optionally you can also include a `state` parameter, this will be added to the redirect uri. The state is perfect to store the instance url that you are authenticating with. Example:
|
||||
|
||||
```
|
||||
http://your-instance.com/auth/authorize?
|
||||
client_id=https%3A%2F%2Fhass-auth-demo.glitch.me&
|
||||
redirect_uri=https%3A%2F%2Fhass-auth-demo.glitch.me%2Fauth_callback&
|
||||
state=http%3A%2F%2Fhassio.local%3A8123
|
||||
```
|
||||
|
||||
The user will navigate to this link and be presented with instructions to log in and authorize your application. Once authorized, the user will be redirected back to the passed in redirect uri with the authorization code and state as part of the query parameters. Example:
|
||||
|
||||
```
|
||||
https://hass-auth-demo.glitch.me/auth_callback
|
||||
code=12345&
|
||||
state=http%3A%2F%2Fhassio.local%3A8123
|
||||
```
|
||||
|
||||
This authorization code can be exchanged for tokens by sending it to the token endpoint (see next section).
|
||||
|
||||
## Token
|
||||
|
||||
The token endpoint returns tokens given valid grants. This grant is either an authorization code retrieved from the authorize endpoint or a refresh token. In thee case of refresh token, the token endpoint is also capable of revoking a token.
|
||||
|
||||
All interactions with this endpoint need to be HTTP POST requests to `http://your-instance.com/auth/token` with the request body encoded in `application/x-www-form-urlencoded`.
|
||||
|
||||
### Authorization code
|
||||
|
||||
> All requests to the token endpoint need to contain the exact same client ID as was used to redirect the user to the authorize endpoint.
|
||||
|
||||
Use the grant type `authorization_code` to retrieve the tokens after a user has successfully finished the authorize step. The request body is:
|
||||
|
||||
```
|
||||
grant_type=authorization_code&
|
||||
code=12345&
|
||||
client_id=https%3A%2F%2Fhass-auth-demo.glitch.me
|
||||
```
|
||||
|
||||
The return response will be an access and refresh token:
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "ABCDEFGH",
|
||||
"expires_in": 1800,
|
||||
"refresh_token": "IJKLMNOPQRST",
|
||||
"token_type": "Bearer"
|
||||
}
|
||||
```
|
||||
|
||||
The access token is a short lived token that can be used to access the API. The refresh token can be used to fetch new access tokens. The `expires_in` value is seconds that the access token is valid.
|
||||
|
||||
An HTTP status code of 400 will be returned if an invalid request has been issued. The HTTP status code will be 403 if a token is requested for an inactive user.
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "invalid_request",
|
||||
"error_description": "Invalid client id",
|
||||
}
|
||||
```
|
||||
|
||||
### Refresh token
|
||||
|
||||
Once you have retrieved a refresh token via the grant type `authorization_code`, you can use it to fetch new access tokens. The request body is:
|
||||
|
||||
```
|
||||
grant_type=refresh_token&
|
||||
refresh_token=IJKLMNOPQRST&
|
||||
client_id=https%3A%2F%2Fhass-auth-demo.glitch.me
|
||||
```
|
||||
|
||||
The return response will be an access token:
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "ABCDEFGH",
|
||||
"expires_in": 1800,
|
||||
"token_type": "Bearer"
|
||||
}
|
||||
```
|
||||
|
||||
An HTTP status code of 400 will be returned if an invalid request has been issued.
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "invalid_request",
|
||||
"error_description": "Invalid client id",
|
||||
}
|
||||
```
|
||||
|
||||
### Revoking a refresh token
|
||||
|
||||
> client_id is not need for revoke refresh token
|
||||
|
||||
The token endpoint is also capable of revoking a refresh token. Revoking a refresh token will immediately revoke the refresh token and all access tokens that it has ever granted. To revoke a refresh token, make the following request:
|
||||
|
||||
```
|
||||
token=IJKLMNOPQRST&
|
||||
action=revoke
|
||||
```
|
||||
|
||||
The request will always respond with an empty body and HTTP status 200, regardless if the request was successful.
|
||||
|
||||
## Long-lived access token
|
||||
|
||||
A long-lived access token is usually used for 3rd party API calls and webhook-ish integrations. To generate a long-lived access token, an active websocket connection has to be established.
|
||||
|
||||
Send websocket command `auth/long_lived_access_token` will create a long-lived access token for current user. Access token will not be saved in Home Assistant. User need to record the token in secure place.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 11,
|
||||
"type": "auth/long_lived_access_token",
|
||||
"client_name": "GPS Logger",
|
||||
"client_icon": null,
|
||||
"lifespan": 365
|
||||
}
|
||||
```
|
||||
|
||||
Result will be a long-lived access token:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 11,
|
||||
"type": "result",
|
||||
"success": true,
|
||||
"result": "ABCDEFGH"
|
||||
}
|
||||
```
|
||||
|
||||
Additionally, a long-lived access token can be created using the UI tool located at the bottom of the user's Home Assistant profile page.
|
||||
|
||||
## Making authenticated requests
|
||||
|
||||
Once you have an access token, you can make authenticated requests to the Home Assistant APIs.
|
||||
|
||||
For the websocket connection, pass the access token in the [authentication message](https://developers.home-assistant.io/docs/en/external_api_websocket.html#authentication-phase).
|
||||
|
||||
For HTTP requests, pass the token type and access token as the authorization header:
|
||||
|
||||
```http
|
||||
Authorization: Bearer ABCDEFGH
|
||||
```
|
||||
|
||||
### Example: cURL
|
||||
|
||||
```shell
|
||||
curl -X GET \
|
||||
https://your.awesome.home/api/error/all \
|
||||
-H 'Authorization: Bearer ABCDEFGH'
|
||||
```
|
||||
|
||||
### Example: Python
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
url = "https://your.awesome.home/api/error/all"
|
||||
headers = {
|
||||
"Authorization": "Bearer ABCDEFGH",
|
||||
}
|
||||
response = requests.request("GET", url, headers=headers)
|
||||
|
||||
print(response.text)
|
||||
```
|
||||
|
||||
### Example: NodeJS
|
||||
```JavaScript
|
||||
fetch('https://your.awesome.home/api/error/all', {
|
||||
headers: { Authorization: 'Bearer ABCDEFGH' }
|
||||
}).then(function (response) {
|
||||
if (!response.ok) {
|
||||
return Promise.reject(response);
|
||||
}
|
||||
return response.text();
|
||||
}).then(function (body ) {
|
||||
console.log(body);
|
||||
});
|
||||
```
|
||||
|
||||
If the access token is no longer valid, you will get a response with HTTP status code 401 unauthorized. This means that you will need to refresh the token. If the refresh token doesn't work, the tokens are no longer valid and so the user is no longer logged in. You should clear the user's data and ask the user to authorize again.
|
||||
|
||||
[oauth2-spec]: https://tools.ietf.org/html/rfc6749
|
||||
[indieauth-spec]: https://indieauth.spec.indieweb.org/
|
||||
[indieauth-clients]: https://indieauth.spec.indieweb.org/#client-identifier
|
||||
|
||||
## Signed paths
|
||||
|
||||
Sometimes you want a user to make a GET request to Home Assistant to download data. In this case the normal auth system won't do, as we can't link the user to an API with the auth header attached to it. In that case, a signed path can help.
|
||||
|
||||
A signed path is a normal path on our server, like `/api/states`, but with an attached secure authentication signature. The user is able to navigate to this path and will be authorised as the access token that created the signed path. Signed paths can be created via the websocket connection and are meant to be shortlived. The default expiration is 30 seconds.
|
||||
|
||||
To get a signed path, send the following command:
|
||||
|
||||
```js
|
||||
{
|
||||
"type": "auth/sign_path",
|
||||
"path": "/api/states",
|
||||
// optional, expiration time in seconds. Defaults to 30 seconds
|
||||
"expires": 20
|
||||
}
|
||||
```
|
||||
|
||||
The response will contain the signed path:
|
||||
|
||||
```js
|
||||
{
|
||||
"path": "/api/states?authSig=ABCDEFGH"
|
||||
}
|
||||
```
|
||||
|
||||
Some things to note about a signed path:
|
||||
|
||||
- If the refresh token is deleted, the signed url is no longer valid.
|
||||
- If the user is deleted, the signed url is no longer valid (because the refresh token will be deleted).
|
||||
- If Home Assistant is restarted, the signed url is no longer valid.
|
||||
- Access is only validated when the request is received. If a response takes longer than the expiration time (ie, downloading a large file), the download will continue after the expiration date has passed.
|
48
website/versioned_docs/version-0.104.0/auth_auth_provider.md
Normal file
48
website/versioned_docs/version-0.104.0/auth_auth_provider.md
Normal file
@ -0,0 +1,48 @@
|
||||
---
|
||||
title: Authentication Providers
|
||||
id: version-0.104.0-auth_auth_provider
|
||||
original_id: auth_auth_provider
|
||||
---
|
||||
|
||||
Authentication providers confirm the identity of users. The user proofs their identity by going through the login flow for an auth provider. The auth provider defines the login flow and can ask the user all information this needs. This will commonly be username and password but could also include a 2FA token or other challenges.
|
||||
|
||||
Once an authentication provider has confirmed the identity of a user, it will pass that on to Home Assistant in the form of a Credentials object.
|
||||
|
||||
## Defining an auth provider
|
||||
|
||||
> We currently only support built-in auth providers. Support for custom auth providers might arrive in the future.
|
||||
|
||||
Auth providers are defined in `homeassistant/auth/providers/<name of provider>.py`. The auth provider module will need to provide an implementation of the `AuthProvider` class and `LoginFlow` class, it is what asks user for information and validates it base on `data_entry_flow`.
|
||||
|
||||
For an example of a fully implemented auth provider, please see [insecure_example.py](https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/auth/providers/insecure_example.py).
|
||||
|
||||
Auth providers shall extend the following methods of `AuthProvider` class.
|
||||
|
||||
| method | Required | Description
|
||||
| ------ | -------- | -----------
|
||||
| async def async_login_flow(self) | Yes | Return an instance of the login flow for a user to identify itself.
|
||||
| async def async_get_or_create_credentials(self, flow_result) | Yes | Given the result of a login flow, return a credentials object. This can either be an existing one or a new one.
|
||||
| async def async_user_meta_for_credentials(credentials) | No | Callback called Home Assistant is going to create a user from a Credentials object. Can be used to populate extra fields for the user.
|
||||
|
||||
Auth providers shall extend the following methods of `LoginFlow` class.
|
||||
|
||||
| method | Required | Description
|
||||
| ------ | -------- | -----------
|
||||
| async def async_step_init(self, user_input=None) | Yes | Handle the login form, see more detail in below.
|
||||
|
||||
## async_step_init of LoginFlow
|
||||
|
||||
> We may change this inteface in near future.
|
||||
|
||||
`LoginFlow` extends `data_entry_flow.FlowHandler`. The first step of data entry flow is hard coded as `init`, so each flow has to implement `async_step_init` method. The pattern of `async_step_init` likes following pseudo-code:
|
||||
|
||||
```python
|
||||
async def async_step_init(self, user_input=None):
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
step_id="init", data_schema="some schema to construct ui form"
|
||||
)
|
||||
if is_invalid(user_input):
|
||||
return self.async_show_form(step_id="init", errors=errors)
|
||||
return await self.async_finish(user_input)
|
||||
```
|
278
website/versioned_docs/version-0.104.0/auth_permissions.md
Normal file
278
website/versioned_docs/version-0.104.0/auth_permissions.md
Normal file
@ -0,0 +1,278 @@
|
||||
---
|
||||
title: Permissions
|
||||
id: version-0.104.0-auth_permissions
|
||||
original_id: auth_permissions
|
||||
---
|
||||
|
||||
> This is an experimental feature that is not enabled or enforced yet
|
||||
|
||||
Permissions limit the things a user has access to or can control. Permissions are attached to groups, of which a user can be a member. The combined permissions of all groups a user is a member of decides what a user can and cannot see or control.
|
||||
|
||||
Permissions do not apply to the user that is flagged as "owner". This user will always have access to everything.
|
||||
|
||||
## General permission structure
|
||||
|
||||
Policies are dictionaries that at the root level consist of different categories of permissions. In the current implementation this is limited to just entities.
|
||||
|
||||
```python
|
||||
{
|
||||
"entities": {
|
||||
# …
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Each category can further split into subcategories that describe parts of that category.
|
||||
|
||||
```python
|
||||
{
|
||||
"entities": {
|
||||
"domains": {
|
||||
# …
|
||||
},
|
||||
"entity_ids": {
|
||||
# …
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If a category is omitted, the user will not have permission to that category.
|
||||
|
||||
When defining a policy, any dictionary value at any place can be replaced with `True` or `None`. `True` means that permission is granted and `None` means use default, which is deny access.
|
||||
|
||||
## Entities
|
||||
|
||||
Entity permissions can be set on a per entity and per domain basis using the subcategories `entity_ids`, `device_ids`, `area_ids` and `domains`. You can either grant all access by setting the value to `True`, or you can specify each entity individually using the "read", "control", "edit" permissions.
|
||||
|
||||
The system will return the first matching result, based on the order: `entity_ids`, `device_ids`, `area_ids`, `domains`, `all`.
|
||||
|
||||
```json
|
||||
{
|
||||
"entities": {
|
||||
"domains": {
|
||||
"switch": true
|
||||
},
|
||||
"entity_ids": {
|
||||
"light.kitchen": {
|
||||
"read": true,
|
||||
"control": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Merging policies
|
||||
|
||||
If a user is a member of multiple groups, the groups permission policies will be combined into a single policy at runtime. When merging policies, we will look at each level of the dictionary and compare the values for each source using the following methodology:
|
||||
|
||||
1. If any of the values is `True`, the merged value becomes `True`.
|
||||
2. If any value is a dictionary, the merged value becomes a dictionary created by recursively checking each value using this methodology.
|
||||
3. If all values are `None`, the merged value becomes `None`.
|
||||
|
||||
Let's look at an example:
|
||||
|
||||
```python
|
||||
{
|
||||
"entities": {
|
||||
"entity_ids": {
|
||||
"light.kitchen": True
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
{
|
||||
"entities": {
|
||||
"entity_ids": True
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Once merged becomes
|
||||
|
||||
```python
|
||||
{
|
||||
"entities": {
|
||||
"entity_ids": True
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Checking permissions
|
||||
|
||||
We currently have two different permission checks: can the user do the read/control/edit operation on an entity, and is the user an admin and thus allowed to change this configuration setting.
|
||||
|
||||
Certain APIs will always be accessible to all users, but might offer a limited scope based on the permissions, like rendering a template.
|
||||
|
||||
### Checking permissions
|
||||
|
||||
To check a permission, you will need to have access to the user object. Once you have the user object, checking the permission is easy.
|
||||
|
||||
```python
|
||||
from homeassistant.exceptions import Unauthorized
|
||||
from homeasistant.permissions.const import POLICY_READ, POLICY_CONTROL, POLICY_EDIT
|
||||
|
||||
# Raise error if user is not an admin
|
||||
if not user.is_admin:
|
||||
raise Unauthorized()
|
||||
|
||||
|
||||
# Raise error if user does not have access to control an entity
|
||||
# Available policies: POLICY_READ, POLICY_CONTROL, POLICY_EDIT
|
||||
if not user.permissions.check_entity(entity_id, POLICY_CONTROL):
|
||||
raise Unauthorized()
|
||||
```
|
||||
|
||||
### The context object
|
||||
|
||||
All service calls, fired events and states in Home Assistant have a context object. This object allows us to attribute changes to events and services. These context objects also contain a user id, which is used for checking the permissions.
|
||||
|
||||
It's crucial for permission checking that actions taken on behalf of the user are done with a context containing the user ID. If you are in a service handler, you should re-use the incoming context `call.context`. If you are inside a WebSocket API or Rest API endpoint, you should create a context with the correct user:
|
||||
|
||||
```python
|
||||
from homeassistant.core import Context
|
||||
|
||||
await hass.services.async_call(
|
||||
"homeassistant", "stop", context=Context(user_id=user.id), blocking=True
|
||||
)
|
||||
```
|
||||
|
||||
### If a permission check fails
|
||||
|
||||
When you detect an anauthorized action, you should raise the `homeassistant.exceptions.Unauthorized` exception. This exception will cancel the current action and notifies the user that their action is unauthorized.
|
||||
|
||||
The `Unauthorized` exception has various parameters, to identify the permission check that failed. All fields are optional.
|
||||
|
||||
| # Not all actions have an ID (like adding config entry)
|
||||
| # We then use this fallback to know what category was unauth
|
||||
|
||||
|
||||
| Parameter | Description
|
||||
| --------- | -----------
|
||||
| context | The context of the current call.
|
||||
| user_id | The user ID that we tried to operate on.
|
||||
| entity_id | The entity ID that we tried to operate on.
|
||||
| config_entry_id | The config entry ID that we tried to operate on.
|
||||
| perm_category | The permission category that we tested. Only necessary if we don't have an object ID that the user tried to operate on (like when we create a config entry).
|
||||
| permission | The permission that we tested, ie `POLICY_READ`.
|
||||
|
||||
### Securing a service call handler
|
||||
|
||||
Service calls allow a user to control entities or with the integration as a whole. A service call uses the attached context to see which user invoked the command. Because context is used, it is important that you also pass the call context to all service calls.
|
||||
|
||||
All services that are registered via the entity component (`component.async_register_entity_service()`) will automatically have their permissions checked.
|
||||
|
||||
#### Checking entity permissions
|
||||
|
||||
Your service call handler will need to check the permissions for each entity that it will act on.
|
||||
|
||||
```python
|
||||
from homeassistant.exceptions import Unauthorized, UnknownUser
|
||||
from homeassistant.auth.permissions.const import POLICY_CONTROL
|
||||
|
||||
|
||||
async def handle_entity_service(call):
|
||||
"""Handle a service call."""
|
||||
entity_ids = call.data["entity_id"]
|
||||
|
||||
for entity_id in entity_ids:
|
||||
if call.context.user_id:
|
||||
user = await hass.auth.async_get_user(call.context.user_id)
|
||||
|
||||
if user is None:
|
||||
raise UnknownUser(
|
||||
context=call.context,
|
||||
entity_id=entity_id,
|
||||
permission=POLICY_CONTROL,
|
||||
)
|
||||
|
||||
if not user.permissions.check_entity(entity_id, POLICY_CONTROL):
|
||||
raise Unauthorized(
|
||||
context=call.context,
|
||||
entity_id=entity_id,
|
||||
permission=POLICY_CONTROL,
|
||||
)
|
||||
|
||||
# Do action on entity
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
hass.services.async_register(DOMAIN, "my_service", handle_entity_service)
|
||||
return True
|
||||
```
|
||||
|
||||
#### Checking admin permission
|
||||
|
||||
Starting Home Assistant 0.90, there is a special decorator to help protect
|
||||
services that require admin access.
|
||||
|
||||
```python
|
||||
# New in Home Assistant 0.90
|
||||
async def handle_admin_service(call):
|
||||
"""Handle a service call."""
|
||||
# Do admin action
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
hass.helpers.service.async_register_admin_service(
|
||||
DOMAIN, "my_service", handle_admin_service, vol.Schema({})
|
||||
)
|
||||
return True
|
||||
```
|
||||
|
||||
### Securing a REST API endpoint
|
||||
|
||||
```python
|
||||
from homeassistant.core import Context
|
||||
from homeassistant.components.http.view import HomeAssistantView
|
||||
from homeassistant.exceptions import Unauthorized
|
||||
|
||||
|
||||
class MyView(HomeAssistantView):
|
||||
"""View to handle Status requests."""
|
||||
|
||||
url = "/api/my-component/my-api"
|
||||
name = "api:my-component:my-api"
|
||||
|
||||
async def post(self, request):
|
||||
"""Notify that the API is running."""
|
||||
hass = request.app["hass"]
|
||||
user = request["hass_user"]
|
||||
|
||||
if not user.is_admin:
|
||||
raise Unauthorized()
|
||||
|
||||
hass.bus.async_fire(
|
||||
"my-component-api-running", context=Context(user_id=user.id)
|
||||
)
|
||||
|
||||
return self.json_message("Done.")
|
||||
```
|
||||
|
||||
### Securing a Websocket API endpoint
|
||||
|
||||
Verifying permissions in a Websocket API endpoint can be done by accessing the
|
||||
user via `connection.user`. If you need to check admin access, you can use the
|
||||
built-in `@require_admin` decorator.
|
||||
|
||||
```python
|
||||
from homeassistant.compnents import websocket_api
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
hass.components.websocket_api.async_register_command(websocket_create)
|
||||
return True
|
||||
|
||||
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
@websocket_api.websocket_command(
|
||||
{vol.Required("type"): "my-component/my-action",}
|
||||
)
|
||||
async def websocket_create(hass, connection, msg):
|
||||
"""Create a user."""
|
||||
# Do action
|
||||
```
|
@ -0,0 +1,147 @@
|
||||
---
|
||||
title: Integration Configuration
|
||||
sidebar_label: Configuration
|
||||
id: version-0.104.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 flow 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):
|
||||
"""Example config flow."""
|
||||
```
|
||||
|
||||
Once you have updated your manifest and created the `config_flow.py`, you will need to run `python3 -m script.hassfest` (one time only) for Home Assistant to activate the config entry for your integration.
|
||||
|
||||
## 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:
|
||||
pass # TODO: 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. |
|
||||
|
||||
## Unique IDs
|
||||
|
||||
A config flow can attach a unique ID to a config flow to avoid the same device being set up twice. When a unique ID is set, it will immediately abort if another flow is in progress for this unique ID. You can also quickly abort if there is already an existing config entry for this ID. Config entries will get the unique ID of the flow that creates them.
|
||||
|
||||
Call inside a config flow step:
|
||||
|
||||
```python
|
||||
await self.async_set_unique_id(device_unique_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
```
|
||||
|
||||
By setting a unique ID, users will have the option to ignore the discovery of your config entry. That way they won't be bothered about it anymore.
|
||||
|
||||
### Unignoring
|
||||
|
||||
Your configuration flow can add support to re-discovered the previously ignored entry by implementing the unignore step in the config flow.
|
||||
|
||||
```python
|
||||
async def async_step_unignore(self, user_input):
|
||||
unique_id = user_input["unique_id"]
|
||||
await self.async_set_unique_id(unique_id)
|
||||
|
||||
# TODO: Discover devices and find the one that matches the unique ID.
|
||||
|
||||
return self.async_show_form(…)
|
||||
```
|
||||
|
||||
## 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. While developing locally, you will need to run `script/translations_develop` to see changes made to `strings.json` [More info on translating Home Assistant.](internationalization_translation.md)
|
140
website/versioned_docs/version-0.104.0/config_entries_index.md
Normal file
140
website/versioned_docs/version-0.104.0/config_entries_index.md
Normal file
@ -0,0 +1,140 @@
|
||||
---
|
||||
title: Config Entries
|
||||
sidebar_label: Introduction
|
||||
id: version-0.104.0-config_entries_index
|
||||
original_id: config_entries_index
|
||||
---
|
||||
|
||||
Config Entries are configuration data that are persistently stored by Home Assistant. A config entry is created by a user via the UI. The UI flow is powered by a [config flow handler](config_entries_config_flow_handler.md) as defined by the component. Config entries can also have an extra [options flow handler](config_entries_options_flow_handler.md), also defined by the component.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
| State | Description |
|
||||
| ----- | ----------- |
|
||||
| not loaded | The config entry has not been loaded. This is the initial state when a config entry is created or when Home Assistant is restarted. |
|
||||
| loaded | The config entry has been loaded. |
|
||||
| setup error | An error occurred when trying to set up the config entry. |
|
||||
| setup retry | A dependency of the config entry was not ready yet. Home Assistant will automatically retry loading this config entry in the future. Time between attempts will automatically increase.
|
||||
| migration error | The config entry had to be migrated to a newer version, but the migration failed.
|
||||
| failed unload | The config entry was attempted to be unloaded, but this was either not supported or it raised an exception.
|
||||
|
||||
<svg width="508pt" height="188pt" viewBox="0.00 0.00 508.00 188.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph1" class="graph" transform="scale(1 1) rotate(0) translate(4 184)">
|
||||
<title>G</title>
|
||||
<polygon fill="white" stroke="white" points="-4,5 -4,-184 505,-184 505,5 -4,5"></polygon>
|
||||
<g id="node1" class="node"><title>not loaded</title>
|
||||
<ellipse fill="none" stroke="black" cx="168" cy="-162" rx="51.3007" ry="18"></ellipse>
|
||||
<text text-anchor="middle" x="168" y="-157.8" font-family="Times,serif" font-size="14.00">not loaded</text>
|
||||
</g>
|
||||
<g id="node3" class="node"><title>loaded</title>
|
||||
<ellipse fill="none" stroke="black" cx="61" cy="-90" rx="36.1722" ry="18"></ellipse>
|
||||
<text text-anchor="middle" x="61" y="-85.8" font-family="Times,serif" font-size="14.00">loaded</text>
|
||||
</g>
|
||||
<g id="edge2" class="edge"><title>not loaded->loaded</title>
|
||||
<path fill="none" stroke="black" d="M140.518,-146.666C123.947,-136.676 103.104,-123.187 86.8392,-111.989"></path>
|
||||
<polygon fill="black" stroke="black" points="88.532,-108.902 78.3309,-106.041 84.5212,-114.639 88.532,-108.902"></polygon>
|
||||
</g>
|
||||
<g id="node5" class="node"><title>setup error</title>
|
||||
<ellipse fill="none" stroke="black" cx="168" cy="-90" rx="52.3895" ry="18"></ellipse>
|
||||
<text text-anchor="middle" x="168" y="-85.8" font-family="Times,serif" font-size="14.00">setup error</text>
|
||||
</g>
|
||||
<g id="edge4" class="edge"><title>not loaded->setup error</title>
|
||||
<path fill="none" stroke="black" d="M162.122,-144.055C161.304,-136.346 161.061,-127.027 161.395,-118.364"></path>
|
||||
<polygon fill="black" stroke="black" points="164.894,-118.491 162.087,-108.275 157.911,-118.012 164.894,-118.491"></polygon>
|
||||
</g>
|
||||
<g id="node7" class="node"><title>setup retry</title>
|
||||
<ellipse fill="none" stroke="black" cx="291" cy="-90" rx="52.0932" ry="18"></ellipse>
|
||||
<text text-anchor="middle" x="291" y="-85.8" font-family="Times,serif" font-size="14.00">setup retry</text>
|
||||
</g>
|
||||
<g id="edge6" class="edge"><title>not loaded->setup retry</title>
|
||||
<path fill="none" stroke="black" d="M189.578,-145.465C206.94,-134.869 231.584,-120.783 252.292,-109.59"></path>
|
||||
<polygon fill="black" stroke="black" points="254.022,-112.634 261.19,-104.832 250.722,-106.461 254.022,-112.634"></polygon>
|
||||
</g>
|
||||
<g id="node9" class="node"><title>migration error</title>
|
||||
<ellipse fill="none" stroke="black" cx="431" cy="-90" rx="69.1427" ry="18"></ellipse>
|
||||
<text text-anchor="middle" x="431" y="-85.8" font-family="Times,serif" font-size="14.00">migration error</text>
|
||||
</g>
|
||||
<g id="edge8" class="edge"><title>not loaded->migration error</title>
|
||||
<path fill="none" stroke="black" d="M207.659,-150.445C252.053,-138.628 324.343,-119.388 374.607,-106.01"></path>
|
||||
<polygon fill="black" stroke="black" points="375.588,-109.37 384.351,-103.416 373.787,-102.606 375.588,-109.37"></polygon>
|
||||
</g>
|
||||
<g id="edge10" class="edge"><title>loaded->not loaded</title>
|
||||
<path fill="none" stroke="black" d="M85.5216,-103.56C102.143,-113.462 123.939,-127.508 141.027,-139.231"></path>
|
||||
<polygon fill="black" stroke="black" points="139.274,-142.276 149.481,-145.116 143.273,-136.53 139.274,-142.276"></polygon>
|
||||
</g>
|
||||
<g id="node12" class="node"><title>failed unload</title>
|
||||
<ellipse fill="none" stroke="black" cx="61" cy="-18" rx="61.5781" ry="18"></ellipse>
|
||||
<text text-anchor="middle" x="61" y="-13.8" font-family="Times,serif" font-size="14.00">failed unload</text>
|
||||
</g>
|
||||
<g id="edge12" class="edge"><title>loaded->failed unload</title>
|
||||
<path fill="none" stroke="black" d="M61,-71.6966C61,-63.9827 61,-54.7125 61,-46.1124"></path>
|
||||
<polygon fill="black" stroke="black" points="64.5001,-46.1043 61,-36.1043 57.5001,-46.1044 64.5001,-46.1043"></polygon>
|
||||
</g>
|
||||
<g id="edge16" class="edge"><title>setup error->not loaded</title>
|
||||
<path fill="none" stroke="black" d="M173.913,-108.275C174.715,-116.03 174.94,-125.362 174.591,-134.005"></path>
|
||||
<polygon fill="black" stroke="black" points="171.094,-133.832 173.878,-144.055 178.077,-134.327 171.094,-133.832"></polygon>
|
||||
</g>
|
||||
<g id="edge14" class="edge"><title>setup retry->not loaded</title>
|
||||
<path fill="none" stroke="black" d="M269.469,-106.507C252.104,-117.106 227.436,-131.206 206.71,-142.408"></path>
|
||||
<polygon fill="black" stroke="black" points="204.973,-139.368 197.805,-147.17 208.273,-145.541 204.973,-139.368"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<!--
|
||||
Graphviz:
|
||||
digraph G {
|
||||
"not loaded" -> "loaded"
|
||||
"not loaded" -> "setup error"
|
||||
"not loaded" -> "setup retry"
|
||||
"not loaded" -> "migration error"
|
||||
"loaded" -> "not loaded"
|
||||
"loaded" -> "failed unload"
|
||||
"setup retry" -> "not loaded"
|
||||
"setup error" -> "not loaded"
|
||||
}
|
||||
-->
|
||||
|
||||
## Setting up an entry
|
||||
|
||||
During startup, Home Assistant first calls the [normal component setup](https://developers.home-assistant.io/docs/en/creating_component_index.html),
|
||||
and then call the method `async_setup_entry(hass, entry)` for each entry. If a new Config Entry is
|
||||
created at runtime, Home Assistant will also call `async_setup_entry(hass, entry)` ([example](https://github.com/home-assistant/home-assistant/blob/0.68.0/homeassistant/components/hue/__init__.py#L119)).
|
||||
|
||||
#### For platforms
|
||||
|
||||
If a component includes platforms, it will need to forward the Config Entry to the platform. This can
|
||||
be done by calling the forward function on the config entry manager ([example](https://github.com/home-assistant/home-assistant/blob/0.68.0/homeassistant/components/hue/bridge.py#L81)):
|
||||
|
||||
```python
|
||||
# Use `hass.async_add_job` to avoid a circular dependency between the platform and the component
|
||||
hass.async_add_job(hass.config_entries.async_forward_entry_setup(config_entry, "light"))
|
||||
```
|
||||
|
||||
For a platform to support config entries, it will need to add a setup entry method ([example](https://github.com/home-assistant/home-assistant/blob/0.68.0/homeassistant/components/light/hue.py#L60)):
|
||||
|
||||
```python
|
||||
async def async_setup_entry(hass, config_entry, async_add_devices):
|
||||
"""Set up entry."""
|
||||
```
|
||||
|
||||
## Unloading entries
|
||||
|
||||
Components can optionally support unloading a config entry. When unloading an entry, the component needs to clean up all entities, unsubscribe any event listener and close all connections. To implement this, add `async_unload_entry(hass, entry)` to your component ([example](https://github.com/home-assistant/home-assistant/blob/0.68.0/homeassistant/components/hue/__init__.py#L136)).
|
||||
|
||||
For each platform that you forwarded the config entry to, you will need to forward the unloading too.
|
||||
|
||||
```python
|
||||
await self.hass.config_entries.async_forward_entry_unload(self.config_entry, "light")
|
||||
```
|
||||
|
||||
If you need to clean up resources used by an entity in a platform, have the entity implement the [`async_will_remove_from_hass`](entity_index.md#async_will_remove_from_hass) method.
|
||||
|
||||
## Removal of entries
|
||||
|
||||
If a component needs to clean up code when an entry is removed, it can define a removal method:
|
||||
|
||||
```python
|
||||
async def async_remove_entry(hass, entry) -> None:
|
||||
"""Handle removal of an entry."""
|
||||
```
|
@ -0,0 +1,60 @@
|
||||
---
|
||||
title: Integration Configuration Options
|
||||
sidebar_label: Configuration Options
|
||||
id: version-0.104.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):
|
||||
"""Handle options update."""
|
||||
```
|
@ -0,0 +1,57 @@
|
||||
---
|
||||
title: Integration Configuration via YAML
|
||||
id: version-0.104.0-configuration_yaml_index
|
||||
original_id: configuration_yaml_index
|
||||
---
|
||||
|
||||
`configuration.yaml` is a configuration file defined by the user. It is automatically created by Home Assistant on first launch. It defines which components to load.
|
||||
|
||||
## Pre-processing
|
||||
|
||||
Home Assistant will do some pre-processing on the config based on the components that are specified to load.
|
||||
|
||||
### CONFIG_SCHEMA
|
||||
|
||||
If a component defines a variable `CONFIG_SCHEMA`, the config object that is passed in will be the result of running the config through `CONFIG_SCHEMA`. `CONFIG_SCHEMA` should be a voluptuous schema.
|
||||
|
||||
### PLATFORM_SCHEMA
|
||||
|
||||
If a component defines a variable `PLATFORM_SCHEMA`, the component will be treated as an entity component. The configuration of entity components is a list of platform configurations.
|
||||
|
||||
Home Assistant will gather all platform configurations for this component. It will do so by looking for configuration entries under the domain of the component (ie `light`) but also under any entry of domain + extra text.
|
||||
|
||||
While gathering the platform configs, Home Assistant will validate them. It will see if the platform exists and if the platform defines a PLATFORM_SCHEMA, validate against that schema. If not defined, it will validate the config against the PLATFORM_SCHEMA defined in the component. Any configuration that references non existing platforms or contains invalid config will be removed.
|
||||
|
||||
The following `configuration.yaml`:
|
||||
|
||||
```yaml
|
||||
unrelated_component:
|
||||
some_key: some_value
|
||||
|
||||
switch:
|
||||
platform: example1
|
||||
|
||||
switch living room:
|
||||
- platform: example2
|
||||
some_config: true
|
||||
- platform: invalid_platform
|
||||
```
|
||||
|
||||
will be passed to the component as
|
||||
|
||||
```python
|
||||
{
|
||||
"unrelated_component": {
|
||||
"some_key": "some_value"
|
||||
},
|
||||
"switch": [
|
||||
{
|
||||
"platform": "example1"
|
||||
},
|
||||
{
|
||||
"platform": "example2",
|
||||
"some_config": True
|
||||
}
|
||||
],
|
||||
}
|
||||
```
|
@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Checklist for creating a component
|
||||
sidebar_label: Component Checklist
|
||||
id: version-0.104.0-creating_component_code_review
|
||||
original_id: creating_component_code_review
|
||||
---
|
||||
|
||||
A checklist of things to do when you're adding a new component.
|
||||
|
||||
> Not all existing code follow the requirements in this checklist. This cannot be used as a reason to not follow them!
|
||||
|
||||
### 0. Common
|
||||
|
||||
1. Follow our [Style guidelines](development_guidelines.md)
|
||||
2. Use existing constants from [`const.py`](https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/const.py)
|
||||
* Only add new constants to `const.py` if they are widely used. Otherwise keep them on components level
|
||||
|
||||
### 1. Requirements
|
||||
|
||||
1. Requirements have been added to [`manifest.json`](creating_integration_manifest.md). The `REQUIREMENTS` constant is deprecated.
|
||||
2. Requirement version should be pinned: `"requirements": ['phue==0.8.1']`
|
||||
3. We no longer want requirements hosted on GitHub. Please upload to PyPi.
|
||||
|
||||
### 2. Configuration
|
||||
|
||||
1. Voluptuous schema present for [configuration validation](development_validation.md)
|
||||
2. Default parameters specified in voluptuous schema, not in `setup(…)`
|
||||
3. Schema using as many generic config keys as possible from `homeassistant.const`
|
||||
4. If your component has platforms, define a `PLATFORM_SCHEMA` instead of a `CONFIG_SCHEMA`.
|
||||
5. If using a `PLATFORM_SCHEMA` to be used with `EntityComponent`, import base from `homeassistant.helpers.config_validation`
|
||||
6. Never depend on users adding things to `customize` to configure behavior inside your component.
|
||||
|
||||
### 3. Component/platform communication
|
||||
|
||||
1. You can share data with your platforms by leveraging `hass.data[DOMAIN]`.
|
||||
2. If the component fetches data that causes its related platform entities to update, you can notify them using the dispatcher code in `homeassistant.helpers.dispatcher`.
|
||||
|
||||
### 4. Communication with devices/services
|
||||
|
||||
1. All API specific code has to be part of a third party library hosted on PyPi. Home Assistant should only interact with objects and not make direct calls to the API.
|
||||
|
||||
```python
|
||||
# bad
|
||||
status = requests.get(url("/status"))
|
||||
# good
|
||||
from phue import Bridge
|
||||
|
||||
bridge = Bridge(...)
|
||||
status = bridge.status()
|
||||
```
|
||||
|
||||
[Tutorial on publishing your own PyPI package](https://jeffknupp.com/blog/2013/08/16/open-sourcing-a-python-project-the-right-way/)
|
||||
|
||||
### 5. Make your pull request as small as possible
|
||||
|
||||
Keep a new integration to the minimum functionality needed for someone to get value out of the integration. This allows reviewers to sign off on smaller chunks of code one at a time, and lets us get your new integration/features in sooner. **Pull requests containing large code dumps will not be a priority for review and may be closed.**
|
||||
|
||||
- Limit to a single platform
|
||||
- Do not add features not needed to directly support the single platform (such as custom services)
|
||||
- Do not mix clean-ups and new features in a single pull request.
|
||||
- Do not solve several issues in a single pull request.
|
||||
- Do not submit pull requests that depend on other work which is still unmerged.
|
||||
|
||||
### 6. Event names
|
||||
Prefix component event names with the domain name. For example, use `netatmo_person` instead of `person` for the `netatmo` component.
|
||||
|
||||
### 7. Tests
|
||||
|
||||
Strongly consider adding tests for your component to minimize future regressions.
|
@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Creating your first integration
|
||||
id: version-0.104.0-creating_component_index
|
||||
original_id: creating_component_index
|
||||
---
|
||||
|
||||
Alright, you learned about the [manifest](creating_integration_manifest.md), so it's time to write your first code for your integration. AWESOME. Don't worry, we've tried hard to keep it as easy as possible. From a Home Assistant development environment, type the following and follow the instructions:
|
||||
|
||||
```shell
|
||||
python3 -m script.scaffold integration
|
||||
```
|
||||
|
||||
This will set you up with everything that you need to build an integration that is able to be set up via the user interface. More extensive examples of integrations are available from [our example repository](https://github.com/home-assistant/example-custom-config/tree/master/custom_components/).
|
||||
|
||||
## The minimum
|
||||
|
||||
The scaffold integration contains a bit more than just the bare minimum. The minimum is that you define a `DOMAIN` constant that contains the domain of the integration. The second part is that it needs to define a setup method that returns a boolean if the set up was successful.
|
||||
|
||||
```python
|
||||
DOMAIN = "hello_state"
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
hass.states.set("hello_state.world", "Paulus")
|
||||
|
||||
# Return boolean to indicate that initialization was successful.
|
||||
return True
|
||||
```
|
||||
|
||||
And if you prefer an async component:
|
||||
|
||||
```python
|
||||
DOMAIN = "hello_state"
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
hass.states.async_set("hello_state.world", "Paulus")
|
||||
|
||||
# Return boolean to indicate that initialization was successful.
|
||||
return True
|
||||
```
|
||||
|
||||
To load this, add `hello_state:` to your `configuration.yaml` file and create a file `<config_dir>/custom_components/hello_state/__init__.py` with one of the two codeblocks above to test it locally.
|
||||
|
||||
## What the scaffold offers
|
||||
|
||||
When using the scaffold script, it will go past the bare minimum of an integration. It will include a config flow, tests for the config flow and basic translation infrastructure to provide internationalization for your config flow.
|
@ -0,0 +1,150 @@
|
||||
---
|
||||
title: Integration Manifest
|
||||
sidebar_label: Manifest
|
||||
id: version-0.104.0-creating_integration_manifest
|
||||
original_id: creating_integration_manifest
|
||||
---
|
||||
|
||||
Since 0.92.0, every integration has a manifest file to specify basic information about an integration. This file is stored as `manifest.json` in your integration directory. It is required to add such a file, except for custom components.
|
||||
|
||||
```json
|
||||
{
|
||||
"domain": "hue",
|
||||
"name": "Philips Hue",
|
||||
"documentation": "https://www.home-assistant.io/components/hue",
|
||||
"dependencies": ["mqtt"],
|
||||
"codeowners": ["@balloob"],
|
||||
"requirements": ["aiohue==1.9.1"],
|
||||
"quality_scale": "platinum"
|
||||
}
|
||||
```
|
||||
|
||||
Or a minimal example that you can copy into your project:
|
||||
|
||||
```json
|
||||
{
|
||||
"domain": "your_domain_name",
|
||||
"name": "Your Integration",
|
||||
"documentation": "https://www.example.com",
|
||||
"dependencies": [],
|
||||
"codeowners": [],
|
||||
"requirements": []
|
||||
}
|
||||
```
|
||||
|
||||
## Domain
|
||||
|
||||
The domain is a short name consisting of characters and underscores. This domain has to be unique and cannot be changed. Example of the domain for the mobile app integration: `mobile_app`.
|
||||
|
||||
## Name
|
||||
|
||||
The name of the integration.
|
||||
|
||||
## Documentation
|
||||
|
||||
The website containing documentation on how to use your integration. If this integration is being submitted for inclusion in Home Assistant, it should be `https://www.home-assistant.io/components/<domain>`
|
||||
|
||||
## Dependencies
|
||||
|
||||
Dependencies are other Home Assistant integrations that you want Home Assistant to set up successfully prior to the integration being loaded. This can be necessary in case you want to offer functionality from that other integration, like using webhooks or an MQTT connection.
|
||||
|
||||
## Code Owners
|
||||
|
||||
GitHub usernames or team names of people that are responsible for this integration. You should add at least your GitHub username here, as well as anyone who helped you to write code that is being included.
|
||||
|
||||
## Config Flow
|
||||
|
||||
Specify the `config_flow` key if your integration has a config flow to create a config entry. When specified, the file `config_flow.py` needs to exist in your integration.
|
||||
|
||||
```json5
|
||||
{
|
||||
"config_flow": true
|
||||
}
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
Requirements are Python libraries or modules that you would normally install using `pip` for your component. Home Assistant will try to install the requirements into the `deps` subdirectory of the Home Assistant [configuration directory](https://www.home-assistant.io/docs/configuration/) if you are not using a `venv` or in something like `path/to/venv/lib/python3.6/site-packages` if you are running in a virtual environment. This will make sure that all requirements are present at startup. If steps fail, like missing packages for the compilation of a module or other install errors, the component will fail to load.
|
||||
|
||||
Requirements is an array of strings. Each entry is a `pip` compatible string. For example, the media player Cast platform depends on the Python package PyChromecast v3.2.0: `["pychromecast==3.2.0"]`.
|
||||
|
||||
### Custom requirements during development & testing
|
||||
|
||||
During the development of a component, it can be useful to test against different versions of a requirement. This can be done in two steps, using `pychromecast` as an example:
|
||||
|
||||
```shell
|
||||
pip install pychromecast==3.2.0 --target ~/.homeassistant/deps
|
||||
hass --skip-pip
|
||||
```
|
||||
|
||||
This will use the specified version, and prevent Home Assistant from trying to override it with what is specified in `requirements`.
|
||||
|
||||
If you need to make changes to a requirement to support your component, it's also possible to install a development version of the requirement using `pip install -e`:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/balloob/pychromecast.git
|
||||
pip install -e ./pychromecast
|
||||
hass --skip-pip
|
||||
```
|
||||
|
||||
## Zeroconf
|
||||
|
||||
If your integration supports discovery via [Zeroconf](https://en.wikipedia.org/wiki/Zero-configuration_networking), you can add the type to your manifest. If the user has the `zeroconf` integration loaded, it will load the `zeroconf` step of your integration's config flow when it is discovered.
|
||||
|
||||
```json5
|
||||
{
|
||||
"zeroconf": ["_googlecast._tcp.local."]
|
||||
}
|
||||
```
|
||||
|
||||
## SSDP
|
||||
|
||||
If your integration supports discovery via [SSDP](https://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol), you can add the type to your manifest. If the user has the `ssdp` integration loaded, it will load the `ssdp` step of your integration's config flow when it is discovered. We support SSDP discovery by ST, and all data in UPnP device description. The manifest value is a list of matcher dictionaries, your integration is discovered if all items of any of the specified matchers are found in the SSDP/UPnP data. It's up to your config flow to filter out duplicates.
|
||||
|
||||
The following example has one matcher consisting of three items, all of which must match for discovery to happen by this config.
|
||||
|
||||
```json5
|
||||
{
|
||||
"ssdp": [
|
||||
{
|
||||
"st": "roku:ecp",
|
||||
"manufacturer": "Roku",
|
||||
"deviceType": "urn:roku-com:device:player:1-0"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## HomeKit
|
||||
|
||||
If your integration supports discovery via HomeKit, you can add the supported model names to your manifest. If the user has the `zeroconf` integration loaded, it will load the `homekit` step of your integration's config flow when it is discovered.
|
||||
|
||||
HomeKit discovery works by testing if the discovered modelname starts with any of the model names specified in the manifest.json.
|
||||
|
||||
```json5
|
||||
{
|
||||
"homekit": {
|
||||
"models": [
|
||||
"LIFX"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Discovery via HomeKit does not mean that you have to talk the HomeKit protocol to communicate with your device. You can communicate with the device however you see fit.
|
||||
|
||||
When a discovery info is routed to your integration because of this entry in your manifest, the discovery info is no longer routed to integrations that listen to the HomeKit zeroconf type.
|
||||
|
||||
## Integration Quality Scale
|
||||
|
||||
The [Integration Quality Scale](https://www.home-assistant.io/docs/quality_scale/) scores an integration 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.
|
||||
|
||||
When your integration has no score, then don't add it to the manifest of your integration. However, be sure to look at the [Integration Quality Scale](https://www.home-assistant.io/docs/quality_scale/) list of requirements. It helps to improve the code and user experience tremendously.
|
||||
|
||||
We highly recommend getting your integration scored.
|
||||
|
||||
```json5
|
||||
{
|
||||
"quality_scale": "silver"
|
||||
}
|
||||
```
|
@ -0,0 +1,90 @@
|
||||
---
|
||||
title: Checklist for creating a platform
|
||||
sidebar_label: Platform Checklist
|
||||
id: version-0.104.0-creating_platform_code_review
|
||||
original_id: creating_platform_code_review
|
||||
---
|
||||
|
||||
A checklist of things to do when you're adding a new platform.
|
||||
|
||||
> Not all existing platforms follow the requirements in this checklist. This cannot be used as a reason to not follow them!
|
||||
|
||||
### 0. Common
|
||||
|
||||
1. Follow our [Style guidelines](development_guidelines.md)
|
||||
2. Use existing constants from [`const.py`](https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/const.py)
|
||||
* Only add new constants to `const.py` if they are widely used. Otherwise keep them on platform level
|
||||
* Use `CONF_MONITORED_CONDITIONS` instead of `CONF_MONITORED_VARIABLES`
|
||||
|
||||
### 1. Requirements
|
||||
|
||||
1. Requirements have been added to [`manifest.json`](creating_integration_manifest.md). The `REQUIREMENTS` constant is deprecated.
|
||||
2. Requirement version should be pinned: `"requirements": ['phue==0.8.1']`
|
||||
3. We no longer want requirements hosted on GitHub. Please upload to PyPi.
|
||||
|
||||
### 2. Configuration
|
||||
|
||||
1. If the platform can be set up directly, add a voluptuous schema for [configuration validation](development_validation.md)
|
||||
2. Voluptuous schema extends schema from component<br>(e.g., `hue.light.PLATFORM_SCHEMA` extends `light.PLATFORM_SCHEMA`)
|
||||
3. Default parameters specified in voluptuous schema, not in `setup_platform(...)`
|
||||
4. Your `PLATFORM_SCHEMA` should use as many generic config keys as possible from `homeassistant.const`
|
||||
5. Never depend on users adding things to `customize` to configure behavior inside your platform.
|
||||
|
||||
```python
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_FILENAME, CONF_HOST
|
||||
from homeassistant.components.light import PLATFORM_SCHEMA
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
CONF_ALLOW_UNREACHABLE = "allow_unreachable"
|
||||
DEFAULT_UNREACHABLE = False
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_ALLOW_UNREACHABLE, default=DEFAULT_UNREACHABLE): cv.boolean,
|
||||
vol.Optional(CONF_FILENAME): cv.string,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### 3. Setup Platform
|
||||
|
||||
1. Verify that the passed in configuration (user/pass/host etc.) works.
|
||||
2. Group your calls to `add_devices` if possible.
|
||||
3. If the platform adds extra services, the format should be `<domain of your integration>.<service name>`. So if your integration's domain is "awesome_sauce" and you are making a light platform, you would register services under the `awesome_sauce` domain. Make sure that your services [verify permissions](auth_permissions.md#checking-permissions).
|
||||
|
||||
### 4. Entity
|
||||
|
||||
1. Extend the entity from the integration you're building a platform for.
|
||||
|
||||
```python
|
||||
from homeassistant.components.light import Light
|
||||
|
||||
|
||||
class HueLight(Light):
|
||||
"""Hue light component."""
|
||||
```
|
||||
|
||||
2. Avoid passing in `hass` as a parameter to the entity. When the entity has been added to Home Assistant, `hass` will be set on the entity when the entity is added to Home Assistant. This means you can access `hass` as `self.hass` inside the entity.
|
||||
3. Do not call `update()` in constructor, use `add_entities(devices, True)` instead.
|
||||
4. Do not do any I/O inside properties. Cache values inside `update()` instead.
|
||||
5. When dealing with time, state and/or attributes should not contain relative time since something happened. Instead, it should store UTC timestamps.
|
||||
6. Leverage the [entity lifecycle callbacks](entity_index.md#lifecycle-hooks) to attach event listeners or clean up connections.
|
||||
|
||||
### 5. Communication with devices/services
|
||||
|
||||
1. All API specific code has to be part of a third party library hosted on PyPi. Home Assistant should only interact with objects and not make direct calls to the API.
|
||||
|
||||
```python
|
||||
# bad
|
||||
status = requests.get(url("/status"))
|
||||
# good
|
||||
from phue import Bridge
|
||||
|
||||
bridge = Bridge(...)
|
||||
status = bridge.status()
|
||||
```
|
||||
|
||||
[Tutorial on publishing your own PyPI package](https://jeffknupp.com/blog/2013/08/16/open-sourcing-a-python-project-the-right-way/)
|
279
website/versioned_docs/version-0.104.0/data_entry_flow_index.md
Normal file
279
website/versioned_docs/version-0.104.0/data_entry_flow_index.md
Normal file
@ -0,0 +1,279 @@
|
||||
---
|
||||
title: Data Entry Flow
|
||||
sidebar_label: Introduction
|
||||
id: version-0.104.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 manages all flows that are in progress and handles creation of new flows.
|
||||
|
||||
Data Entry Flow is 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):
|
||||
"""Create flow."""
|
||||
```
|
||||
|
||||
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):
|
||||
"""Finish flow."""
|
||||
```
|
||||
|
||||
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 look 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 look 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 look 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 doesn't have to be `init`, as `async_create_flow` can assign `init_step` dependent on the current workflow, for example in configuration, `context.source` will be used 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):
|
||||
"""Handle user step."""
|
||||
```
|
||||
|
||||
### 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):
|
||||
# Specify items in the order they are to be displayed in the UI
|
||||
data_schema = {
|
||||
vol.Required("username"): str,
|
||||
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"
|
||||
|
||||
# Specify items in the order they are to be displayed in the UI
|
||||
data_schema = {
|
||||
vol.Required("username"): str,
|
||||
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=f"https://example.com/?config_flow_id={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."""
|
||||
```
|
30
website/versioned_docs/version-0.104.0/dev_101_config.md
Normal file
30
website/versioned_docs/version-0.104.0/dev_101_config.md
Normal file
@ -0,0 +1,30 @@
|
||||
---
|
||||
title: Using Config
|
||||
id: version-0.104.0-dev_101_config
|
||||
original_id: dev_101_config
|
||||
---
|
||||
|
||||
Based on where you are in the code, `config` can mean various things.
|
||||
|
||||
### On the hass object
|
||||
|
||||
On the hass object it is an instance of the Config class. The Config class contains the users preferred units, the path to the config directory and which components are loaded. [See available methods.](https://dev-docs.home-assistant.io/en/master/api/core.html#homeassistant.core.Config)
|
||||
|
||||
### Config passed into component setup
|
||||
|
||||
The `config` parameter passed to a component setup is a dictionary containing all of the user supplied configuration. The keys of the dictionary are the component names and the value is another dictionary with the component configuration.
|
||||
|
||||
The object will have already been validated using your `CONFIG_SCHEMA` or `PLATFORM_SCHEMA` if available. If you have defined a `PLATFORM_SCHEMA`, all references to your component (ie `light 2:` etc) will have been changed to be accessible as a list under `config[DOMAIN]`.
|
||||
|
||||
If your configuration file contains the following lines:
|
||||
|
||||
```yaml
|
||||
example:
|
||||
host: paulusschoutsen.nl
|
||||
```
|
||||
|
||||
Then in the setup method of your component you will be able to refer to `config['example']['host']` to get the value `paulusschoutsen.nl`.
|
||||
|
||||
### Passed into platform setup
|
||||
|
||||
The `config` parameter passed to a platform setup function is only the config for that specific platform.
|
63
website/versioned_docs/version-0.104.0/dev_101_events.md
Normal file
63
website/versioned_docs/version-0.104.0/dev_101_events.md
Normal file
@ -0,0 +1,63 @@
|
||||
---
|
||||
title: Using Events
|
||||
id: version-0.104.0-dev_101_events
|
||||
original_id: dev_101_events
|
||||
---
|
||||
|
||||
The core of Home Assistant is driven by events. That means that if you want to respond to something happening, you'll have to respond to events. Most of the times you won't interact directly with the event system but use one of the [event listener helpers][helpers].
|
||||
|
||||
The event system is very flexible. There are no limitations on the event type, as long as it's a string. Each event can contain data. The data is a dictionary that can contain any data as long as it's JSON serializable. This means that you can use number, string, dictionary and list.
|
||||
|
||||
[List of events that Home Assistant fires.][object]
|
||||
|
||||
## Firing events
|
||||
|
||||
To fire an event, you have to interact with the event bus. The event bus is available on the Home Assistant instance as `hass.bus`.
|
||||
|
||||
Example component that will fire an event when loaded. Note that custom event names are prefixed with the component name.
|
||||
|
||||
```python
|
||||
DOMAIN = "example_component"
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up is called when Home Assistant is loading our component."""
|
||||
|
||||
# Fire event example_component_my_cool_event with event data answer=42
|
||||
hass.bus.fire("example_component_my_cool_event", {"answer": 42})
|
||||
|
||||
# Return successful setup
|
||||
return True
|
||||
```
|
||||
|
||||
## Listening to events
|
||||
|
||||
Most of the times you'll not be firing events but instead listen to events. For example, the state change of an entity is broadcasted as an event.
|
||||
|
||||
```python
|
||||
DOMAIN = "example_component"
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up is called when Home Assistant is loading our component."""
|
||||
count = 0
|
||||
|
||||
# Listener to handle fired events
|
||||
def handle_event(event):
|
||||
nonlocal count
|
||||
count += 1
|
||||
print(f"Answer {count} is: {event.data.get('answer')}")
|
||||
|
||||
# Listen for when example_component_my_cool_event is fired
|
||||
hass.bus.listen("example_component_my_cool_event", handle_event)
|
||||
|
||||
# Return successful setup
|
||||
return True
|
||||
```
|
||||
|
||||
### Helpers
|
||||
|
||||
Home Assistant comes with a lot of bundled helpers to listen to specific types of event. There are helpers to track a point in time, to track a time interval, a state change or the sun set. [See available methods.][helpers]
|
||||
|
||||
[helpers]: https://dev-docs.home-assistant.io/en/master/api/helpers.html#module-homeassistant.helpers.event
|
||||
[object]: https://www.home-assistant.io/docs/configuration/events/
|
49
website/versioned_docs/version-0.104.0/dev_101_index.md
Normal file
49
website/versioned_docs/version-0.104.0/dev_101_index.md
Normal file
@ -0,0 +1,49 @@
|
||||
---
|
||||
title: Development 101
|
||||
sidebar_label: Introduction
|
||||
id: version-0.104.0-dev_101_index
|
||||
original_id: dev_101_index
|
||||
---
|
||||
|
||||
The goal of development 101 is to get you familiar with the basics of developing for Home Assistant. Before we start, please make sure you familiarize yourself with the [architecture](architecture_index.md).
|
||||
|
||||
To get our code running inside Home Assistant we're going to create a custom component. The first step is to locate your config folder. You can find the path to your config folder by opening the Home Assistant frontend, click on the <img src='/img/dev-tools/about-icon.png' alt='service developer tool icon' class="inline" width="38" />. It's the path after the text "Path to configuration.yaml".
|
||||
|
||||
Inside your configuration directory create a new folder called `custom_components`. It might be that one already exists, that's fine too. This is the folder that Home Assistant will look at when looking for custom code.
|
||||
|
||||
> The Home Assistant API has two variants: a synchronous and an asynchronous version (asyncio). This development course will focus on the synchronous version.
|
||||
|
||||
To verify that everything is working correctly, let's create a small Hello World component. To do so, create a file called `hello_world.py` in your custom components folder. Copy paste the following content to it:
|
||||
|
||||
```python
|
||||
# The domain of your component. Equal to the filename of your component.
|
||||
DOMAIN = "hello_world"
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Setup the hello_world component."""
|
||||
# States are in the format DOMAIN.OBJECT_ID.
|
||||
hass.states.set("hello_world.Hello_World", "Works!")
|
||||
|
||||
# Return boolean to indicate that initialization was successfully.
|
||||
return True
|
||||
```
|
||||
|
||||
Last step is to add `hello_world:` entry to your `configuration.yaml` file.
|
||||
|
||||
```yaml
|
||||
# Hello World component
|
||||
hello_world:
|
||||
```
|
||||
|
||||
After running `hass`, we should see log entries stating that `hello_world` component was loaded. What is more, an additional state card will appear within the main panel.
|
||||
|
||||
```log
|
||||
2018-04-03 21:44:20 INFO (MainThread) [homeassistant.loader] Loaded hello_world from custom_components.hello_world
|
||||
2018-04-03 21:44:20 INFO (MainThread) [homeassistant.setup] Setting up hello_world
|
||||
```
|
||||
|
||||
<img
|
||||
src='/img/en/frontend/hello-world-state-card.png'
|
||||
alt='State card showing that Hello World component is working as intended.'
|
||||
/>
|
78
website/versioned_docs/version-0.104.0/dev_101_services.md
Normal file
78
website/versioned_docs/version-0.104.0/dev_101_services.md
Normal file
@ -0,0 +1,78 @@
|
||||
---
|
||||
title: Integration Services
|
||||
sidebar_label: Custom Services
|
||||
id: version-0.104.0-dev_101_services
|
||||
original_id: dev_101_services
|
||||
---
|
||||
|
||||
Home Assistant provides ready-made services for a lot of things, but it doesn't always cover everything. Instead of trying to change Home Assistant, it is preferred to add it as a service under your own integration first. Once we see a pattern in these services, we can talk about generalizing them.
|
||||
|
||||
This is a simple "hello world" example to show the basics of registering a service. To use this example, create the file `<config dir>/custom_components/hello_service/__init__.py` and copy the below example code.
|
||||
|
||||
Services can be called from automations and from the service "Developer tools" in the frontend.
|
||||
|
||||
```python
|
||||
DOMAIN = "hello_service"
|
||||
|
||||
ATTR_NAME = "name"
|
||||
DEFAULT_NAME = "World"
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up is called when Home Assistant is loading our component."""
|
||||
|
||||
def handle_hello(call):
|
||||
"""Handle the service call."""
|
||||
name = call.data.get(ATTR_NAME, DEFAULT_NAME)
|
||||
|
||||
hass.states.set("hello_service.hello", name)
|
||||
|
||||
hass.services.register(DOMAIN, "hello", handle_hello)
|
||||
|
||||
# Return boolean to indicate that initialization was successfully.
|
||||
return True
|
||||
```
|
||||
|
||||
Load the integration by adding the following to your `configuration.yaml`. When your component is loaded, a new service should be available to call.
|
||||
|
||||
```yaml
|
||||
# configuration.yaml entry
|
||||
hello_service:
|
||||
```
|
||||
|
||||
Open the frontend and in the sidebar, click the first icon in the developer tool section. This will open the Call Service developer tool. On the right, find your service and click on it. This will automatically fill in the correct values.
|
||||
|
||||
Pressing "Call Service" will now call your service without any parameters. This will cause your service to create a state with the default name 'World'. If you want to specify the name, you have to specify parameters. Add the following JSON as Service Data and press "Call Service again".
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Planet"
|
||||
}
|
||||
```
|
||||
|
||||
The service will now overwrite the previous state with "Planet".
|
||||
|
||||
## Service descriptions
|
||||
|
||||
Adding services is only useful if users know about them. In Home Assistant we use a `services.yaml` as part of your integration to describe the services.
|
||||
|
||||
Services are published under the domain name of your integration, so in `services.yaml` we only use the service name as the base key.
|
||||
|
||||
```yaml
|
||||
# Example services.yaml entry
|
||||
|
||||
set_speed:
|
||||
# Description of the service
|
||||
description: Sets fan speed.
|
||||
# Different fields that your service accepts
|
||||
fields:
|
||||
# Key of the field
|
||||
entity_id:
|
||||
# Description of the field
|
||||
description: Name(s) of the entities to set
|
||||
# Example value that can be passed for this field
|
||||
example: 'fan.living_room'
|
||||
speed:
|
||||
description: Speed setting
|
||||
example: 'low'
|
||||
```
|
130
website/versioned_docs/version-0.104.0/dev_101_states.md
Normal file
130
website/versioned_docs/version-0.104.0/dev_101_states.md
Normal file
@ -0,0 +1,130 @@
|
||||
---
|
||||
title: Using States
|
||||
id: version-0.104.0-dev_101_states
|
||||
original_id: dev_101_states
|
||||
---
|
||||
|
||||
Home Assistant keeps track of the states of entities in a state machine. The state machine has very few requirements:
|
||||
|
||||
- Each state is related to an entity identified by an entity id. This id is made up of a domain and an object id. For example `light.kitchen_ceiling`. You can make up any combination of domain and object id, even overwriting existing states.
|
||||
- Each state has a primary attribute that describes the state of the entity. In the case of a light this could be for example "on" and "off". You can store anything you want in the state, as long as it's a string (will be converted if it's not).
|
||||
- You can store more information about an entity by setting attributes. Attributes is a dictionary that can contain any data that you want. The only requirement is that it's JSON serializable, so you're limited to numbers, strings, dictionaries and lists.
|
||||
|
||||
[Description of the state object.](https://www.home-assistant.io/docs/configuration/state_object/)
|
||||
|
||||
## Using states in your component
|
||||
|
||||
This is a simple tutorial/example on how to create and set states. We will do our work in a component called "hello_state". The purpose of this component is to display a given text in the frontend.
|
||||
|
||||
To get started, create the file `<config dir>/custom_components/hello_state.py` and copy the below example code.
|
||||
|
||||
```python
|
||||
"""
|
||||
Support for showing text in the frontend.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://developers.home-assistant.io/docs/en/dev_101_states.html
|
||||
"""
|
||||
import logging
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "hello_state"
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Setup the Hello State component. """
|
||||
_LOGGER.info("The 'hello state' component is ready!")
|
||||
|
||||
return True
|
||||
```
|
||||
|
||||
1. In the file header we decided to add some details: A short description and the link to the documentation.
|
||||
2. We want to do some logging. This means that we import the Python logging module and create an alias.
|
||||
3. The component name is equal to the domain name.
|
||||
4. The `setup` function will take care of the initialization of our component.
|
||||
The component will only write a log message. Keep in mind for later that you have several options for the severity:
|
||||
|
||||
- `_LOGGER.info(msg)`
|
||||
- `_LOGGER.warning(msg)`
|
||||
- `_LOGGER.error(msg)`
|
||||
- `_LOGGER.critical(msg)`
|
||||
- `_LOGGER.exception(msg)`
|
||||
|
||||
5. We return `True` if everything is ok.
|
||||
|
||||
Add the component to your `configuration.yaml` file.
|
||||
|
||||
```yaml
|
||||
hello_state:
|
||||
```
|
||||
|
||||
After a start or a restart of Home Assistant the component will create an entry in the log.
|
||||
|
||||
```log
|
||||
16-03-12 14:16:42 INFO (MainThread) [custom_components.hello_state] The 'hello state' component is ready!
|
||||
```
|
||||
|
||||
The next step is the introduction of configuration options. A user can pass configuration options to our component via `configuration.yaml`. To use them we'll use the passed in `config` variable to our `setup` method.
|
||||
|
||||
```python
|
||||
import logging
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "hello_state"
|
||||
|
||||
CONF_TEXT = "text"
|
||||
DEFAULT_TEXT = "No text!"
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up the Hello State component. """
|
||||
# Get the text from the configuration. Use DEFAULT_TEXT if no name is provided.
|
||||
text = config[DOMAIN].get(CONF_TEXT, DEFAULT_TEXT)
|
||||
|
||||
# States are in the format DOMAIN.OBJECT_ID
|
||||
hass.states.set("hello_state.Hello_State", text)
|
||||
|
||||
return True
|
||||
```
|
||||
|
||||
To use the latest feature of our component, update the entry in your `configuration.yaml` file.
|
||||
|
||||
```yaml
|
||||
hello_state:
|
||||
text: 'Hello, World!'
|
||||
```
|
||||
|
||||
Thanks to `DEFAULT_TEXT` variable the component will launch even if no `text:` field is used in the `configuration.yaml` file. Quite often there are variables which are required. It's important to check if all mandatory configuration variables are provided. If not, the setup should fail. We will use `voluptuous` as a helper to achieve this. The next listing shows the essential parts.
|
||||
|
||||
```python
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{DOMAIN: vol.Schema({vol.Required(CONF_TEXT): cv.string,})}, extra=vol.ALLOW_EXTRA
|
||||
)
|
||||
```
|
||||
|
||||
Now, when `text:` is missing from the config, Home Assistant will alert the user and not setup your component.
|
||||
|
||||
After a start or a restart of Home Assistant the component will be visible in the frontend if the `configuration.yaml` file is up-to-date.
|
||||
|
||||
<p class='img'>
|
||||
<img src='/img/en/development/create-component01.png' />
|
||||
</p>
|
||||
|
||||
In order to expose attributes for a platform, you will need to define a property called `device_state_attributes` on the entity class, which will return a dictionary of attributes:
|
||||
|
||||
```python
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return device specific state attributes."""
|
||||
return self._attributes
|
||||
```
|
||||
|
||||
> Entities also have a similar property `state_attributes`, which normally doesn't need to be defined by new platforms. This property is used by base components to add standard sets of attributes to a state. Example: The light component uses `state_attributes` to add brightness to the state dictionary. If you are designing a new component, you should define `state_attributes` instead.
|
||||
|
||||
To get your component included in the Home Assistant releases, follow the steps described in the [Submit your work](development_submitting.md) section. Basically you only need to move your component into the `homeassistant/component/` directory of your fork and create a Pull Request.
|
@ -0,0 +1,38 @@
|
||||
---
|
||||
title: Catching up with Reality
|
||||
id: version-0.104.0-development_catching_up
|
||||
original_id: development_catching_up
|
||||
---
|
||||
|
||||
If it's taking a while to develop your feature, and you want to catch up with what's in the current Home Assistant `dev` branch, you can use `git rebase`. This will pull the latest Home Assistant changes locally, rewind your commits, bring in the latest changes from Home Assistant, and replay all of your commits on top.
|
||||
|
||||
> If you use the workflow below, it is important that you force push the update as described. Git might prompt you to do `git pull` first. Do **NOT** do that! It would mess up your commit history.
|
||||
|
||||
```shell
|
||||
# Run this from your feature branch
|
||||
$ git fetch upstream dev # to pull the latest changes into a local dev branch
|
||||
$ git rebase upstream/dev # to put those changes into your feature branch before your changes
|
||||
```
|
||||
|
||||
If rebase detects conflicts, repeat this process until all changes have been resolved:
|
||||
|
||||
1. `git status` shows you the file with the conflict; edit the file and resolve the lines between `<<<< | >>>>`
|
||||
3. Add the modified file: `git add <file>` or `git add .`
|
||||
4. Continue rebase: `git rebase --continue`
|
||||
5. Repeat until you've resolved all conflicts
|
||||
|
||||
After rebasing your branch, you will have rewritten history relative to your GitHub fork's branch. When you go to push you will see an error that your history has diverged from the original branch. In order to get your GitHub fork up-to-date with your local branch, you will need to force push, using the following command:
|
||||
|
||||
```shell
|
||||
# Run this from your feature branch
|
||||
$ git push origin --force
|
||||
```
|
||||
|
||||
Other workflows are covered in detail in the [Github documentation](https://help.github.com/articles/fork-a-repo/). Add an additional `remote` after you clone your fork.
|
||||
|
||||
```shell
|
||||
$ git remote add upstream https://github.com/home-assistant/home-assistant.git
|
||||
```
|
||||
|
||||
Then, `git pull --rebase upstream dev`.
|
||||
|
@ -0,0 +1,151 @@
|
||||
---
|
||||
title: Set up Development Environment
|
||||
id: version-0.104.0-development_environment
|
||||
original_id: development_environment
|
||||
---
|
||||
|
||||
You'll need to set up a development environment if you want to develop a new feature or component for Home Assistant. Read on to learn how to set up.
|
||||
|
||||
## Preparing your environment
|
||||
|
||||
### Developing on Linux
|
||||
|
||||
Install the core dependencies.
|
||||
|
||||
```shell
|
||||
$ sudo apt-get install python3-pip python3-dev python3-venv
|
||||
```
|
||||
|
||||
In order to run `script/setup` below you will need some more dependencies.
|
||||
|
||||
```shell
|
||||
$ sudo apt-get install autoconf libssl-dev libxml2-dev libxslt1-dev libjpeg-dev libffi-dev libudev-dev zlib1g-dev pkg-config
|
||||
$ sudo apt-get install -y libavformat-dev libavcodec-dev libavdevice-dev libavutil-dev libswscale-dev libavresample-dev libavfilter-dev
|
||||
```
|
||||
|
||||
> Different distributions have different package installation mechanisms and sometimes packages names as well. For example CentOS would use: `sudo yum install epel-release && sudo yum install python36 python36-devel mysql-devel gcc`
|
||||
|
||||
Additional dependencies exist if you plan to perform Frontend Development, please read the [Frontend](frontend_index.md) section to learn more.
|
||||
|
||||
### Developing on Windows
|
||||
|
||||
Since Home Assistant is mainly designed and developed on Linux distributions, on Windows 10 you can setup a [Linux subsystem](https://docs.microsoft.com/windows/wsl/install-win10).
|
||||
|
||||
Open Powershell as an Administrator and run
|
||||
|
||||
```shell
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
|
||||
```
|
||||
|
||||
From Windows Store install Ubuntu.
|
||||
|
||||
When the Linux subsystem is set up, perform install as for Linux.
|
||||
|
||||
```shell
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install python3-pip python3.7-dev python3.7-venv python-wheel-common
|
||||
$ sudo apt-get install autoconf libssl-dev libxml2-dev libxslt1-dev libjpeg-dev libffi-dev libudev-dev zlib1g-dev
|
||||
$ sudo apt-get install -y libavformat-dev libavcodec-dev libavdevice-dev libavutil-dev libswscale-dev libavresample-dev libavfilter-dev
|
||||
```
|
||||
|
||||
Hint: Git is included in Linux subsytem.
|
||||
|
||||
When invoking your installation (see below), make sure to specify a folder for configuration which is accessible from Windows.
|
||||
|
||||
```shell
|
||||
$ mkdir -p ../config
|
||||
$ hass -c ../config
|
||||
```
|
||||
|
||||
### Developing on macOS
|
||||
|
||||
Install [Homebrew](https://brew.sh/), then use that to install Python 3:
|
||||
|
||||
```shell
|
||||
$ brew install python3 autoconf
|
||||
```
|
||||
|
||||
Then install ffmpeg:
|
||||
|
||||
```shell
|
||||
$ brew install ffmpeg
|
||||
```
|
||||
|
||||
### Developing with devcontainer
|
||||
|
||||
The devcontainer is a preconfigured development environment with all the tools you need.
|
||||
|
||||
**Prerequisites**
|
||||
|
||||
- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
||||
- [Docker](https://docs.docker.com/install/)
|
||||
- [Visual Studio code](https://code.visualstudio.com/)
|
||||
- [Remote - Containers (VSC Extension)](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
|
||||
|
||||
[More info about requirements and devcontainer in general](https://code.visualstudio.com/docs/remote/containers#_getting-started)
|
||||
|
||||
**Getting started:**
|
||||
|
||||
1. Fork the repository.
|
||||
1. Clone the repository to your computer.
|
||||
1. Open the repository using Visual Studio code.
|
||||
|
||||
When you open this repository with Visual Studio code you are asked to "Reopen in Container", this will start the build of the container.
|
||||
|
||||
_If you don't see this notification, open the command pallet and select `Remote-Containers: Reopen Folder in Container`._
|
||||
|
||||
_If you get `command 'remote-containers.reopenInContainer' not found` make sure to use a Visual Studio code version with remote container support enabled._
|
||||
|
||||
The devcontainter comes with some useful tasks to help you with development, you can start these tasks by opening the command pallet and select `Tasks: Run Task` then select the task you want to run.
|
||||
|
||||
Running tasks like `Preview` can be restarted by opening the command pallet and selecting `Tasks: Restart Running Task`, then select the task you want to restart.
|
||||
|
||||
## Setup Local Repository
|
||||
|
||||
Visit the [Home Assistant repository](https://github.com/home-assistant/home-assistant) and click **Fork**.
|
||||
Once forked, setup your local copy of the source using the commands:
|
||||
|
||||
_Windows users should be sure to clone to a path that inside the WSL (ex: ~/)._
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/YOUR_GIT_USERNAME/home-assistant.git
|
||||
$ cd home-assistant
|
||||
$ git remote add upstream https://github.com/home-assistant/home-assistant.git
|
||||
```
|
||||
|
||||
## Setting up virtual environment
|
||||
|
||||
To isolate your environment from the rest of the system, set up a [`venv`](https://docs.python.org/3/library/venv.html). Within the `home-assistant` directory, create and activate your virtual environment.
|
||||
|
||||
```shell
|
||||
$ python3.7 -m venv venv
|
||||
$ source venv/bin/activate
|
||||
```
|
||||
|
||||
Install the requirements with a provided script named `setup`.
|
||||
|
||||
```shell
|
||||
$ script/setup
|
||||
```
|
||||
|
||||
Invoke your installation, adjusting the [configuration](https://www.home-assistant.io/docs/configuration/) if required.
|
||||
|
||||
```shell
|
||||
$ hass
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
By default logging in Home Assistant is tuned for operating in production (set to INFO by default, with some modules set to even less verbose logging levels).
|
||||
|
||||
You can use the [logger](https://www.home-assistant.io/components/logger/) component to adjust logging to DEBUG to see even more details about what is going on:
|
||||
|
||||
```yaml
|
||||
logger:
|
||||
default: info
|
||||
logs:
|
||||
homeassistant.core: debug
|
||||
nest.nest: debug
|
||||
asyncio: debug
|
||||
homeassistant.components.cloud.iot: debug
|
||||
```
|
@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Style guidelines
|
||||
id: version-0.104.0-development_guidelines
|
||||
original_id: development_guidelines
|
||||
---
|
||||
|
||||
Home Assistant enforces quite strict [PEP8 style](https://www.python.org/dev/peps/pep-0008/) and [PEP 257 (Docstring Conventions)](https://www.python.org/dev/peps/pep-0257/) compliance on all code submitted.
|
||||
|
||||
We use [Black](https://github.com/psf/black) for uncompromised code formatting. Every pull request is automatically checked as part of the linting process and we never merge submissions that diverge.
|
||||
|
||||
Summary of the most relevant points:
|
||||
|
||||
- Comments should be full sentences and end with a period.
|
||||
- [Imports](https://www.python.org/dev/peps/pep-0008/#imports) should be ordered.
|
||||
- Constants and the content of lists and dictionaries should be in alphabetical order.
|
||||
|
||||
It is advisable to adjust IDE or editor settings to match those requirements.
|
||||
|
||||
## Our recommendations
|
||||
|
||||
For some cases [PEPs](https://www.python.org/dev/peps/) don't make a statement. This section covers our recommendations about the code style. Those points were collected from the existing code and based on what contributors and developers were using the most. This is basically a majority decision, thus you may not agree with it. But we would like to encourage you follow those recommendations to keep the code consistent.
|
||||
|
||||
### File headers
|
||||
|
||||
The docstring in the file header should describe what the file is about.
|
||||
|
||||
```python
|
||||
"""Support for MQTT lights."""
|
||||
```
|
||||
|
||||
### Log messages
|
||||
|
||||
There is no need to add the platform or component name to the log messages. This will be added automatically. Like `syslog` messages there shouldn't be any period at the end. A widely used style is shown below but you are free to compose the messages as you like.
|
||||
|
||||
```python
|
||||
_LOGGER.error("No route to device: %s", self._resource)
|
||||
```
|
||||
|
||||
```log
|
||||
2017-05-01 14:28:07 ERROR [homeassistant.components.sensor.arest] No route to device: 192.168.0.18
|
||||
```
|
||||
|
||||
Do not print out API keys, tokens, usernames or passwords (even if they are wrong).
|
||||
Also note that `_LOGGER.info` is reserved for the core, use `_LOGGER.debug` for anything else.
|
||||
|
||||
### Ordering of imports
|
||||
|
||||
Instead of order the imports manually, use [`isort`](https://github.com/timothycrosley/isort).
|
||||
|
||||
```shell
|
||||
$ pip3 install isort
|
||||
$ isort homeassistant/components/sensor/fixer.py
|
||||
```
|
||||
|
||||
### Use new style string formatting
|
||||
|
||||
Prefer [f-strings](https://docs.python.org/3/reference/lexical_analysis.html#f-strings) over `%` or `str.format`.
|
||||
|
||||
```python
|
||||
# New
|
||||
f"{some_value} {some_other_value}"
|
||||
# Old, wrong
|
||||
"{} {}".format("New", "style")
|
||||
"%s %s" % ("Old", "style")
|
||||
```
|
||||
|
||||
One exception is for logging which uses the percentage formatting. This is to avoid formatting the log message when it is suppressed.
|
||||
|
||||
```python
|
||||
_LOGGER.info("Can't connect to the webservice %s at %s", string1, string2)
|
||||
```
|
@ -0,0 +1,86 @@
|
||||
---
|
||||
title: Testing your code
|
||||
id: version-0.104.0-development_testing
|
||||
original_id: development_testing
|
||||
---
|
||||
|
||||
As it states in the [Style guidelines section](development_guidelines.md) all code is checked to verify the following:
|
||||
|
||||
- All the unit tests pass
|
||||
- All code passes the checks from the linting tools
|
||||
|
||||
Local testing is done using [Tox](https://tox.readthedocs.io), which has been installed as part of running `script/setup` in the [virtual environment](development_environment.md). To start the tests, activate the virtual environment and simply run the command:
|
||||
|
||||
```shell
|
||||
$ tox
|
||||
```
|
||||
|
||||
It might be required that you install additional packages depending on your distribution/operating system:
|
||||
|
||||
- Fedora: `sudo dnf -y install systemd-devel gcc-c++`
|
||||
- Ubuntu: `sudo apt-get install libudev-dev`
|
||||
|
||||
**Important:** Run `tox` before you create your pull request to avoid annoying fixes.
|
||||
|
||||
Running `tox` will run unit tests against the locally available Python releases, as well as validate the code and document style using `pycodestyle`, `pydocstyle` and `pylint`. You can run tests on only one `tox` target -- just use `-e` to select an environment. For example, `tox -e lint` runs the linters only, and `tox -e py38` runs unit tests only on Python 3.8.
|
||||
|
||||
`tox` uses virtual environments under the hood to create isolated testing environments. The `tox` virtual environments will get out-of-date when requirements change, causing test errors. Run `tox -r` to tell `tox` to recreate the virtual environments.
|
||||
|
||||
macOS users may see an `Error creating virtualenv` when runnng `tox`. If this occurs, install the [tox-venv](https://pypi.org/project/tox-venv/) package using the command `pip install tox-venv` and try again.
|
||||
|
||||
### Adding new dependencies to test environment
|
||||
|
||||
If you are working on tests for an integeration and you need the dependencies available inside the `tox` environment, update the list inside `script/gen_requirements_all.py`. Then run the script and then run `tox -r` to recreate the virtual environments.
|
||||
|
||||
### Running single tests using `tox`
|
||||
|
||||
You can pass arguments via `tox` to `py.test` to be able to run single test suites or test files. Replace `py38` with the Python version that you use.
|
||||
|
||||
```shell
|
||||
# Stop after the first test fails
|
||||
$ tox -e py38 -- tests/test_core.py -x
|
||||
# Run test with specified name
|
||||
$ tox -e py38 -- tests/test_core.py -k test_split_entity_id
|
||||
# Fail a test after it runs for 2 seconds
|
||||
$ tox -e py38 -- tests/test_core.py --timeout 2
|
||||
# Show the 10 slowest tests
|
||||
$ tox -e py38 -- tests/test_core.py --duration=10
|
||||
```
|
||||
|
||||
### Testing outside of Tox
|
||||
|
||||
Running `tox` will invoke the full test suite. Even if you specify which tox target to run, you still run all tests inside that target. That's not very convenient to quickly iterate on your code! To be able to run the specific test suites without `tox`, you'll need to install the test dependencies into your Python environment:
|
||||
|
||||
```shell
|
||||
$ pip3 install -r requirements_test_all.txt -c homeassistant/package_constraints.txt
|
||||
```
|
||||
|
||||
Now that you have all test dependencies installed, you can run tests on individual files:
|
||||
|
||||
```shell
|
||||
$ flake8 homeassistant/core.py
|
||||
$ pylint homeassistant/core.py
|
||||
$ pydocstyle homeassistant/core.py
|
||||
$ py.test tests/test_core.py
|
||||
```
|
||||
|
||||
You can also run linting tests against all changed files, as reported by `git diff upstream/dev... --diff-filter=d --name-only`, using the `lint` script:
|
||||
|
||||
```shell
|
||||
$ script/lint
|
||||
```
|
||||
|
||||
### Preventing linter errors
|
||||
|
||||
Save yourself the hassle of extra commits just to fix style errors by enabling the Flake8 git commit hook. Flake8 will check your code when you try to commit to the repository and block the commit if there are any style errors, which gives you a chance to fix them!
|
||||
|
||||
```shell
|
||||
$ pip3 install flake8 flake8-docstrings
|
||||
$ flake8 --install-hook=git
|
||||
```
|
||||
|
||||
The `flake8-docstrings` extension will check docstrings according to [PEP257](https://www.python.org/dev/peps/pep-0257/) when running Flake8.
|
||||
|
||||
### Notes on PyLint and PEP8 validation
|
||||
|
||||
If you can't avoid a PyLint warning, add a comment to disable the PyLint check for that line with `# pylint: disable=YOUR-ERROR-NAME`. Example of an unavoidable one is if PyLint incorrectly reports that a certain object doesn't have a certain member.
|
@ -0,0 +1,95 @@
|
||||
---
|
||||
title: Validate the input
|
||||
id: version-0.104.0-development_validation
|
||||
original_id: development_validation
|
||||
---
|
||||
|
||||
The `configuration.yaml` file contains the configuration options for components and platforms. We use [voluptuous](https://pypi.python.org/pypi/voluptuous) to make sure that the configuration provided by the user is valid. Some entries are optional or could be required to set up a platform or a component. Others must be a defined type or from an already-defined list.
|
||||
|
||||
We test the configuration to ensure that users have a great experience and minimize notifications if something is wrong with a platform or component setup before Home Assistant runs.
|
||||
|
||||
Besides [voluptuous](https://pypi.python.org/pypi/voluptuous) default types, many custom types are available. For an overview, take a look at the [config_validation.py](https://github.com/home-assistant/home-assistant/blob/master/homeassistant/helpers/config_validation.py) helper.
|
||||
|
||||
- Types: `string`, `byte`, and `boolean`
|
||||
- Entity ID: `entity_id` and `entity_ids`
|
||||
- Numbers: `small_float` and `positive_int`
|
||||
- Time: `time`, `time_zone`
|
||||
- Misc: `template`, `slug`, `temperature_unit`, `latitude`, `longitude`, `isfile`, `sun_event`, `ensure_list`, `port`, `url`, and `icon`
|
||||
|
||||
To validate platforms using [MQTT](https://www.home-assistant.io/components/mqtt/), `valid_subscribe_topic` and `valid_publish_topic` are available.
|
||||
|
||||
Some things to keep in mind:
|
||||
|
||||
- Use the constants defined in `const.py`
|
||||
- Import `PLATFORM_SCHEMA` from the integration you are integrating with and extend it.
|
||||
- Preferred order is `required` first and `optional` second
|
||||
- Default values for optional configuration keys need to be valid values. Don't use a default which is `None` like `vol.Optional(CONF_SOMETHING, default=None): cv.string`, set the default to `default=''` if required.
|
||||
|
||||
### Snippets
|
||||
|
||||
This section contains snippets for the validation we use.
|
||||
|
||||
#### Default name
|
||||
|
||||
It's common to set a default for a sensor if the user doesn't provide a name to use.
|
||||
|
||||
```python
|
||||
DEFAULT_NAME = "Sensor name"
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
# ...
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
#### Limit the values
|
||||
|
||||
You might want to limit the user's input to a couple of options.
|
||||
|
||||
```python
|
||||
DEFAULT_METHOD = "GET"
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
# ...
|
||||
vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(["POST", "GET"]),
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
#### Port
|
||||
|
||||
All port numbers are from a range of 1 to 65535.
|
||||
|
||||
```python
|
||||
DEFAULT_PORT = 993
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
# ...
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
#### Lists
|
||||
|
||||
If a sensor has a pre-defined list of available options, test to make sure the configuration entry matches the list.
|
||||
|
||||
```python
|
||||
SENSOR_TYPES = {
|
||||
"article_cache": ("Article Cache", "MB"),
|
||||
"average_download_rate": ("Average Speed", "MB/s"),
|
||||
}
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
# ...
|
||||
vol.Optional(CONF_MONITORED_VARIABLES, default=[]): vol.All(
|
||||
cv.ensure_list, [vol.In(SENSOR_TYPES)]
|
||||
),
|
||||
}
|
||||
)
|
||||
```
|
@ -0,0 +1,81 @@
|
||||
---
|
||||
title: Device Registry
|
||||
sidebar_label: Introduction
|
||||
id: version-0.104.0-device_registry_index
|
||||
original_id: device_registry_index
|
||||
---
|
||||
|
||||
The device registry is a registry where Home Assistant keeps track of devices. A device is represented in Home Assistant via one or more entities. For example, a battery-powered temperature and humidity sensor might expose entities for temperature, humidity and battery level.
|
||||
|
||||
<img
|
||||
src='/img/en/device_registry/overview.png'
|
||||
alt='Device registry overview'
|
||||
/>
|
||||
|
||||
## What is a device?
|
||||
|
||||
A device in Home Assistant represents a physical device that has its own control unit. The control unit itself does not have to be smart, but it should be in control of what happens. For example, an Ecobee thermostat with 4 room sensors equals 5 devices in Home Assistant, one for the thermostat including all sensors inside it, and one for each sensor. Each device exists in a specific geographical area, and may have more than one input or output within that area.
|
||||
|
||||
If you connect a sensor to another device to read some of its data, it should still be represented as two different devices. The reason for this is that the sensor could be moved to read the data of another device.
|
||||
|
||||
A device that offers multiple endpoints, where parts of the device sense or output in different areas, should be split into separate devices and refer back to parent device with the `via_device` attribute. This allows the separate endpoints to be assigned to different areas in the building.
|
||||
|
||||
> Although not currently available, we could consider offering an option to users to merge devices.
|
||||
|
||||
## Device properties
|
||||
|
||||
| Attribute | Description |
|
||||
| --------- | ----------- |
|
||||
| id | Unique ID of device (generated by Home Assistant)
|
||||
| name | Name of this device
|
||||
| connections | A set of tuples of `(connection_type, connection identifier)`. Connection types are defined in the device registry module.
|
||||
| identifiers | Set of identifiers. They identify the device in the outside world. An example is a serial number.
|
||||
| manufacturer | The manufacturer of the device.
|
||||
| model | The model of the device.
|
||||
| config_entries | Config entries that are linked to this device.
|
||||
| sw_version | The firmware version of the device.
|
||||
| via_device | Identifier of a device that routes messages between this device and Home Assistant. Examples of such devices are hubs, or parent devices of a sub-device. This is used to show device topology in Home Assistant.
|
||||
| area_id | The Area which the device is placed in.
|
||||
|
||||
## Defining devices
|
||||
|
||||
> Entity device info is only read if the entity is loaded via a [config entry](config_entries_index.md).
|
||||
|
||||
Each entity is able to define a device via the `device_info` property. This property is read when an entity is added to Home Assistant via a config entry. A device will be matched up with an existing device via supplied identifiers and connections, like serial numbers or MAC addresses.
|
||||
|
||||
```python
|
||||
# Inside a platform
|
||||
class HueLight(LightEntity):
|
||||
@property
|
||||
def device_info(self):
|
||||
return {
|
||||
"identifiers": {
|
||||
# Serial numbers are unique identifiers within a specific domain
|
||||
(hue.DOMAIN, self.unique_id)
|
||||
},
|
||||
"name": self.name,
|
||||
"manufacturer": self.light.manufacturername,
|
||||
"model": self.light.productname,
|
||||
"sw_version": self.light.swversion,
|
||||
"via_device": (hue.DOMAIN, self.api.bridgeid),
|
||||
}
|
||||
```
|
||||
|
||||
Components are also able to register devices in the case that there are no entities representing them. An example is a hub that communicates with the lights.
|
||||
|
||||
```python
|
||||
# Inside a component
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
device_registry = await dr.async_get_registry(hass)
|
||||
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, config.mac)},
|
||||
identifiers={(DOMAIN, config.bridgeid)},
|
||||
manufacturer="Signify",
|
||||
name=config.name,
|
||||
model=config.modelid,
|
||||
sw_version=config.swversion,
|
||||
)
|
||||
```
|
@ -0,0 +1,142 @@
|
||||
---
|
||||
title: Create a new page
|
||||
id: version-0.104.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/integrations/) 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_quality_scale: silver
|
||||
ha_config_flow: true
|
||||
ha_codeowners:
|
||||
- '@balloob'
|
||||
---
|
||||
|
||||
Content... Written in markdown.
|
||||
|
||||
### Title Header
|
||||
...
|
||||
```
|
||||
|
||||
Additional keys for the file header:
|
||||
|
||||
- `title`: This title should match with the name of the integration as written in the integration manifest file.
|
||||
- `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/integrations/).
|
||||
- `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_quality_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.
|
||||
- `ha_codeowners`: GitHub usernames or team names (starting with `@`) of people that are responsible for this integration. This should match with the codeowners as listed in the integration manifest file.
|
||||
|
||||
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.104.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:
|
||||
|
||||
```shell
|
||||
$ 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.
|
||||
|
||||
```shell
|
||||
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:
|
||||
|
||||
```shell
|
||||
bundle exec rake integrate
|
||||
```
|
188
website/versioned_docs/version-0.104.0/entity_cover.md
Normal file
188
website/versioned_docs/version-0.104.0/entity_cover.md
Normal file
@ -0,0 +1,188 @@
|
||||
---
|
||||
title: Cover Entity
|
||||
sidebar_label: Cover
|
||||
id: version-0.104.0-entity_cover
|
||||
original_id: entity_cover
|
||||
---
|
||||
|
||||
A cover entity is a device that controls an opening or cover, such as a garage door and window shade. Derive entity platforms from [`homeassistant.components.cover.CoverDevice`](https://github.com/home-assistant/home-assistant/blob/master/homeassistant/components/cover/__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.
|
||||
|
||||
### Platform Properties (to be implemented by deriving platform classes)
|
||||
|
||||
| Name | Type | Default | Description
|
||||
| ---- | ---- | ------- | -----------
|
||||
| current_cover_position | int | None | The current position of cover where 0 means closed and 100 is fully open. Required with `SUPPORT_SET_POSITION`.
|
||||
| current_cover_tilt_position | int | None | The current tilt position of the cover where 0 means closed/no tilt and 100 means open/maximum tilt. Required with `SUPPORT_SET_TILT_POSITION`
|
||||
| is_opening | bool | None | If the cover is opening or not. Used to determine `state`.
|
||||
| is_closing | bool | None | If the cover is closing or not. Used to determine `state`.
|
||||
| is_closed | bool | `NotImplementedError()` | If the cover is closed or not. if the state is unknown, return `None`. Used to determine `state`.
|
||||
|
||||
### Entity Properties (base class properties which may be overriden)
|
||||
|
||||
| Name | Type | Default | Description
|
||||
| ---- | ---- | ------- | -----------
|
||||
| device_class | string | None | Describes the type/class of the cover. Must be `None` or one of the valid values from the table below.
|
||||
| supported_features | int (bitwise) | Value determined from `current_cover_position` and `current_cover_tilt_position` | Describes the supported features. See the related table below for details.
|
||||
|
||||
### Device Classes
|
||||
| Constant | Description
|
||||
|----------|-----------------------|
|
||||
| `DEVICE_CLASS_AWNING` | Control of an awning, such as an exterior retractible window, door, or patio cover.
|
||||
| `DEVICE_CLASS_BLIND` | Control of blinds, which are linked slats that expand or collapse to cover an opening or may be tilted to partially cover an opening, such as window blinds.
|
||||
| `DEVICE_CLASS_CURTAIN` | Control of curtains or drapes, which is often fabric hung above a window or door that can be drawn open.
|
||||
| `DEVICE_CLASS_DAMPER` | Control of a mechanical damper that reduces air flow, sound, or light.
|
||||
| `DEVICE_CLASS_DOOR` | Control of a door or gate that provides access to an area.
|
||||
| `DEVICE_CLASS_GARAGE` | Control of a garage door that provides access to a garage.
|
||||
| `DEVICE_CLASS_SHADE` | Control of shades, which are a continous plane of material or connected cells that expanded or collapsed over an opening, such as window shades.
|
||||
| `DEVICE_CLASS_SHUTTER` | Control of shutters, which are linked slats that swing out/in to cover an opening or may be tilted to partially cover an opening, such as indoor or exterior window shutters.
|
||||
| `DEVICE_CLASS_WINDOW` | Control of a physical window that opens and closes or may tilt.
|
||||
|
||||
### States
|
||||
| Constant | Description
|
||||
|----------|------------------------|
|
||||
| `STATE_OPENING` | The cover is in the process of opening to reach a set position.
|
||||
| `STATE_OPEN` | The cover has reached the open position.
|
||||
| `STATE_CLOSING` | The cover is in the process of closing to reach a set position.
|
||||
| `STATE_CLOSED` | The cover has reach the closed position.
|
||||
|
||||
|
||||
### Supported Features
|
||||
|
||||
Supported features constants are combined using the bitwise or (`|`) operator.
|
||||
|
||||
| Constant | Description |
|
||||
|----------|--------------------------------------|
|
||||
| `SUPPORT_OPEN` | The cover supports being opened.
|
||||
| `SUPPORT_CLOSE` | The cover supports being closed.
|
||||
| `SUPPORT_SET_POSITION` | The cover supports moving to a specific position between opened and closed.
|
||||
| `SUPPORT_STOP` | The cover supports stopping the current action (open, close, set position)
|
||||
| `SUPPORT_OPEN_TILT` | The cover supports being tilting open.
|
||||
| `SUPPORT_CLOSE_TILT` | The cover supports being tilting closed.
|
||||
| `SUPPORT_SET_TILT_POSITION` | The cover supports moving to a specific tilt position between opened and closed.
|
||||
| `SUPPORT_STOP_TILT` | The cover supports stopping the current tilt action (open, close, set position)
|
||||
|
||||
## Methods
|
||||
|
||||
### Open cover
|
||||
|
||||
Only implement this method if the flag `SUPPORT_OPEN` is set.
|
||||
|
||||
```python
|
||||
class MyCover(CoverDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def open_cover(self, **kwargs):
|
||||
"""Open the cover."""
|
||||
|
||||
async def async_open_cover(self, **kwargs):
|
||||
"""Open the cover."""
|
||||
```
|
||||
|
||||
### Close cover
|
||||
|
||||
Only implement this method if the flag `SUPPORT_CLOSE` is set.
|
||||
|
||||
```python
|
||||
class MyCover(CoverDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def close_cover(self, **kwargs):
|
||||
"""Close cover."""
|
||||
|
||||
async def async_close_cover(self, **kwargs):
|
||||
"""Close cover."""
|
||||
```
|
||||
|
||||
### Set cover position
|
||||
|
||||
Only implement this method if the flag `SUPPORT_SET_POSITION` is set.
|
||||
|
||||
```python
|
||||
class MyCover(CoverDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def set_cover_position(self, **kwargs):
|
||||
"""Move the cover to a specific position."""
|
||||
|
||||
async def async_set_cover_position(self, **kwargs):
|
||||
"""Move the cover to a specific position."""
|
||||
```
|
||||
|
||||
### Stop cover
|
||||
|
||||
Only implement this method if the flag `SUPPORT_STOP` is set.
|
||||
|
||||
```python
|
||||
class MyCover(CoverDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def stop_cover(self, **kwargs):
|
||||
"""Stop the cover."""
|
||||
|
||||
async def async_stop_cover(self, **kwargs):
|
||||
"""Stop the cover."""
|
||||
```
|
||||
|
||||
### Open cover tilt
|
||||
|
||||
Only implement this method if the flag `SUPPORT_OPEN_TILT` is set.
|
||||
|
||||
```python
|
||||
class MyCover(CoverDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def open_cover_tilt(self, **kwargs):
|
||||
"""Open the cover tilt."""
|
||||
|
||||
async def async_open_cover_tilt(self, **kwargs):
|
||||
"""Open the cover tilt."""
|
||||
```
|
||||
|
||||
### Close cover tilt
|
||||
|
||||
Only implement this method if the flag `SUPPORT_CLOSE_TILT` is set.
|
||||
|
||||
```python
|
||||
class MyCover(CoverDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def close_cover_tilt(self, **kwargs):
|
||||
"""Close the cover tilt."""
|
||||
|
||||
async def async_close_cover_tilt(self, **kwargs):
|
||||
"""Close the cover tilt."""
|
||||
```
|
||||
|
||||
### Set cover tilt position
|
||||
|
||||
Only implement this method if the flag `SUPPORT_SET_TILT_POSITION` is set.
|
||||
|
||||
```python
|
||||
class MyCover(CoverDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def set_cover_tilt_position(self, **kwargs):
|
||||
"""Move the cover tilt to a specific position."""
|
||||
|
||||
async def async_set_cover_tilt_position(self, **kwargs):
|
||||
"""Move the cover tilt to a specific position."""
|
||||
```
|
||||
|
||||
### Stop cover tilt
|
||||
|
||||
Only implement this method if the flag `SUPPORT_STOP_TILT` is set.
|
||||
|
||||
```python
|
||||
class MyCover(CoverDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def stop_cover_tilt(self, **kwargs):
|
||||
"""Stop the cover."""
|
||||
|
||||
async def async_stop_cover_tilt(self, **kwargs):
|
||||
"""Stop the cover."""
|
||||
```
|
98
website/versioned_docs/version-0.104.0/entity_fan.md
Normal file
98
website/versioned_docs/version-0.104.0/entity_fan.md
Normal file
@ -0,0 +1,98 @@
|
||||
---
|
||||
title: Fan Entity
|
||||
sidebar_label: Fan
|
||||
id: version-0.104.0-entity_fan
|
||||
original_id: entity_fan
|
||||
---
|
||||
|
||||
A fan entity is a device that controls the different vectors of your fan such as speed, direction and oscillation. Derive enitity platforms from ['homeassistant.components.fan.FanDevice'](https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/fan/__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
|
||||
| ---- | ---- | ------- | -----------
|
||||
| current_direction | str | None | Return the current direction of the fan |
|
||||
|is_on| boolean | None |Return true if the entity is on |
|
||||
| speed | str | None | Return the current speed |
|
||||
| speed_list | list | None| Get the list of available speeds |
|
||||
| state_attributes | dict | None | Return optional state attributes |
|
||||
| supported_features | int | None | Flag supported features |
|
||||
|
||||
|
||||
## Supported Features
|
||||
|
||||
| Constant | Description |
|
||||
|----------|--------------------------------------|
|
||||
| 'SUPPORT_DIRECTION' | The fan supports changing the direction of it.
|
||||
| 'SUPPORT_SET_SPEED' | The fan supports setting the speed.
|
||||
| 'SUPPORT_OSCILLATE' | The fan supports oscillation.
|
||||
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
### Set direction
|
||||
|
||||
Only implement this method if the flag `SUPPORT_DIRECTION` is set.
|
||||
|
||||
```python
|
||||
class FanEntity(ToggleEntity):
|
||||
# Implement one of these methods.
|
||||
|
||||
def set_direction(self, direction: str) -> None:
|
||||
"""Set the direction of the fan."""
|
||||
|
||||
async def async_set_direction(self, direction: str):
|
||||
"""Set the direction of the fan."""
|
||||
```
|
||||
|
||||
### Set speed
|
||||
|
||||
Only implement this method if the flag `SUPPORT_SET_SPEED` is set.
|
||||
|
||||
```python
|
||||
class FanEntity(ToggleEntity):
|
||||
# Implement one of these methods.
|
||||
|
||||
def set_speed(self, speed: str) -> None:
|
||||
"""Set the speed of the fan."""
|
||||
|
||||
async def async_set_speed(self, speed: str):
|
||||
"""Set the speed of the fan."""
|
||||
```
|
||||
|
||||
### Turn on
|
||||
|
||||
```python
|
||||
class FanEntity(ToggleEntity):
|
||||
# Implement one of these methods.
|
||||
|
||||
def turn_on(self, speed: str = None, **kwargs) -> None:
|
||||
"""Turn on the fan."""
|
||||
|
||||
async def async_turn_on(self, speed: str = None, **kwargs):
|
||||
"""Turn on the fan."""
|
||||
```
|
||||
|
||||
### Oscillate
|
||||
|
||||
Only implement this method if the flag `SUPPORT_OSCILLATE` is set.
|
||||
|
||||
```python
|
||||
class FanEntity(ToggleEntity):
|
||||
# Implement one of these methods.
|
||||
|
||||
def oscillate(self, oscillating: bool) -> None:
|
||||
"""Oscillate the fan."""
|
||||
|
||||
def async_oscillate(self, oscillating: bool):
|
||||
"""Oscillate the fan."""
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
113
website/versioned_docs/version-0.104.0/entity_index.md
Normal file
113
website/versioned_docs/version-0.104.0/entity_index.md
Normal file
@ -0,0 +1,113 @@
|
||||
---
|
||||
title: Entity
|
||||
sidebar_label: Introduction
|
||||
id: version-0.104.0-entity_index
|
||||
original_id: entity_index
|
||||
---
|
||||
|
||||
Each device is represented in Home Assistant as an entity. An entity abstracts away the internal working of Home Assistant. As an integrator you don't have to worry about how services or the state machine work. Instead, you extend an entity class and implement the necessary properties and methods for the device type that you're integrating.
|
||||
|
||||
Below is an example switch entity that keeps track of their state in memory.
|
||||
|
||||
```python
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
|
||||
|
||||
class MySwitch(SwitchDevice):
|
||||
def __init__(self):
|
||||
self._is_on = False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Name of the device."""
|
||||
return "My Switch"
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""If the switch is currently on or off."""
|
||||
return self._is_on
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the switch on."""
|
||||
self._is_on = True
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn the switch off."""
|
||||
self._is_on = False
|
||||
```
|
||||
|
||||
That's all there is to it to build a switch entity! Continue reading to learn more or check out the [video tutorial](https://youtu.be/Cfasc9EgbMU?t=737).
|
||||
|
||||
## Updating the entity
|
||||
|
||||
An entity represents a device. There are various strategies to keep your entity in sync with the state of the device, the most popular one being polling.
|
||||
|
||||
### Polling
|
||||
|
||||
With polling, Home Assistant will ask the entity from time to time (depending on the update interval of the component) to fetch the latest state. Home Assistant will poll an entity when the `should_poll` property returns `True` (the default value). You can either implement your update logic using `update()` or the async method `async_update()`. This method should fetch the latest state from the device and store it in an instance variable for the properties to return it.
|
||||
|
||||
### Subscribing to updates
|
||||
|
||||
When you subscribe to updates, your code is responsible for letting Home Assistant know that an update is available. Make sure you have the `should_poll` property return `False`.
|
||||
|
||||
Whenever you receive new state from your subscription, you can tell Home Assistant that an update is available by calling `schedule_update_ha_state()` or async callback `async_schedule_update_ha_state()`. Pass in the boolean `True` to the method if you want Home Assistant to call your update method before writing the update to Home Assistant.
|
||||
|
||||
## Generic properties
|
||||
|
||||
The entity base class has a few properties that are common among all entities in Home Assistant. These can be added to any entity regardless of the type. All these properties are optional and don't need to be implemented.
|
||||
|
||||
> 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
|
||||
| ---- | ---- | ------- | -----------
|
||||
| assumed_state | boolean | `False` | Return `True` if the state is based on our assumption instead of reading it from the device.
|
||||
| available | boolean | `True` | Indicate if Home Assistant is able to read the state and control the underlying device.
|
||||
| device_state_attributes | dict | `None` | Extra information to store in the state machine. It needs to be information that further explains the state, it should not be static information like firmware version. See [below](entity_index.md#standard-attributes) for details of standard attributes.
|
||||
| entity_picture | URL | `None` | Url of a picture to show for the entity.
|
||||
| name | string | `None` | Name of the entity
|
||||
| should_poll | boolean | `True` | Should Home Assistant check with the entity for an updated state. If set to `False`, entity will need to notify Home Assistant of new updates by calling one of the [schedule update methods](#methods).
|
||||
| unique_id | string | `None` | A unique identifier for this entity. Needs to be unique within a platform (ie `light.hue`). Should not be configurable by the user or be changeable. [Learn more.](entity_registry_index.md#unique-id-requirements)
|
||||
|
||||
## Advanced properties
|
||||
|
||||
The following properties are also available on entities. However, they are for advanced use only and should be used with caution.
|
||||
|
||||
| Name | Type | Default | Description
|
||||
| ---- | ---- | ------- | -----------
|
||||
| force_update | boolean | `False` | Write each update to the state machine, even if the data is the same. Example use: when you are directly reading the value from a connected sensor instead of a cache. Use with caution, will spam the state machine.
|
||||
| hidden | boolean | `False` | Indicate if the entity should not be shown on the frontend.
|
||||
| icon | icon | `None` | Icon to use in the frontend. Icons start with `mdi:` plus an [identifier](https://materialdesignicons.com/). You probably don't need this since Home Assistant already provides default icons for all devices.
|
||||
| entity_registry_enabled_default | boolean | `True` | Indicate if the entity should be enabled or disabled when it is first added to the entity registry.
|
||||
|
||||
## System properties
|
||||
|
||||
The following properties are used and controlled by Home Assistant, and should not be overridden by integrations.
|
||||
|
||||
| Name | Type | Default | Description
|
||||
| ---- | ---- | ------- | -----------
|
||||
| enabled | boolean | `True` | Indicate if entity is enabled in the entity registry. It also returns `True` if the platform doesn't support the entity registry. Disabled entities will not be added to Home Assistant.
|
||||
|
||||
## Standard attributes
|
||||
|
||||
The following `device_state_attributes` are considered standard and should follow the convention below. The constant should be imported from `homeassistant/const.py`.
|
||||
|
||||
| Name | Type | Unit | Constant | Description
|
||||
| ---- | ---- | ---- | -------- | -----------
|
||||
| battery_charging | boolean | N/A | `ATTR_BATTERY_CHARGING` | Battery charging status of the entity, shown as a boolean `true` or `false`. If charging is not supported, then this attribute should not be created.
|
||||
| battery_level | integer | % | `ATTR_BATTERY_LEVEL` | Battery level of the entity, shown as an integer percentage between 0-100.
|
||||
|
||||
## Lifecycle hooks
|
||||
|
||||
Use these lifecycle hooks to execute code when certain events happen to the entity. All lifecycle hooks are async methods.
|
||||
|
||||
### `async_added_to_hass()`
|
||||
|
||||
Called when an entity has their entity_id and hass object assigned, before it is written to the state machine for the first time. Example uses: restore the state, subscribe to updates or set callback/dispatch function/listener.
|
||||
|
||||
### `async_will_remove_from_hass()`
|
||||
|
||||
Called when an entity is about to be removed from Home Assistant. Example use: disconnect from the server or unsubscribe from updates.
|
||||
|
||||
## Changing the entity model
|
||||
|
||||
If you want to add a new feature to an entity or any of its subtypes (light, switch, etc), you will need to propose it first in our [architecture repo](https://github.com/home-assistant/architecture/issues). Only additions will be considered that are common features among various vendors.
|
60
website/versioned_docs/version-0.104.0/entity_light.md
Normal file
60
website/versioned_docs/version-0.104.0/entity_light.md
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
title: Light Entity
|
||||
sidebar_label: Light
|
||||
id: version-0.104.0-entity_light
|
||||
original_id: entity_light
|
||||
---
|
||||
|
||||
|
||||
A light entity is a device that controls the brightness, RGB value,color temperature and effects of a light source.
|
||||
|
||||
## Properties
|
||||
|
||||
| Name | Type | Default | Description
|
||||
| ---- | ---- | ---- | ----
|
||||
| brightness | int | None | Return the brightness of this light between 0..255
|
||||
| color_temp | int | None | Return the CT color value in mireds.
|
||||
| effect | String | None | Return the current effect.
|
||||
| effect_list | list | None | Return the list of supported effects.
|
||||
| hs_color | list | None | Return the hue and saturation color value [float, float].
|
||||
| is_on | bool | bool | Returns if the light entity is on or not.
|
||||
| max_minreds | int | int | Return the warmest color_temp that this light supports.
|
||||
| min_mireds | int | int | Return the coldest color_temp that this light supports.
|
||||
| supported_features | int | int | Flag supported features.
|
||||
| white_value | int | None | Return the white value of this light between 0..255.
|
||||
|
||||
|
||||
## Support Feature
|
||||
| Constant | Description
|
||||
|----------|-----------------------
|
||||
| `SUPPORT_BRIGHTNESS` | Controls the brightness of a light source
|
||||
| `SUPPORT_COLOR` | Controls the color a light source shows
|
||||
| `SUPPORT_COLOR_TEMP` | Controls the representation a light source shows based on temperature
|
||||
| `SUPPORT_EFFECT` | Controls the effect a light source shows
|
||||
| `SUPPORT_FLASH` | Controls the duration of a flash a light source shows
|
||||
| `SUPPORT_TRANSITION` | Controls the duration of transitions between color and effects
|
||||
| `SUPPORT_WHITE_VALUE` | Controls the white light a light source shows.
|
||||
|
||||
## Methods
|
||||
|
||||
# Turn on Light Device
|
||||
|
||||
```python
|
||||
class MyLightDevice(LightDevice):
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the device on."""
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn device on."""
|
||||
```
|
||||
|
||||
# Turn Off Light Device
|
||||
|
||||
```python
|
||||
class MyLightDevice(LightDevice):
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn the device off."""
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn device off."""
|
||||
```
|
@ -0,0 +1,77 @@
|
||||
---
|
||||
title: Media Player Entity
|
||||
sidebar_label: Media Player
|
||||
id: version-0.104.0-entity_media_player
|
||||
original_id: entity_media_player
|
||||
---
|
||||
|
||||
> This entry is incomplete. Contribution welcome.
|
||||
|
||||
## 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
|
||||
| ---- | ---- | ------- | -----------
|
||||
| sound_mode | string | None | The current sound mode of the media player
|
||||
| sound_mode_list | list | None | Dynamic list of available sound modes (set by platform, empty means sound mode not supported)
|
||||
| source | string | None | The currently selected input source for the media player.
|
||||
| source_list | list | None | The list of possible input sources for the media player. (This list should contain human readable names, suitible for frontend display)
|
||||
| media_image_url | string | None | URL that represents the current image.
|
||||
| media_image_remotely_accessible | boolean | False | Return `True` if property `media_image_url` is accessible outside of the home network.
|
||||
| device_class | string | `None` | Type of media player.
|
||||
|
||||
## Methods
|
||||
### Select sound mode
|
||||
Optional. Switch the sound mode of the media player.
|
||||
|
||||
class MyMediaPlayer(MediaPlayerDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def select_sound_mode(self, sound_mode):
|
||||
"""Switch the sound mode of the entity."""
|
||||
|
||||
def async_select_sound_mode(self, sound_mode):
|
||||
"""Switch the sound mode of the entity."""
|
||||
|
||||
### Select source
|
||||
Optional. Switch the selected input source for the media player.
|
||||
|
||||
class MyMediaPlayer(MediaPlayerDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def select_source(self, source):
|
||||
"""Select input source."""
|
||||
|
||||
def async_select_source(self, source):
|
||||
"""Select input source."""
|
||||
|
||||
### Mediatype
|
||||
Required. Returns one of the defined constants from the below list that matches the mediatype
|
||||
|
||||
| CONST |
|
||||
|-------|
|
||||
|MEDIA_TYPE_MUSIC|
|
||||
|MEDIA_TYPE_TVSHOW|
|
||||
|MEDIA_TYPE_MOVIE|
|
||||
|MEDIA_TYPE_VIDEO|
|
||||
|MEDIA_TYPE_EPISODE|
|
||||
|MEDIA_TYPE_CHANNEL|
|
||||
|MEDIA_TYPE_PLAYLIST|
|
||||
|MEDIA_TYPE_IMAGE|
|
||||
|MEDIA_TYPE_URL|
|
||||
|MEDIA_TYPE_GAME|
|
||||
|MEDIA_TYPE_APP|
|
||||
|
||||
class MyMediaPlayer(MediaPlayerDevice):
|
||||
# Implement the following method.
|
||||
|
||||
def media_content_type(self):
|
||||
"""Content type of current playing media."""
|
||||
|
||||
### Available device classes
|
||||
Optional. What type of media device is this. It will possibly map to google device types.
|
||||
| Value | Description
|
||||
| ----- | -----------
|
||||
| tv | Device is a television type device.
|
||||
| speaker | Device is speakers or stereo type device.
|
71
website/versioned_docs/version-0.104.0/entity_switch.md
Normal file
71
website/versioned_docs/version-0.104.0/entity_switch.md
Normal file
@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Switch Entity
|
||||
sidebar_label: Switch
|
||||
id: version-0.104.0-entity_switch
|
||||
original_id: entity_switch
|
||||
---
|
||||
|
||||
## 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
|
||||
| ---- | ---- | ------- | -----------
|
||||
| is_on | boolean | **Required** | If the switch is currently on or off.
|
||||
| current_power_w | float | `None` | The current power usage in W.
|
||||
| today_energy_kwh | float | `None` | Total energy usage in kWh.
|
||||
| is_standby | boolean | `None` | Indicate if the device connected to the switch is currently in standby.
|
||||
|
||||
## Methods
|
||||
|
||||
### Turn On
|
||||
|
||||
Turn the switch on.
|
||||
|
||||
```python
|
||||
class MySwitch(SwitchDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def turn_on(self, **kwargs) -> None:
|
||||
"""Turn the entity on."""
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn the entity on."""
|
||||
```
|
||||
|
||||
### Turn Off
|
||||
|
||||
Turn the switch off.
|
||||
|
||||
```python
|
||||
class MySwitch(SwitchDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
"""Turn the entity off."""
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn the entity off."""
|
||||
```
|
||||
|
||||
### Toggle
|
||||
|
||||
Optional. If not implemented will default to checking what method to call using the `is_on` property.
|
||||
|
||||
```python
|
||||
class MySwitch(SwitchDevice):
|
||||
# Implement one of these methods.
|
||||
|
||||
def toggle(self, **kwargs):
|
||||
"""Toggle the entity."""
|
||||
|
||||
async def async_toggle(self, **kwargs):
|
||||
"""Toggle the entity."""
|
||||
```
|
||||
|
||||
### Available device classes
|
||||
Optional. What type of device this. It will possibly map to google device types.
|
||||
| Value | Description
|
||||
| ----- | -----------
|
||||
| outlet | Device is an outlet for power.
|
||||
| switch | Device is switch for some type of entity.
|
552
website/versioned_docs/version-0.104.0/external_api_rest.md
Normal file
552
website/versioned_docs/version-0.104.0/external_api_rest.md
Normal file
@ -0,0 +1,552 @@
|
||||
---
|
||||
title: REST API
|
||||
id: version-0.104.0-external_api_rest
|
||||
original_id: external_api_rest
|
||||
---
|
||||
|
||||
Home Assistant provides a RESTful API on the same port as the web frontend. (default port is port 8123).
|
||||
|
||||
If you are not using the [`frontend`](https://www.home-assistant.io/components/frontend/) in your setup then you need to add the [`api` component](https://www.home-assistant.io/components/api/) to your `configuration.yaml` file.
|
||||
|
||||
* http://IP_ADDRESS:8123/ is an interface to control Home Assistant.
|
||||
* http://IP_ADDRESS:8123/api/ is a RESTful API.
|
||||
|
||||
The API accepts and returns only JSON encoded objects.
|
||||
|
||||
All API calls have to be accompanied by the header `Authorization: Bearer ABCDEFGH`, where `ABCDEFGH` is replaced by your token. You can obtain a token ("Long-Lived Access Token") by logging into the frontend using a web browser, and going to [your profile](https://www.home-assistant.io/docs/authentication/#your-account-profile) `http://IP_ADDRESS:8123/profile`.
|
||||
|
||||
There are multiple ways to consume the Home Assistant Rest API. One is with `curl`:
|
||||
|
||||
```shell
|
||||
$ curl -X GET \
|
||||
-H "Authorization: Bearer ABCDEFGH" \
|
||||
-H "Content-Type: application/json" \
|
||||
http://IP_ADDRESS:8123/ENDPOINT
|
||||
```
|
||||
|
||||
Another option is to use Python and the [Requests](http://docs.python-requests.org/en/latest/) module.
|
||||
|
||||
```python
|
||||
from requests import get
|
||||
|
||||
url = "http://localhost:8123/ENDPOINT"
|
||||
headers = {
|
||||
"Authorization": "Bearer ABCDEFGH",
|
||||
"content-type": "application/json",
|
||||
}
|
||||
|
||||
response = get(url, headers=headers)
|
||||
print(response.text)
|
||||
```
|
||||
Another option is to use the Restful Command component https://www.home-assistant.io/components/rest_command/ in a Home Assistant automation or script.
|
||||
|
||||
```yaml
|
||||
turn_light_on:
|
||||
url: http://localhost:8123/api/states/light.study_light
|
||||
method: POST
|
||||
headers:
|
||||
authorization: 'Bearer ABCDEFGH'
|
||||
content-type: 'application/json'
|
||||
payload: '{"state":"on"}'
|
||||
```
|
||||
|
||||
Successful calls will return status code 200 or 201. Other status codes that can return are:
|
||||
|
||||
- 400 (Bad Request)
|
||||
- 401 (Unauthorized)
|
||||
- 404 (Not Found)
|
||||
- 405 (Method not allowed)
|
||||
|
||||
### Actions
|
||||
|
||||
The API supports the following actions:
|
||||
|
||||
#### GET /api/
|
||||
|
||||
Returns a message if the API is up and running.
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "API running."
|
||||
}
|
||||
```
|
||||
|
||||
Sample `curl` command:
|
||||
|
||||
```shell
|
||||
$ curl -X GET -H "Authorization: Bearer ABCDEFGH" \
|
||||
-H "Content-Type: application/json" http://localhost:8123/api/
|
||||
```
|
||||
|
||||
#### GET /api/config
|
||||
|
||||
Returns the current configuration as JSON.
|
||||
|
||||
```json
|
||||
{
|
||||
"components":[
|
||||
"sensor.cpuspeed",
|
||||
"frontend",
|
||||
"config.core",
|
||||
"http",
|
||||
"map",
|
||||
"api",
|
||||
"sun",
|
||||
"config",
|
||||
"discovery",
|
||||
"conversation",
|
||||
"recorder",
|
||||
"group",
|
||||
"sensor",
|
||||
"websocket_api",
|
||||
"automation",
|
||||
"config.automation",
|
||||
"config.customize"
|
||||
],
|
||||
"config_dir":"/home/ha/.homeassistant",
|
||||
"elevation":510,
|
||||
"latitude":45.8781529,
|
||||
"location_name":"Home",
|
||||
"longitude":8.458853651,
|
||||
"time_zone":"Europe/Zurich",
|
||||
"unit_system":{
|
||||
"length":"km",
|
||||
"mass":"g",
|
||||
"temperature":"\u00b0C",
|
||||
"volume":"L"
|
||||
},
|
||||
"version":"0.56.2",
|
||||
"whitelist_external_dirs":[
|
||||
"/home/ha/.homeassistant/www",
|
||||
"/home/ha/.homeassistant/"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Sample `curl` command:
|
||||
|
||||
```shell
|
||||
$ curl -X GET -H "Authorization: Bearer ABCDEFGH" \
|
||||
-H "Content-Type: application/json" http://localhost:8123/api/config
|
||||
```
|
||||
|
||||
#### GET /api/discovery_info
|
||||
|
||||
Returns basic information about the Home Assistant instance as JSON.
|
||||
|
||||
```json
|
||||
{
|
||||
"base_url": "http://192.168.0.2:8123",
|
||||
"location_name": "Home",
|
||||
"requires_api_password": true,
|
||||
"version": "0.56.2"
|
||||
}
|
||||
```
|
||||
|
||||
Sample `curl` command:
|
||||
|
||||
```shell
|
||||
$ curl -X GET -H "Authorization: Bearer ABCDEFGH" \
|
||||
-H "Content-Type: application/json" http://localhost:8123/api/discovery_info
|
||||
```
|
||||
|
||||
#### GET /api/events
|
||||
|
||||
Returns an array of event objects. Each event object contains event name and listener count.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"event": "state_changed",
|
||||
"listener_count": 5
|
||||
},
|
||||
{
|
||||
"event": "time_changed",
|
||||
"listener_count": 2
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Sample `curl` command:
|
||||
|
||||
```shell
|
||||
$ curl -X GET -H "Authorization: Bearer ABCDEFGH" \
|
||||
-H "Content-Type: application/json" http://localhost:8123/api/events
|
||||
```
|
||||
|
||||
#### GET /api/services
|
||||
|
||||
Returns an array of service objects. Each object contains the domain and which services it contains.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"domain": "browser",
|
||||
"services": [
|
||||
"browse_url"
|
||||
]
|
||||
},
|
||||
{
|
||||
"domain": "keyboard",
|
||||
"services": [
|
||||
"volume_up",
|
||||
"volume_down"
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Sample `curl` command:
|
||||
|
||||
```shell
|
||||
$ curl -X GET -H "Authorization: Bearer ABCDEFGH" \
|
||||
-H "Content-Type: application/json" http://localhost:8123/api/services
|
||||
```
|
||||
|
||||
#### GET /api/history/period/<timestamp>
|
||||
|
||||
Returns an array of state changes in the past. Each object contains further details for the entities.
|
||||
|
||||
The `<timestamp>` (`YYYY-MM-DDThh:mm:ssTZD`) is optional and defaults to 1 day before the time of the request. It determines the beginning of the period.
|
||||
|
||||
You can pass the following optional GET parameters:
|
||||
|
||||
- `filter_entity_id=<entity_ids>` to filter on one or more entities - comma separated.
|
||||
- `end_time=<timestamp>` to choose the end of the period in URL encoded format (defaults to 1 day).
|
||||
|
||||
```json
|
||||
[
|
||||
[
|
||||
{
|
||||
"attributes": {
|
||||
"friendly_name": "Weather Temperature",
|
||||
"unit_of_measurement": "\u00b0C"
|
||||
},
|
||||
"entity_id": "sensor.weather_temperature",
|
||||
"last_changed": "2016-02-06T22:15:00+00:00",
|
||||
"last_updated": "2016-02-06T22:15:00+00:00",
|
||||
"state": "-3.9"
|
||||
},
|
||||
{
|
||||
"attributes": {
|
||||
"friendly_name": "Weather Temperature",
|
||||
"unit_of_measurement": "\u00b0C"
|
||||
},
|
||||
"entity_id": "sensor.weather_temperature",
|
||||
"last_changed": "2016-02-06T22:15:00+00:00",
|
||||
"last_updated": "2016-02-06T22:15:00+00:00",
|
||||
"state": "-1.9"
|
||||
},
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
Sample `curl` commands:
|
||||
|
||||
```shell
|
||||
$ curl -X GET -H "Authorization: Bearer ABCDEFGH" \
|
||||
-H "Content-Type: application/json" \
|
||||
http://localhost:8123/api/history/period/2016-12-29T00:00:00+02:00
|
||||
```
|
||||
|
||||
```shell
|
||||
$ curl -X GET -H "Authorization: Bearer ABCDEFGH" \
|
||||
-H "Content-Type: application/json" \
|
||||
http://localhost:8123/api/history/period/2016-12-29T00:00:00+02:00?filter_entity_id=sensor.temperature
|
||||
```
|
||||
|
||||
```shell
|
||||
$ curl -X GET -H "Authorization: Bearer ABCDEFGH" \
|
||||
-H "Content-Type: application/json" \
|
||||
http://localhost:8123/api/history/period/2016-12-29T00:00:00+02:00?end_time=2016-12-31T00%3A00%3A00%2B02%3A00
|
||||
```
|
||||
|
||||
#### GET /api/states
|
||||
|
||||
Returns an array of state objects. Each state has the following attributes: entity_id, state, last_changed and attributes.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"attributes": {},
|
||||
"entity_id": "sun.sun",
|
||||
"last_changed": "2016-05-30T21:43:32.418320+00:00",
|
||||
"state": "below_horizon"
|
||||
},
|
||||
{
|
||||
"attributes": {},
|
||||
"entity_id": "process.Dropbox",
|
||||
"last_changed": "22016-05-30T21:43:32.418320+00:00",
|
||||
"state": "on"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Sample `curl` command:
|
||||
|
||||
```shell
|
||||
$ curl -X GET -H "Authorization: Bearer ABCDEFGH" \
|
||||
-H "Content-Type: application/json" http://localhost:8123/api/states
|
||||
```
|
||||
|
||||
#### GET /api/states/<entity_id>
|
||||
|
||||
Returns a state object for specified entity_id. Returns 404 if not found.
|
||||
|
||||
```json
|
||||
{
|
||||
"attributes":{
|
||||
"azimuth":336.34,
|
||||
"elevation":-17.67,
|
||||
"friendly_name":"Sun",
|
||||
"next_rising":"2016-05-31T03:39:14+00:00",
|
||||
"next_setting":"2016-05-31T19:16:42+00:00"
|
||||
},
|
||||
"entity_id":"sun.sun",
|
||||
"last_changed":"2016-05-30T21:43:29.204838+00:00",
|
||||
"last_updated":"2016-05-30T21:50:30.529465+00:00",
|
||||
"state":"below_horizon"
|
||||
}
|
||||
```
|
||||
|
||||
Sample `curl` command:
|
||||
|
||||
```shell
|
||||
$ curl -X GET -H "Authorization: Bearer ABCDEFGH" \
|
||||
-H "Content-Type: application/json" \
|
||||
http://localhost:8123/api/states/sensor.kitchen_temperature
|
||||
```
|
||||
|
||||
#### GET /api/error_log
|
||||
|
||||
Retrieve all errors logged during the current session of Home Assistant as a plaintext response.
|
||||
|
||||
```text
|
||||
15-12-20 11:02:50 homeassistant.components.recorder: Found unfinished sessions
|
||||
15-12-20 11:03:03 netdisco.ssdp: Error fetching description at http://192.168.1.1:8200/rootDesc.xml
|
||||
15-12-20 11:04:36 homeassistant.components.alexa: Received unknown intent HelpIntent
|
||||
```
|
||||
|
||||
Sample `curl` command:
|
||||
|
||||
```shell
|
||||
$ curl -X GET -H "Authorization: Bearer ABCDEFGH" \
|
||||
-H "Content-Type: application/json" \
|
||||
http://localhost:8123/api/error_log
|
||||
```
|
||||
|
||||
#### GET /api/camera_proxy/camera.<entity_id>
|
||||
|
||||
Returns the data (image) from the specified camera entity_id.
|
||||
|
||||
Sample `curl` command:
|
||||
|
||||
```shell
|
||||
$ curl -X GET -H "Authorization: Bearer ABCDEFGH" \
|
||||
-H "Content-Type: application/json" \
|
||||
http://localhost:8123/api/camera_proxy/camera.my_sample_camera?time=1462653861261 -o image.jpg
|
||||
```
|
||||
|
||||
#### POST /api/states/<entity_id>
|
||||
|
||||
Updates or creates a state. You can create any state that you want, it does not have to be backed by an entity in Home Assistant.
|
||||
|
||||
Expects a JSON object that has at least a state attribute:
|
||||
|
||||
```json
|
||||
{
|
||||
"state": "below_horizon",
|
||||
"attributes": {
|
||||
"next_rising":"2016-05-31T03:39:14+00:00",
|
||||
"next_setting":"2016-05-31T19:16:42+00:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The return code is 200 if the entity existed, 201 if the state of a new entity was set. A location header will be returned with the URL of the new resource. The response body will contain a JSON encoded State object.
|
||||
|
||||
```json
|
||||
{
|
||||
"attributes": {
|
||||
"next_rising":"2016-05-31T03:39:14+00:00",
|
||||
"next_setting":"2016-05-31T19:16:42+00:00"
|
||||
},
|
||||
"entity_id": "sun.sun",
|
||||
"last_changed": "2016-05-30T21:43:29.204838+00:00",
|
||||
"last_updated": "2016-05-30T21:47:30.533530+00:00",
|
||||
"state": "below_horizon"
|
||||
}
|
||||
```
|
||||
|
||||
Sample `curl` command:
|
||||
|
||||
```shell
|
||||
$ curl -X POST -H "Authorization: Bearer ABCDEFGH" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"state": "25", "attributes": {"unit_of_measurement": "°C"}}' \
|
||||
http://localhost:8123/api/states/sensor.kitchen_temperature
|
||||
```
|
||||
|
||||
#### POST /api/events/<event_type>
|
||||
|
||||
Fires an event with event_type
|
||||
|
||||
You can pass an optional JSON object to be used as `event_data`.
|
||||
|
||||
```json
|
||||
{
|
||||
"next_rising":"2016-05-31T03:39:14+00:00",
|
||||
}
|
||||
```
|
||||
|
||||
Returns a message if successful.
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Event download_file fired."
|
||||
}
|
||||
```
|
||||
|
||||
#### POST /api/services/<domain>/<service>
|
||||
|
||||
Calls a service within a specific domain. Will return when the service has been executed or after 10 seconds, whichever comes first.
|
||||
|
||||
You can pass an optional JSON object to be used as `service_data`.
|
||||
|
||||
```json
|
||||
{
|
||||
"entity_id": "light.Ceiling"
|
||||
}
|
||||
```
|
||||
|
||||
Returns a list of states that have changed while the service was being executed.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"attributes": {},
|
||||
"entity_id": "sun.sun",
|
||||
"last_changed": "2016-05-30T21:43:32.418320+00:00",
|
||||
"state": "below_horizon"
|
||||
},
|
||||
{
|
||||
"attributes": {},
|
||||
"entity_id": "process.Dropbox",
|
||||
"last_changed": "22016-05-30T21:43:32.418320+00:00",
|
||||
"state": "on"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Sample `curl` commands:
|
||||
|
||||
Turn the light on:
|
||||
|
||||
```shell
|
||||
$ curl -X POST -H "Authorization: Bearer ABCDEFGH" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"entity_id": "switch.christmas_lights"}' \
|
||||
http://localhost:8123/api/services/switch/turn_on
|
||||
```
|
||||
|
||||
Send a MQTT message:
|
||||
|
||||
```shell
|
||||
$ curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "x-ha-access:YOUR_PASSWORD" \
|
||||
-d '{"payload": "OFF", "topic": "home/fridge", "retain": "True"}' \
|
||||
http://localhost:8123/api/services/mqtt/publish
|
||||
```
|
||||
|
||||
> The result will include any states that changed while the service was being executed, even if their change was the result of something else happening in the system.
|
||||
|
||||
#### POST /api/template
|
||||
|
||||
Render a Home Assistant template. [See template docs for more information.](https://www.home-assistant.io/topics/templating/)
|
||||
|
||||
```json
|
||||
{
|
||||
"template": "Paulus is at {{ states('device_tracker.paulus') }}!"
|
||||
}
|
||||
```
|
||||
|
||||
Returns the rendered template in plain text.
|
||||
|
||||
```text
|
||||
Paulus is at work!
|
||||
```
|
||||
|
||||
Sample `curl` command:
|
||||
|
||||
```shell
|
||||
$ curl -X POST -H "Authorization: Bearer ABCDEFGH" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"template": "It is {{ now() }}!"}' http://localhost:8123/api/template
|
||||
```
|
||||
|
||||
#### POST /api/config/core/check_config
|
||||
|
||||
Trigger a check of `configuration.yaml`. No additional data needs to be passed in with this request.
|
||||
|
||||
If the check is successful, the following will be returned:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"errors": null,
|
||||
"result": "valid"
|
||||
}
|
||||
```
|
||||
|
||||
If the check fails, the errors attribute in the object will list what caused the check to fail. For example:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"errors": "Integration not found: frontend:",
|
||||
"result": "invalid"
|
||||
}
|
||||
```
|
||||
|
||||
#### POST /api/event_forwarding
|
||||
|
||||
Set up event forwarding to another Home Assistant instance.
|
||||
|
||||
Requires a JSON object that represents the API to forward to.
|
||||
|
||||
```javascript
|
||||
{
|
||||
"host": "machine",
|
||||
"api_password": "my_super_secret_password",
|
||||
"port": 8880 // optional
|
||||
}
|
||||
```
|
||||
|
||||
It will return a message if event forwarding was set up successfully.
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Event forwarding setup."
|
||||
}
|
||||
```
|
||||
|
||||
#### DELETE /api/event_forwarding
|
||||
|
||||
Cancel event forwarding to another Home Assistant instance.<br>
|
||||
|
||||
Requires a JSON object that represents the API to cancel forwarding to.
|
||||
|
||||
```javascript
|
||||
{
|
||||
"host": "machine",
|
||||
"api_password": "my_super_secret_password",
|
||||
"port": 8880 // optional
|
||||
}
|
||||
```
|
||||
|
||||
It will return a message if event forwarding was canceled successfully.
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Event forwarding cancelled."
|
||||
}
|
||||
```
|
@ -0,0 +1,74 @@
|
||||
---
|
||||
title: Server-sent events
|
||||
id: version-0.104.0-external_api_server_sent_events
|
||||
original_id: external_api_server_sent_events
|
||||
---
|
||||
|
||||
The [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) feature is a one-way channel from your Home Assistant server to a client which is acting as a consumer. For a bi-directional streaming API, check out the [WebSocket API](external_api_websocket.md).
|
||||
|
||||
The URI that is generating the data is `/api/stream`.
|
||||
|
||||
A requirement on the client-side is existing support for the [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) interface.
|
||||
|
||||
There are various ways to access the stream. If you have not set an `api_password` in the [`http`](https://www.home-assistant.io/components/http/) section of your `configuration.yaml` file then you use your modern browser to read the messages. A command-line option is `curl`:
|
||||
|
||||
```shell
|
||||
$ curl -X GET -H 'Authorization: Bearer ABCDEFGH' \
|
||||
-H "Content-Type: application/json" http://localhost:8123/api/stream
|
||||
```
|
||||
|
||||
> Will no longer work with the new Authentication system.
|
||||
|
||||
You can create a convenient view for this by creating an HTML file (`sse.html`) in the `www` folder of your Home Assistant configuration directory (`.homeassistant`). Paste this snippet into the file:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Getting Home Assistant server events</h1>
|
||||
<div id="events"></div>
|
||||
<script type="text/javascript">
|
||||
var source = new EventSource("/api/stream?api_password=YOUR_PASSWORD");
|
||||
source.onmessage = function(event) {
|
||||
document.getElementById("events").innerHTML += event.data + "<br>";
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Visit [http://localhost:8123/local/sse.html](http://localhost:8123/local/sse.html) to see the stream of events.
|
||||
|
||||
## Examples
|
||||
|
||||
A simple way to consume server-sent events is to use a command-line http client like [httpie](https://httpie.org/). Installation info is on the site (if you use Homebrew, it's `brew install httpie`). Once installed, run this snippet from your terminal:
|
||||
|
||||
```shell
|
||||
$ http --stream http://localhost:8123/api/stream 'Authorization:Bearer ABCDEFGH' content-type:application/json
|
||||
```
|
||||
|
||||
### Website
|
||||
|
||||
> Will no longer work with the new Authentication system.
|
||||
|
||||
The [home-assistant-sse](https://github.com/fabaff/home-assistant-sse) repository contains a more advanced example.
|
||||
|
||||
### Python
|
||||
|
||||
If you want to test the server-sent events without creating a website, the Python module [`sseclient` ](https://pypi.python.org/pypi/sseclient/) can help. To install (assuming Python and pip3 are already installed):
|
||||
|
||||
```shell
|
||||
$ pip3 install sseclient
|
||||
```
|
||||
|
||||
A simple script to consume SSE in Python looks like this:
|
||||
|
||||
```python
|
||||
from sseclient import SSEClient
|
||||
|
||||
auth = {"Authorization": "Bearer ABCDEFGH"}
|
||||
messages = SSEClient("http://localhost:8123/api/stream", headers=auth)
|
||||
|
||||
for msg in messages:
|
||||
print(msg)
|
||||
```
|
@ -0,0 +1,145 @@
|
||||
---
|
||||
title: Extending the WebSocket API
|
||||
id: version-0.104.0-frontend_add_websocket_api
|
||||
original_id: frontend_add_websocket_api
|
||||
---
|
||||
|
||||
As a component you might have information that you want to make available to the frontend. For example, the media player will want to make album covers available for the frontend to show. Our frontend is communicating with the backend over the websocket API, which can be extended with custom commands.
|
||||
|
||||
## Registering a command (Python)
|
||||
|
||||
To register a command, you need to have a message type, a message schema and a message handler. Your component does not have to add the websocket API as a dependency. You register your command, and if the user is using the websocket API, the command will be made available.
|
||||
|
||||
### Message Types
|
||||
|
||||
Message types are made up the domain and the message type, separated by a forward slash. In the below example, we're defining `media_player/thumbnail`.
|
||||
|
||||
```python
|
||||
# The type of the message
|
||||
WS_TYPE_MEDIA_PLAYER_THUMBNAIL = "media_player/thumbnail"
|
||||
```
|
||||
|
||||
### Message Schema
|
||||
|
||||
The message schema defines what type of data we expect when the message is invoked. It is defined as a voluptuous schema and has to extend the base web socket command schema.
|
||||
|
||||
```python
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import websocket_api
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
||||
# The schema for the message
|
||||
SCHEMA_WEBSOCKET_GET_THUMBNAIL = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
|
||||
{
|
||||
"type": WS_TYPE_MEDIA_PLAYER_THUMBNAIL,
|
||||
# The entity that we want to retrieve the thumbnail for.
|
||||
"entity_id": cv.entity_id,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Defining a handler
|
||||
|
||||
Message handlers are callback functions that are run inside the event loop. If you want to do I/O or have to wait for your result, create a new function and queue it up using `hass.async_add_job`. This is done so that the websocket API can get back to handling the next message as soon as possible.
|
||||
|
||||
#### Sending a direct response
|
||||
|
||||
If you are defining a command that is querying simple information, you might be able to fulfill the request while the handler is being called by the websocket API. To do this, use `connection.to_write.put_nowait`.
|
||||
|
||||
```python
|
||||
@callback
|
||||
def websocket_handle_thumbnail(hass, connection, msg):
|
||||
"""Handle getting a thumbnail."""
|
||||
|
||||
# We know the answer without having to fetch any information,
|
||||
# so we send it directly.
|
||||
connection.to_write.put_nowait(
|
||||
websocket_api.result_message(
|
||||
msg["id"], {"thumbnail": "http://via.placeholder.com/350x150"}
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
#### Sending a delayed response
|
||||
|
||||
If your command needs to interact with the network, a device or needs to compute information, you will need to queue a job to do the work and send the response. To do this, use `connection.send_message_outside`.
|
||||
|
||||
```python
|
||||
@callback
|
||||
def websocket_handle_thumbnail(hass, connection, msg):
|
||||
"""Handle get media player cover command."""
|
||||
# Retrieve media player using passed in entity id.
|
||||
player = hass.data[DOMAIN].get_entity(msg["entity_id"])
|
||||
|
||||
# If the player does not exist, send an error message.
|
||||
if player is None:
|
||||
connection.to_write.put_nowait(
|
||||
websocket_api.error_message(
|
||||
msg["id"], "entity_not_found", "Entity not found"
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
# Define a function to be enqueued.
|
||||
async def send_image():
|
||||
"""Send image."""
|
||||
data, content_type = await player.async_get_media_image()
|
||||
|
||||
# No media player thumbnail available
|
||||
if data is None:
|
||||
connection.send_message_outside(
|
||||
websocket_api.error_message(
|
||||
msg["id"], "thumbnail_fetch_failed", "Failed to fetch thumbnail"
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
connection.send_message_outside(
|
||||
websocket_api.result_message(
|
||||
msg["id"],
|
||||
{
|
||||
"content_type": content_type,
|
||||
"content": base64.b64encode(data).decode("utf-8"),
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
# Player exist. Queue up a job to send the thumbnail.
|
||||
hass.async_add_job(send_image())
|
||||
```
|
||||
|
||||
### Registering with the Websocket API
|
||||
|
||||
With all pieces defined, it's time to register the command. This is done inside your setup method.
|
||||
|
||||
```python
|
||||
async def async_setup(hass, config):
|
||||
"""Setup of your component."""
|
||||
hass.components.websocket_api.async_register_command(
|
||||
WS_TYPE_MEDIA_PLAYER_THUMBNAIL,
|
||||
websocket_handle_thumbnail,
|
||||
SCHEMA_WEBSOCKET_GET_THUMBNAIL,
|
||||
)
|
||||
```
|
||||
|
||||
## Calling the command from the frontend (JavaScript)
|
||||
|
||||
With your command defined, it's time to call it from the frontend! This is done using JavaScript. You will need access to the `hass` object which holds the WebSocket connection to the backend. Then just call `hass.connection.sendMessagePromise`. This will return a promise that will resolve if the command succeeds and errors if the command fails.
|
||||
|
||||
```js
|
||||
hass.connection.sendMessagePromise({
|
||||
type: 'media_player/thumbnail',
|
||||
entity_id: 'media_player.living_room_tv',
|
||||
}).then(
|
||||
(resp) => {
|
||||
console.log('Message success!', resp.result);
|
||||
},
|
||||
(err) => {
|
||||
console.error('Message failed!', err);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
If your command is not sending a response, you can use `hass.connection.sendMessage`.
|
@ -0,0 +1,122 @@
|
||||
---
|
||||
title: Creating custom UI
|
||||
id: version-0.104.0-frontend_creating_custom_ui
|
||||
original_id: frontend_creating_custom_ui
|
||||
---
|
||||
|
||||
> This feature has been deprecated and is no longer supported. To add custom UI to Home Assistant, build a [custom Lovelace card](lovelace_custom_card.md) instead.
|
||||
|
||||
### State card
|
||||
|
||||
If you would like to use your own [State card](frontend_add_card.md) without merging your code into [home-assistant-polymer](https://github.com/home-assistant/home-assistant-polymer/) you can create your own implementation.
|
||||
|
||||
Put the element source file and its dependencies in `www/custom_ui/` directory under your Home Assistant [configuration](https://www.home-assistant.io/docs/configuration/) directory.
|
||||
|
||||
For example if creating a state card for the `light` domain named `state-card-my-custom-light` put `state-card-my-custom-light.html` in `www/custom_ui/`.
|
||||
|
||||
That file should implement `<state-card-my-custom-light>` tag with Polymer.
|
||||
|
||||
In `state-card-my-custom-light.html` you should use `<link rel="import">` to import all the dependencies **not** used by Home Assistant's UI.
|
||||
Do not import any dependencies used by the Home Assistant UI.
|
||||
Importing those will work in `development: 1` mode, but will fail in production mode.
|
||||
|
||||
1. In the `customize:` section of the `configuration.yaml` file put `custom_ui_state_card: state-card-my-custom-light`.
|
||||
2. In the `frontend` section use `extra_html_url` to specify the URL to load.
|
||||
|
||||
Example:
|
||||
|
||||
`configuration.yaml`:
|
||||
|
||||
```yaml
|
||||
homeassistant:
|
||||
customize:
|
||||
light.bedroom:
|
||||
custom_ui_state_card: state-card-my-custom-light
|
||||
|
||||
frontend:
|
||||
extra_html_url:
|
||||
- /local/custom_ui/state-card-my-custom-light.html
|
||||
```
|
||||
|
||||
`www/custom_ui/state-card-my-custom-light.html`:
|
||||
|
||||
```html
|
||||
<script>
|
||||
{
|
||||
// show the version of your custom UI in the HA dev info panel (HA 0.66.0+):
|
||||
const _NAME = 'My custom light';
|
||||
const _URL = 'https://home-assistant.io/developers/frontend_creating_custom_ui/';
|
||||
const _VERSION = '20180312';
|
||||
|
||||
if (!window.CUSTOM_UI_LIST) window.CUSTOM_UI_LIST = [];
|
||||
window.CUSTOM_UI_LIST.push({
|
||||
name: _NAME,
|
||||
url: _URL,
|
||||
version: _VERSION
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<dom-module id='state-card-my-custom-light'>
|
||||
<template>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<textarea>[[_toStr(stateObj)]]</textarea>
|
||||
</template>
|
||||
</dom-module>
|
||||
|
||||
<script>
|
||||
class StateCardMyCustomLight extends Polymer.Element {
|
||||
static get is() { return 'state-card-my-custom-light'; }
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
// Home Assistant object
|
||||
hass: Object,
|
||||
// inDialog is true if shown as more-info-card
|
||||
inDialog: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
// includes state, config and more information of the entity
|
||||
stateObj: Object,
|
||||
};
|
||||
}
|
||||
|
||||
_toStr(obj) {
|
||||
return JSON.stringify(obj, null, 2);
|
||||
}
|
||||
}
|
||||
customElements.define(StateCardMyCustomLight.is, StateCardMyCustomLight);
|
||||
</script>
|
||||
```
|
||||
|
||||
> Some browsers don't support latest ECMAScript standards, these require a separate ES5 compatible file (`extra_html_url_es5`).
|
||||
|
||||
For more possibilities, see the [Custom UI section](https://www.home-assistant.io/cookbook/#user-interface) on our Examples page.
|
||||
|
||||
### More info dialog
|
||||
|
||||
_Introduced in Home Assistant 0.69._
|
||||
|
||||
Similar to the custom State card, if you would like to use your own [More info dialog](frontend_add_more_info.md) you can create your own implementation.
|
||||
|
||||
Following a similar example, if creating a more info dialog a light named `more-info-my-custom-light` put `more-info-my-custom-light.html` in `www/custom_ui/`.
|
||||
|
||||
1. In the `customize:` section of the `configuration.yaml` file put `custom_ui_more_info: more-info-my-custom-light`.
|
||||
2. In the `frontend` section use `extra_html_url` to specify the URL to load.
|
||||
|
||||
Example:
|
||||
|
||||
`configuration.yaml`:
|
||||
|
||||
```yaml
|
||||
homeassistant:
|
||||
customize:
|
||||
light.bedroom:
|
||||
custom_ui_more_info: more-info-my-custom-light
|
||||
|
||||
frontend:
|
||||
extra_html_url:
|
||||
- /local/custom_ui/more-info-my-custom-light.html
|
||||
```
|
105
website/versioned_docs/version-0.104.0/frontend_development.md
Normal file
105
website/versioned_docs/version-0.104.0/frontend_development.md
Normal file
@ -0,0 +1,105 @@
|
||||
---
|
||||
title: Frontend development
|
||||
sidebar_label: Development
|
||||
id: version-0.104.0-frontend_development
|
||||
original_id: frontend_development
|
||||
---
|
||||
|
||||
The Home Assistant frontend is built using web components. For more background about our technology choices, [see this blog post](https://developers.home-assistant.io/blog/2019/05/22/internet-of-things-and-the-modern-web.html).
|
||||
|
||||
> Do not use development mode in production. Home Assistant uses aggressive caching to improve the mobile experience. This is disabled during development so that you do not have to restart the server in between changes.
|
||||
|
||||
## Setting up the environment
|
||||
|
||||
### Getting the code
|
||||
|
||||
First step is to fork the [home-assistant-polymer repository][hass-polymer] and add the upstream remote. You can place the forked repository anywhere on your system.
|
||||
|
||||
```shell
|
||||
$ git clone git@github.com:YOUR_GIT_USERNAME/home-assistant-polymer.git
|
||||
$ cd home-assistant-polymer
|
||||
$ git remote add upstream https://github.com/home-assistant/home-assistant-polymer.git
|
||||
```
|
||||
|
||||
### Configuring Home Assistant
|
||||
|
||||
You will need to have an instance of Home Assistant set up. See our guide on [setting up a development environment](https://developers.home-assistant.io/docs/en/development_environment.html).
|
||||
|
||||
Next step is to configure Home Assistant to use the development mode for the frontend. Do this by updating the frontend config in your `configuration.yaml` and set the path to the home-assistant-polymer repository that you cloned in the last step:
|
||||
|
||||
```yaml
|
||||
frontend:
|
||||
# Example absolute path: /home/paulus/dev/hass/home-assistant-polymer
|
||||
development_repo: <absolute path to home-assistant-polymer repo>
|
||||
```
|
||||
|
||||
### Installing Node.js
|
||||
|
||||
Node.js is required to build the frontend. The preferred method of installing node.js is with [nvm](https://github.com/creationix/nvm). Install nvm using the instructions in the [README](https://github.com/creationix/nvm#install-script), and install the correct node.js by running the following command:
|
||||
|
||||
```shell
|
||||
$ nvm install
|
||||
```
|
||||
|
||||
[Yarn](https://yarnpkg.com/en/) is used as the package manager for node modules. [Install yarn using the instructions here.](https://yarnpkg.com/en/docs/install)
|
||||
|
||||
Next, development dependencies need to be installed to bootstrap the frontend development environment. First activate the right Node version and then download all the dependencies:
|
||||
|
||||
```shell
|
||||
$ nvm use
|
||||
$ script/bootstrap
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
During development, you will need to run the development script to maintain a development build of the frontend that auto updates when you change any of the source files. To run this server, run:
|
||||
|
||||
```shell
|
||||
$ nvm use
|
||||
$ script/develop
|
||||
```
|
||||
|
||||
Make sure you have cache disabled and correct settings to avoid stale content:
|
||||
|
||||
> Instructions are for Google Chrome
|
||||
|
||||
1. Disable cache by ticking the box in `Network` > `Disable cache`
|
||||
|
||||
<p class='img'>
|
||||
<img src='/img/en/development/disable-cache.png' />
|
||||
</p>
|
||||
|
||||
2. Enable Bypass for network in `Application` > `Service Workers` > `Bypass for network`
|
||||
|
||||
<p class='img'>
|
||||
<img src='/img/en/development/bypass-for-network.png' />
|
||||
</p>
|
||||
|
||||
## Creating pull requests
|
||||
|
||||
If you're planning on issuing a PR back to the Home Assistant codebase you need to fork the polymer project and add your fork as a remote to the Home Assistant Polymer repo.
|
||||
|
||||
```shell
|
||||
$ git remote add fork <github URL to your fork>
|
||||
```
|
||||
|
||||
When you've made your changes and are ready to push them change to the working directory for the polymer project and then push your changes
|
||||
|
||||
``` bash
|
||||
$ git add -A
|
||||
$ git commit -m "Added new feature X"
|
||||
$ git push -u fork HEAD
|
||||
```
|
||||
|
||||
## Building the Polymer frontend
|
||||
|
||||
If you're making changes to the way the frontend is packaged, it might be necessary to try out a new packaged build of the frontend in the main repository (instead of pointing it at the frontend repo). To do so, first build a production version of the frontend by running `script/build_frontend`.
|
||||
|
||||
To test it out inside Home Assistant, run the following command from the main Home Assistant repository:
|
||||
|
||||
```shell
|
||||
$ pip3 install -e /path/to/home-assistant-polymer/
|
||||
$ hass --skip-pip
|
||||
```
|
||||
|
||||
[hass-polymer]: https://github.com/home-assistant/home-assistant-polymer
|
@ -0,0 +1,72 @@
|
||||
---
|
||||
title: External Authentication
|
||||
id: version-0.104.0-frontend_external_auth
|
||||
original_id: frontend_external_auth
|
||||
---
|
||||
|
||||
By default, the frontend will take care of its own authentication tokens. If none found, it will redirect the user to the login page and it will take care that the token is up to date.
|
||||
|
||||
If you want to embed the Home Assistant frontend in an external app, you will want to store the authentication inside the app but make it available to the frontend. To support this, Home Assistant exposes an external authentication API.
|
||||
|
||||
To activate this API, load the frontend with `?external_auth=1` appended to the URL. If this is passed in, Home Assistant will expect either `window.externalApp` (for Android) or `window.webkit.messageHandlers` (for iOS) to be defined containing the methods described below.
|
||||
|
||||
## Get Access Token
|
||||
|
||||
_This API has been introduced in Home Assistant 0.78._
|
||||
|
||||
When the frontend loads, it will request an access token from the external authentication. It does so by calling one of the following methods with an options object. The options object defines the callback method to be called with the response and an optional `force` boolean which is set to `true` if the access token should be refreshed, regardless if it has expired or not.
|
||||
|
||||
The `force` boolean has been introduced in Home Assistant 0.104 and might not always be available.
|
||||
|
||||
```js
|
||||
window.externalApp.getExternalAuth({
|
||||
callback: "externalAuthSetToken",
|
||||
force: true
|
||||
});
|
||||
// or
|
||||
window.webkit.messageHandlers.getExternalAuth.postMessage({
|
||||
callback: "externalAuthSetToken",
|
||||
force: true
|
||||
});
|
||||
```
|
||||
|
||||
The response should contain a boolean if it was successful and an object containing an access token and the number of seconds that it will remain valid. Pass the response to the function defined in the options object.
|
||||
|
||||
```js
|
||||
// To be called by external app
|
||||
window.externalAuthSetToken(true, {
|
||||
access_token: "qwere",
|
||||
expires_in: 1800
|
||||
});
|
||||
|
||||
// If unable to get new access token
|
||||
window.externalAuthSetToken(false);
|
||||
```
|
||||
|
||||
The frontend will call this method when the page first loads and whenever it needs a valid token but the previous received token has expired.
|
||||
|
||||
## Revoke Token
|
||||
|
||||
_This API has been introduced in Home Assistant 0.78._
|
||||
|
||||
When the user presses the logout button on the profile page, the external app will have to [revoke the refresh token](auth_api.md#revoking-a-refresh-token), and log the user out.
|
||||
|
||||
```js
|
||||
window.externalApp.revokeExternalAuth({
|
||||
callback: "externalAuthRevokeToken"
|
||||
});
|
||||
// or
|
||||
window.webkit.messageHandlers.revokeExternalAuth.postMessage({
|
||||
callback: "externalAuthRevokeToken"
|
||||
});
|
||||
```
|
||||
|
||||
When done, the external app has to call the function defined in the options object.
|
||||
|
||||
```js
|
||||
// To be called by external app
|
||||
window.externalAuthRevokeToken(true);
|
||||
|
||||
// If unable to logout
|
||||
window.externalAuthRevokeToken(false);
|
||||
```
|
232
website/versioned_docs/version-0.104.0/hassio_addon_config.md
Normal file
232
website/versioned_docs/version-0.104.0/hassio_addon_config.md
Normal file
@ -0,0 +1,232 @@
|
||||
---
|
||||
title: Add-On Configuration
|
||||
id: version-0.104.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.
|
||||
|
||||
```shell
|
||||
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.
|
||||
|
||||
```dockerfile
|
||||
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 | string | 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,60 @@
|
||||
---
|
||||
title: Publishing your add-on
|
||||
id: version-0.104.0-hassio_addon_publishing
|
||||
original_id: hassio_addon_publishing
|
||||
---
|
||||
|
||||
There are two different ways of publishing add-ons. One is to publish pre-build containers to Docker Hub and the other option is to have users build the containers locally on their Hass.io instance.
|
||||
|
||||
#### Pre-build containers
|
||||
|
||||
With pre-build containers, the developer is responsible for building the images for each architecture on their machine and push the results out to Docker Hub. This has a lot of advantages for the user. As a user it will only have to download the final container and be up and running once the download finishes. This makes the installation process fast and almost no chance of failure. This is the preferred method.
|
||||
|
||||
We have automated the process of building and publishing add-ons. See below for the instructions.
|
||||
|
||||
#### Locally build containers
|
||||
|
||||
Starting Hass.io 0.26, it is possible to distribute add-ons that will be built on the users machine. The advantage is that as a developer it is easy to test an idea and see if people are interested in your add-ons. This method includes installing and potentially compiling code. This means that installing such an add-on is slow and adds more wear and tear to users SD card/hard drive than the above mentioned pre-build solution. It also has a higher chance of failure if one of the dependencies of the container has changed or is no longer available.
|
||||
|
||||
Use this option when you are playing with add-ons and seeing if someone is interested in your work. Once you're an established repository, please migrate to pushing builds to Docker Hub as it greatly improves the user experience. In the future we will mark locally built add-ons in the add-on store to warn users.
|
||||
|
||||
## Build scripts to publish add-ons to Docker Hub
|
||||
|
||||
All add-ons are simple docker containers. Inside your add-on `config.json` you specify the Docker image that will be installed for your add-on:
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"image": "myhub/image-{arch}-addon-name",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
You can use `{arch}` inside the image name to support multiple architectures with one (1) configuration file. It will be replaced with the architecture of the user when we load the image. If you use `Buildargs` you can use the `build.json` to overwrite our default args.
|
||||
|
||||
Hass.io assumes that the `master` branch of your add-on repository matches the latest tag on Docker Hub. When you're building a new version, it's suggested that you use another branch, ie `build` or do it with a PR on GitHub. After you push the add-on to [Docker Hub](https://hub.docker.com/), you can merge this branch to master.
|
||||
|
||||
## Custom Add-ons
|
||||
|
||||
You need a Docker Hub account to make your own add-ons. You can build your Docker images with the Docker `build` command or use our script that make it simple. Pull our [Builder Docker engine][builder] and run one of the following commands.
|
||||
|
||||
For a git repository:
|
||||
|
||||
```shell
|
||||
$ docker run --rm --privileged -v \
|
||||
~/.docker:/root/.docker homeassistant/amd64-builder \
|
||||
--all -t addon-folder -r https://github.com/xy/addons \
|
||||
-b branchname
|
||||
```
|
||||
|
||||
For a local repository:
|
||||
|
||||
```shell
|
||||
$ docker run --rm --privileged -v \
|
||||
~/.docker:/root/.docker -v /my_addon:/data homeassistant/amd64-builder \
|
||||
--all -t /data
|
||||
```
|
||||
|
||||
> If you are developing on macOS and using Docker for Mac, you may encounter an error message similar to the following: <code>error creating aufs mount to /var/lib/docker/aufs/mnt/<SOME_ID>-init: invalid argument</code>. A proposed workaround is to add the following to the Advanced Daemon JSON configuration via Docker > Preferences > Daemon > Advanced: <code>"storage-driver" : "aufs"</code> or map the docker socket into container.
|
||||
|
||||
[builder]: https://github.com/home-assistant/hassio-builder
|
182
website/versioned_docs/version-0.104.0/hassio_addon_tutorial.md
Normal file
182
website/versioned_docs/version-0.104.0/hassio_addon_tutorial.md
Normal file
@ -0,0 +1,182 @@
|
||||
---
|
||||
title: Tutorial: Making your first add-on
|
||||
id: version-0.104.0-hassio_addon_tutorial
|
||||
original_id: hassio_addon_tutorial
|
||||
---
|
||||
|
||||
So you've got Home Assistant going and you've been enjoying the built-in add-ons but you're missing this one application. Time to make your own add-on! In Hass.io 0.24 we introduced the option to have local add-ons be build on your device. This is great for developing new add-ons locally.
|
||||
|
||||
To get started with developing add-ons, we first need access to where Hass.io looks for local add-ons. For this you can use the Samba add-on or the SSH add-on.
|
||||
|
||||
For Samba, once you have enabled and started it, your Hass.io instance will show up in your local network tab and share a folder called "addons". This is the folder to store your custom add-ons.
|
||||
|
||||
If you are on macOS and the folder is not showing up automatically, go to Finder and press CMD+K then enter 'smb://hassio.local'
|
||||
|
||||

|
||||
|
||||
For SSH, you will have to install it. Before you can start it, you will have to have a private/public key pair and store your public key in the add-on config ([see docs for more info][ssh]). Once started, you can SSH to Hass.io and store your custom add-ons in "/addons".
|
||||
|
||||

|
||||
|
||||
Once you have located your add-on directory, it's time to get started!
|
||||
|
||||
[ssh]: https://www.home-assistant.io/addons/ssh/
|
||||
|
||||
## Step 1: The basics
|
||||
|
||||
- Create a new directory called `hello_world`
|
||||
- Inside that directory create three files.
|
||||
|
||||
`Dockerfile`:
|
||||
```dockerfile
|
||||
ARG BUILD_FROM
|
||||
FROM $BUILD_FROM
|
||||
|
||||
ENV LANG C.UTF-8
|
||||
|
||||
# Copy data for add-on
|
||||
COPY run.sh /
|
||||
RUN chmod a+x /run.sh
|
||||
|
||||
CMD [ "/run.sh" ]
|
||||
```
|
||||
|
||||
`config.json`:
|
||||
```json
|
||||
{
|
||||
"name": "Hello world",
|
||||
"version": "1",
|
||||
"slug": "hello_world",
|
||||
"description": "My first real add-on!",
|
||||
"arch": ["armhf", "armv7", "aarch64", "amd64", "i386"],
|
||||
"startup": "before",
|
||||
"boot": "auto",
|
||||
"options": {},
|
||||
"schema": {}
|
||||
}
|
||||
```
|
||||
|
||||
`run.sh`:
|
||||
```shell
|
||||
echo Hello world!
|
||||
```
|
||||
Make sure your editor is using UNIX-like line breaks (LF), not Dos/Windows (CRLF).
|
||||
|
||||
## Step 2: Installing and testing your add-on
|
||||
|
||||
Now comes the fun part, time to open the Hass.io UI and install and run your add-on.
|
||||
|
||||
- Open the Home Assistant frontend
|
||||
- Go to the Hass.io panel
|
||||
- On the top right click the shopping basket to go to the add-on store.
|
||||
|
||||

|
||||
|
||||
- On the top right click the refresh button
|
||||
- You should now see a new card called "Local" that lists your add-on!
|
||||
|
||||

|
||||
|
||||
- Click on your add-on to go to the add-on details page.
|
||||
- Install your add-on
|
||||
- Start your add-on
|
||||
- Refresh the logs of your add-on, you should now see "Hello world!" in your logs.
|
||||
|
||||

|
||||
|
||||
### I don't see my add-on?!
|
||||
|
||||
Oops! You clicked refresh in the store and your add-on didn't show up. Or maybe you just updated an option, clicked refresh and saw your add-on disappear.
|
||||
|
||||
When this happens, it means that your `config.json` is invalid. It's either invalid JSON or one of the specified options is incorrect. To see what went wrong, go to the Hass.io panel and in the supervisor card click on "View logs". This should bring you to a page with the logs of the supervisor. Scroll to the bottom and you should be able to find the validation error.
|
||||
|
||||
Once you fixed the error, go to the add-on store and click refresh again.
|
||||
|
||||
## Step 3: Hosting a server
|
||||
|
||||
Until now we've been able to do some basic stuff, but it's not very useful yet. So let's take it one step further and host a server that we expose on a port. For this we're going to use the built-in HTTP server that comes with Python 3.
|
||||
|
||||
To do this, we will need to update our files as follows:
|
||||
|
||||
- `Dockerfile`: Install Python 3
|
||||
- `config.json`: Make the port from the container available on the host
|
||||
- `run.sh`: Run the Python 3 command to start the HTTP server
|
||||
|
||||
Add to your `Dockerfile` before `RUN`:
|
||||
|
||||
```dockerfile
|
||||
# Install requirements for add-on
|
||||
RUN apk add --no-cache python3
|
||||
|
||||
# Python 3 HTTP Server serves the current working dir
|
||||
# So let's set it to our add-on persistent data directory.
|
||||
WORKDIR /data
|
||||
```
|
||||
|
||||
Add "ports" to `config.json`. This will make TCP on port 8000 inside the container available on the host on port 8000.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Hello world",
|
||||
"version": "0.2",
|
||||
"slug": "hello_world",
|
||||
"description": "My first real add-on!",
|
||||
"arch": ["armhf", "armv7", "aarch64", "amd64", "i386"],
|
||||
"startup": "before",
|
||||
"boot": "auto",
|
||||
"options": {},
|
||||
"schema": {},
|
||||
"ports": {
|
||||
"8000/tcp": 8000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Update `run.sh` to start the Python 3 server:
|
||||
|
||||
```shell
|
||||
python3 -m http.server 8000
|
||||
```
|
||||
|
||||
## Step 4: Installing the update
|
||||
|
||||
Since we updated the version number in our `config.json`, Home Assistant will show an update button when looking at the add-on details. You might have to refresh your browser or click the refresh button in the add-on store for it to show up. If you did not update the version number, you can also uninstall and install the add-on again. After installing the add-on again, make sure you start it.
|
||||
|
||||
Now navigate to [http://hassio.local:8000](http://hassio.local:8000) to see our server in action!
|
||||
|
||||

|
||||
|
||||
## Bonus: Working with add-on options
|
||||
|
||||
In the screenshot you've probably seen that our server only served up 1 file: `options.json`. This file contains the user configuration for this add-on. Because we specified an empty "config" and "schema" in our `config.json`, the file is currently empty.
|
||||
|
||||
Let's see if we can get some data into that file!
|
||||
|
||||
To do this, we need to specify the default options and a schema for the user to change the options.
|
||||
|
||||
Change the options and schema entries in your `config.json` with the following:
|
||||
|
||||
```json
|
||||
{
|
||||
…
|
||||
|
||||
"options": {
|
||||
"beer": true,
|
||||
"wine": true,
|
||||
"liquor": false,
|
||||
"name": "world",
|
||||
"year": 2017
|
||||
},
|
||||
"schema": {
|
||||
"beer": "bool",
|
||||
"wine": "bool",
|
||||
"liquor": "bool",
|
||||
"name": "str",
|
||||
"year": "int"
|
||||
},
|
||||
|
||||
…
|
||||
}
|
||||
```
|
||||
|
||||
Refresh the add-on store and re-install your add-on. You will now see the options available in the add-on config screen. When you now go back to our Python 3 server and download `options.json`, you'll see the options you set. [Example of how options.json can be used inside `run.sh`](https://github.com/home-assistant/hassio-addons/blob/master/mosquitto/data/run.sh#L4-L5)
|
91
website/versioned_docs/version-0.104.0/hassio_debugging.md
Normal file
91
website/versioned_docs/version-0.104.0/hassio_debugging.md
Normal file
@ -0,0 +1,91 @@
|
||||
---
|
||||
title: Debugging Hass.io
|
||||
id: version-0.104.0-hassio_debugging
|
||||
original_id: hassio_debugging
|
||||
---
|
||||
|
||||
> This section is not for end users. End users should use the [SSH add-on] to SSH into Hass.io. This is for <b>developers</b> of Hass.io. Do not ask for support if you are using these options.
|
||||
|
||||
[SSH add-on]: https://www.home-assistant.io/addons/ssh/
|
||||
|
||||
The following debug tips and tricks are for developers who are running the Hass.io image and are working on the base image. If you use the generic Linux installer script, you should be able to access your host and logs as per your host.
|
||||
|
||||
## Debug Supervisor
|
||||
|
||||
Visual Studio Code config:
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Hass.io remote debug",
|
||||
"type": "python",
|
||||
"request": "attach",
|
||||
"port": 33333,
|
||||
"host": "IP",
|
||||
"pathMappings": [
|
||||
{
|
||||
"localRoot": "${workspaceFolder}",
|
||||
"remoteRoot": "/usr/src/hassio"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You need set the dev mode on supervisor and enable debug with options. You need also install the Remote debug Add-on from Developer Repository to expose the debug port to Host.
|
||||
|
||||
## SSH access to the host
|
||||
> SSH access through the [SSH add-on] (which will give you SSH access through port 22) will not provide you with all the necessary privileges, and you will be asked for a username and password when typing the 'login' command. You need to follow the steps below, which will setup a separate SSH access through port 22222 with all necessary privileges.
|
||||
|
||||
### resinOS based Hass.io (deprecated)
|
||||
Create an `authorized_keys` file containing your public key, and place it in the root of the boot partition of your SD card. See [Generating SSH Keys](#generating-ssh-keys) section below if you need help generating keys. Once the device is booted, you can access your device as root over SSH on port 22222.
|
||||
|
||||
### HassOS based Hass.io
|
||||
Use a USB drive formatted with FAT, ext4, or NTFS and name it CONFIG (case sensitive). Create an `authorized_keys` file (no extension) containing your public key, and place it in the root of the USB drive. File needs to be ANSI encoded (not UTF-8) and must have Unix line ends (LF), not Windows (CR LF). See [Generating SSH Keys](#generating-ssh-keys) section below if you need help generating keys. From the UI, navigate to the hass.io system page and choose "Import from USB". You can now access your device as root over SSH on port 22222. Alternatively, the file will be imported from the USB when the hass.io device is rebooted.
|
||||
|
||||
> Make sure when you are copying the public key to the root of the USB drive that you rename the file correctly to `authorized_keys` with no `.pub` file extension.
|
||||
|
||||
You should then be able to SSH into your Hass.io device. On Mac/Linux, use:
|
||||
|
||||
```shell
|
||||
ssh root@hassio.local -p 22222
|
||||
```
|
||||
|
||||
You will initially be logged in to Hass.io CLI for HassOS where you can perform normal [CLI functions]. If you need access to the host system use the 'login' command. [Hass.io OS] is a hypervisor for Docker. See the [Hass.io Architecture] documentation for information regarding the Hass.io supervisor. The supervisor offers an API to manage the host and running the Docker containers. Home Assistant itself and all installed addon's run in separate Docker containers.
|
||||
|
||||
[CLI functions]: https://www.home-assistant.io/hassio/commandline/
|
||||
[Hass.io OS]: https://github.com/home-assistant/hassos
|
||||
[Hass.io Architecture]: https://developers.home-assistant.io/docs/en/architecture_hassio.html
|
||||
|
||||
## Checking the logs
|
||||
|
||||
```shell
|
||||
# Logs from the supervisor service on the Host OS
|
||||
journalctl -f -u hassos-supervisor.service
|
||||
|
||||
# Hass.io supervisor logs
|
||||
docker logs hassos_supervisor
|
||||
|
||||
# Home Assistant logs
|
||||
docker logs homeassistant
|
||||
```
|
||||
|
||||
## Accessing the container bash
|
||||
|
||||
```shell
|
||||
docker exec -it homeassistant /bin/bash
|
||||
```
|
||||
|
||||
[windows-keys]: https://www.digitalocean.com/community/tutorials/how-to-use-ssh-keys-with-putty-on-digitalocean-droplets-windows-users
|
||||
|
||||
### Generating SSH Keys
|
||||
|
||||
Windows instructions for how to generate and use private/public keys with Putty are [here][windows-keys]. Instead of the droplet instructions, add the public key as per above instructions.
|
||||
|
||||
Alternative instructions, for Mac, Windows and Linux can be found [here](https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/#platform-mac).
|
||||
|
||||
Follow steps 1-4 under 'Generating a new SSH key' (The other sections are not applicable to Hass.io and can be ignored.)
|
||||
|
||||
Step 3 in the link above, shows the path to the private key file `id_rsa` for your chosen operating system. Your public key, `id_rsa.pub`, is saved in the same folder. Next, select all text from text box "Public key for pasting into the authorized_keys file" and save it to the root of your USB drive as `authorized_keys`.
|
59
website/versioned_docs/version-0.104.0/hassio_hass.md
Normal file
59
website/versioned_docs/version-0.104.0/hassio_hass.md
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
title: Hass.io <> Home Assistant integration development
|
||||
sidebar_label: Integration development
|
||||
id: version-0.104.0-hassio_hass
|
||||
original_id: hassio_hass
|
||||
---
|
||||
|
||||
These steps will help you connect your local Home Assistant to a remote Hass.io instance. You can then make changes locally to either the Hass.io component or the frontend and test it out against a real instance.
|
||||
|
||||
For this guide, we're going to assume that you have an Hass.io instance up and running. If you don't, you can use the generic installation method to install it inside a [virtual machine](https://github.com/home-assistant/hassio-build/tree/master/install#install-hassio).
|
||||
|
||||
## API Access
|
||||
|
||||
To develop for the frontend, we're going to need API access to the supervisor.
|
||||
|
||||
- Add our developer Add-on repository: https://github.com/home-assistant/hassio-addons-development
|
||||
- Install the Add-on "Remote API proxy"
|
||||
|
||||
For some API commands you need explicit the Home Assistant API token, but 99% of the functionality work with `Remote API proxy`. This token change sometimes but you can read the current legal token on host system with:
|
||||
```shell
|
||||
$ docker inspect homeassistant | grep HASSIO_TOKEN
|
||||
```
|
||||
|
||||
## Having Home Assistant connect to remote Hass.io
|
||||
|
||||
The connection with the supervisor is hidden inside the host and is only accessible from applications running on the host. So to make it accessible for our Home Assistant instance we will need to route the connection to our computer running Home Assistant. We're going to do this by forwarding the API with "Remote API proxy" add-on.
|
||||
|
||||
First, make sure Home Assistant will load the Hass.io component by adding `hassio:` to your `configuration.yaml` file. Next, we will need to tell the local Home Assistant instance how to connect to the remote Hass.io instance. We do this by setting the `HASSIO` and `HASSIO_TOKEN` environment variables when starting Home Assistant. Note that the `HASSIO` value is not the same as the one that we saw above and the `HASSIO_TOKEN` is available inside log output of "Remote API Add-on" (This changes every restart of the add-on!).
|
||||
|
||||
```shell
|
||||
HASSIO=<IP OF HASS.IO>:80 HASSIO_TOKEN=<VALUE OF HASSIO_TOKEN> hass
|
||||
```
|
||||
|
||||
Voila. Your local Home Assistant installation will now connect to a remote Hass.io instance.
|
||||
|
||||
## Frontend development
|
||||
|
||||
> This requires Home Assistant 0.71 or later.
|
||||
|
||||
We need a couple more steps to do frontend development. First, make sure you have a Home Assistant frontend development set up ([instructions](frontend_index.md)).
|
||||
|
||||
Update the Hass.io component configuration in your `configuration.yaml` to point at the frontend repository:
|
||||
|
||||
```yaml
|
||||
# configuration.yaml
|
||||
hassio:
|
||||
development_repo: /home/paulus/dev/hass/home-assistant-polymer
|
||||
```
|
||||
|
||||
To build a local version of the Hass.io panel, go to the frontend repository and run:
|
||||
|
||||
```shell
|
||||
cd hassio
|
||||
script/develop
|
||||
```
|
||||
|
||||
Now start Home Assistant as discussed in the previous section and it will now connect to the remote Hass.io but show your local frontend.
|
||||
|
||||
Once you have `script/develop` the hassio panel will be rebuilt whenever you make changes to the source files.
|
@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Registering sentences
|
||||
id: version-0.104.0-intent_conversation
|
||||
original_id: intent_conversation
|
||||
---
|
||||
|
||||
The conversation component handles incoming commands from the frontend and converts them to intents. It does this based on registered sentences.
|
||||
|
||||
As a component, you can register sentences with the conversation component to allow it to be remote controlled. Refer to named slots by putting the slot name between curly braces: `{item}`. Use square brackets around (partial) words to mark them as optional.
|
||||
|
||||
Example code:
|
||||
|
||||
```python
|
||||
async def async_setup(hass, config):
|
||||
hass.components.conversation.async_register(
|
||||
"MyCoolIntent",
|
||||
["I think that {object} is [very] cool", "Nothing is cooler than {object}"],
|
||||
)
|
||||
```
|
||||
|
||||
If a sentence like "I think that beer is cool" comes in, the conversation component will generate an intent of type `MyCoolIntent` and with 1 slot, named `object` and value `beer`.
|
52
website/versioned_docs/version-0.104.0/intent_firing.md
Normal file
52
website/versioned_docs/version-0.104.0/intent_firing.md
Normal file
@ -0,0 +1,52 @@
|
||||
---
|
||||
title: Firing intents
|
||||
id: version-0.104.0-intent_firing
|
||||
original_id: intent_firing
|
||||
---
|
||||
|
||||
When you fire an intent, you will get a response back or an error will be raised. It is up to the component to return the result to the user.
|
||||
|
||||
Example code to handle an intent in Home Assistant.
|
||||
|
||||
```python
|
||||
from homeassistant.helpers import intent
|
||||
|
||||
intent_type = "TurnLightOn"
|
||||
slots = {"entity": {"value": "Kitchen"}}
|
||||
|
||||
try:
|
||||
intent_response = yield from intent.async_handle(
|
||||
hass, "example_component", intent_type, slots
|
||||
)
|
||||
|
||||
except intent.UnknownIntent as err:
|
||||
_LOGGER.warning("Received unknown intent %s", intent_type)
|
||||
|
||||
except intent.InvalidSlotInfo as err:
|
||||
_LOGGER.error("Received invalid slot data: %s", err)
|
||||
|
||||
except intent.IntentError:
|
||||
_LOGGER.exception("Error handling request for %s", intent_type)
|
||||
```
|
||||
|
||||
The intent response is an instance of `homeassistant.helpers.intent.IntentResponse`.
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| `intent` | Intent | Instance of intent that triggered response. |
|
||||
| `speech` | Dictionary | Speech responses. Each key is a type. Allowed types are `plain` and `ssml`. |
|
||||
| `card` | Dictionary | Card responses. Each key is a type. |
|
||||
|
||||
Speech dictionary values:
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| `speech` | String | The text to say
|
||||
| `extra_data` | Any | Extra information related to this speech.
|
||||
|
||||
Card dictionary values:
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| `title` | String | The title of the card
|
||||
| `content` | Any | The content of the card
|
45
website/versioned_docs/version-0.104.0/intent_handling.md
Normal file
45
website/versioned_docs/version-0.104.0/intent_handling.md
Normal file
@ -0,0 +1,45 @@
|
||||
---
|
||||
title: Handling intents
|
||||
id: version-0.104.0-intent_handling
|
||||
original_id: intent_handling
|
||||
---
|
||||
|
||||
Any component can register to handle intents. This allows a single component to handle intents fired from multiple voice assistants.
|
||||
|
||||
A component has to register an intent handler for each type that it wants to handle. Intent handlers have to extend `homeassistant.helpers.intent.IntentHandler`
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from homeassistant.helpers import intent
|
||||
|
||||
DATA_KEY = "example_key"
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
hass.data[DATA_KEY] = 0
|
||||
intent.async_register(hass, CountInvocationIntent())
|
||||
|
||||
|
||||
class CountInvocationIntent(intent.IntentHandler):
|
||||
"""Handle CountInvocationIntent intents."""
|
||||
|
||||
# Type of intent to handle
|
||||
intent_type = "CountInvocationIntent"
|
||||
|
||||
# Optional. A validation schema for slots
|
||||
# slot_schema = {
|
||||
# 'item': cv.string
|
||||
# }
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_handle(self, intent_obj):
|
||||
"""Handle the intent."""
|
||||
intent_obj.hass.data[DATA_KEY] += 1
|
||||
|
||||
response = intent_obj.create_response()
|
||||
response.async_set_speech(
|
||||
f"This intent has been invoked {intent_obj.hass.data[DATA_KEY]} times"
|
||||
)
|
||||
return response
|
||||
```
|
@ -0,0 +1,45 @@
|
||||
---
|
||||
title: Backend Localization
|
||||
id: version-0.104.0-internationalization_backend_localization
|
||||
original_id: internationalization_backend_localization
|
||||
---
|
||||
|
||||
## Translation Strings
|
||||
Platform translation strings are stored as JSON in the [home-assistant](https://github.com/home-assistant/home-assistant) repository. These files must be located adjacent to the component/platform they belong to. Components must have their own directory, and the file is simply named `strings.json` in that directory. For platforms, they are named `strings.<platform name>.json` in the platform directory. This file will contain the different strings that will be translatable.
|
||||
|
||||
In order to test changes to translation files, the translation strings must be compiled into Home Assistant’s translation directories by running the following script:
|
||||
|
||||
```shell
|
||||
$ script/translations_develop
|
||||
```
|
||||
|
||||
After the pull request with the strings file is merged into the `dev` branch, the strings will be automatically uploaded to Lokalise, where contributors can submit translations. The translated strings in Lokalise will be periodically pulled in to the home-assistant repository.
|
||||
|
||||
## States Localization
|
||||
The first step when localizing platform states is to ensure that the states defined in the actual platform code are defined in `snake_case`. The states should not contain capital letters or spaces. Next, the strings file needs to be created. The states should exist under the `state` key, and map the backend state keys to their English translations. [The season sensor localization](https://github.com/home-assistant/home-assistant/pull/12453/commits/bb2f328ce10c3867990e34a88da64e2f8dc7a5c4) is a good example.
|
||||
|
||||
## Configuration Flow Localization
|
||||
The translation strings for the configuration flow handler are defined under the `config` key. An example strings file below describes the different supported keys:
|
||||
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"title": "This title is shown in the integrations list",
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "The user visible title of the `init` step.",
|
||||
"description": "Markdown that is shown with the step.",
|
||||
"data": {
|
||||
"api_key": "The label for the `api_key` input field"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_api_key": "This message will be displayed if `invalid_api_key` is returned as a flow error."
|
||||
},
|
||||
"abort": {
|
||||
"stale_api_key": "This message will be displayed if `stale_api_key` is returned as the abort reason."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
@ -0,0 +1,43 @@
|
||||
---
|
||||
title: Translation
|
||||
id: version-0.104.0-internationalization_translation
|
||||
original_id: internationalization_translation
|
||||
---
|
||||
|
||||
Translations for Home Assistant are managed through [Lokalise](https://lokalise.co/), an online translation management tool. Our translations are split between two projects, a backend project for platform-specific translations, and a frontend project for UI translations. Click the links below to join both projects! Even if your language is completely translated, extra proofreading is a big help! Please feel free to review the existing translations, and vote for alternatives that might be more appropriate.
|
||||
|
||||
- [Join the frontend translation team](https://lokalise.co/signup/3420425759f6d6d241f598.13594006/all/)
|
||||
- [Join the backend translation team](https://lokalise.co/signup/130246255a974bd3b5e8a1.51616605/all/)
|
||||
- [Join the iOS translation team](https://lokalise.co/signup/834452985a05254348aee2.46389241/all/)
|
||||
- [Join the Android translation team](https://lokalise.com/public/145814835dd655bc5ab0d0.36753359/)
|
||||
|
||||
For more information about the translation workflow, please see the [Lokalise translation workflow documents](https://docs.lokalise.co/category/iOzEuQPS53-for-team-leads-and-translators).
|
||||
|
||||
> The translation of the Home Assistant frontend is still a work in progress. More phrases will be available for translation soon.
|
||||
|
||||
## Translation placeholders
|
||||
|
||||
Some translation strings will contain special placeholders that will be replaced later. Placeholders shown in square brackets `[]` are [Lokalise key references](https://docs.lokalise.co/article/KO5SZWLLsy-key-referencing). These are primarily used to link translation strings that will be duplicated. Different languages may not have the same duplicates as English, and are welcome to link duplicate translations that are not linked in English. Placeholders shown in curly brackets `{}` are [translation arguments](https://formatjs.io/guides/message-syntax/) that will be replaced with a live value when Home Assistant is running. Any translation argument placeholders present in the original string must be included in the translated string. These may include special syntax for defining plurals or other replacement rules. The linked format.js guide explains the syntax for adding plural definitions and other rules.
|
||||
|
||||
## Rules
|
||||
1. Only native speakers should submit translations.
|
||||
2. Stick to [Material Design guidelines](https://material.io/guidelines/style/writing.html).
|
||||
3. Don't translate or change proper nouns like `Home Assistant`, `Hass.io` or `Hue`.
|
||||
4. For a region specific translation, keys that will be the same as the base translation should be filled with `[VOID]`. These will be replaced during our translation build process.
|
||||
5. Translations under the `state_badge` keys will be used for the notification badge display. These translations should be short enough to fit in the badge label without overflowing. This can be tested in the Home Assistant UI either by editing the label text with your browsers development tools, or by using the States <img src='/img/dev-tools/states-icon.png' alt='states dev tool icon' class="inline" width="38" /> developer tool in the Home Assistant UI. In the UI, enter a new entity ID (`device_tracker.test`), and enter the text you want to test in state.
|
||||
6. If text will be duplicated across different translation keys, make use of the Lokalise key reference feature where possible. The base translation provides examples of this underneath the `states` translations. Please see the [Lokalise key referencing](https://docs.lokalise.com/en/articles/1400528-key-referencing) documentation for more details.
|
||||
|
||||
## Adding a new language
|
||||
If your language is not listed you can request it at [GitHub](https://github.com/home-assistant/home-assistant-polymer/issues/new). Please provide both the English name and the native name for your language. For example:
|
||||
```
|
||||
English Name: German
|
||||
Native Name: Deutsch
|
||||
```
|
||||
|
||||
> Region specific translations (`en-US`, `fr-CA`) will only be included if translations for that region need to differ from the base language translation.
|
||||
|
||||
### Maintainer steps to add a new language
|
||||
1. Language tags have to follow [BCP 47](https://tools.ietf.org/html/bcp47). A list of most language tags can be found here: [IANA sutbtag registry](http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry). Examples: `fr`, `fr-CA`, `zh-Hans`. Only include the country code if country specific overrides are being included, and the base language is already translated.
|
||||
2. Add the language tag and native name in `src/translations/translationMetadata.json`. Examples: "Français", "Français (CA)"
|
||||
3. Add the new language in Lokalize.
|
||||
Note: Sometimes you have to change the tag in Lokalise (Language -> Language settings -> custom ISO code).
|
249
website/versioned_docs/version-0.104.0/lovelace_custom_card.md
Normal file
249
website/versioned_docs/version-0.104.0/lovelace_custom_card.md
Normal file
@ -0,0 +1,249 @@
|
||||
---
|
||||
title: Lovelace: Custom Cards
|
||||
id: version-0.104.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.xyz/) (Home Assistant Community Store).
|
||||
|
||||
- [HACS Plugin Docs](https://hacs.xyz/docs/publish/plugin)
|
47
website/versioned_docs/version-0.104.0/maintenance.md
Normal file
47
website/versioned_docs/version-0.104.0/maintenance.md
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Maintenance
|
||||
id: version-0.104.0-maintenance
|
||||
original_id: maintenance
|
||||
---
|
||||
|
||||
This page documents a couple of points for maintaining the Home Assistant code. Most of the tasks don't need to be performed on a regular base thus the steps, used tools, or details are preserved here.
|
||||
|
||||
## Source code
|
||||
|
||||
### Line separator
|
||||
|
||||
People are using various operating systems to develop components and platforms for Home Assistant. This could lead to different line endings on file. We prefer `LN`. Especially Microsoft Windows tools tend to use `CRLF`.
|
||||
|
||||
```shell
|
||||
$ find homeassistant -name "*.py" -exec file {} \; | grep BOM
|
||||
$ find homeassistant -name "*.py" -exec file {} \; | grep CRLF
|
||||
```
|
||||
|
||||
To fix the line separator, use `dos2unix` or `sed`.
|
||||
|
||||
```shell
|
||||
$ dos2unix homeassistant/components/notify/kodi.py
|
||||
```
|
||||
|
||||
### File permissions
|
||||
|
||||
Most files don't need to the be executable. `0644` is fine.
|
||||
|
||||
### Dependencies
|
||||
|
||||
A lot of components and platforms depends on third-party Python modules. The dependencies which are stored in the `requirements_all.txt` files can be tracked with [`pur`](https://pypi.org/project/pur/) or [`pip-upgrader`](https://github.com/simion/pip-upgrader).
|
||||
|
||||
If you update the requirements of a component/platform by updating `manifest.json`, run the provided script to update the `requirements_*.txt` file(s).
|
||||
|
||||
```shell
|
||||
$ script/gen_requirements_all.py
|
||||
```
|
||||
|
||||
Start a test run of Home Assistant. If that was successful, include all files in a Pull Request. Add a short summary of the changes, a sample configuration entry, details about the tests you performed to ensure the update works, and other useful information to the description.
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
- Merge `current` into `next` on a regular base.
|
||||
- Optimize the images.
|
||||
|
@ -0,0 +1,29 @@
|
||||
---
|
||||
title: Reproduce State / Scene support
|
||||
id: version-0.104.0-reproduce_state_index
|
||||
original_id: reproduce_state_index
|
||||
---
|
||||
|
||||
Home Assistant has support for scenes. Scenes are a collection of (partial) entity states. When a scene is activated, Home Assistant will try to call the right services to get the specified scenes in their specified state.
|
||||
|
||||
Integrations are responsible for adding support to Home Assistant to be able to call the right services to reproduce the states in a scene.
|
||||
|
||||
## Adding support
|
||||
|
||||
The quickest way to add reproduce state support to a new integration is by using our built-in scaffold template. From a Home Assistant dev environment, run `python3 -m script.scaffold reproduce_state` and follow the instructions.
|
||||
|
||||
If you prefer to go the manual route, create a new file in your integration folder called `reproduce_state.py` and implement the following method:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from typing import Iterable, Optional
|
||||
from homeassistant.core import Context, State
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
|
||||
async def async_reproduce_states(
|
||||
hass: HomeAssistantType, states: Iterable[State], context: Optional[Context] = None
|
||||
) -> None:
|
||||
"""Reproduce component states."""
|
||||
# TODO reproduce states
|
||||
```
|
@ -1,4 +1,5 @@
|
||||
[
|
||||
"0.104.0",
|
||||
"0.103.0",
|
||||
"0.102.0",
|
||||
"0.101.0",
|
||||
|
Loading…
x
Reference in New Issue
Block a user