This commit is contained in:
Paulus Schoutsen 2019-03-06 15:19:16 -08:00
parent 764026b6f0
commit bd46242622
17 changed files with 1409 additions and 0 deletions

View File

@ -1124,6 +1124,53 @@
},
"version-0.88.0-lovelace_custom_card": {
"title": "Lovelace: Custom Cards"
},
"version-0.89.0-app_integration_index": {
"title": "Native App Integration",
"sidebar_label": "Introduction"
},
"version-0.89.0-app_integration_sending_data": {
"title": "Sending data home"
},
"version-0.89.0-app_integration_setup": {
"title": "Connecting to an instance"
},
"version-0.89.0-auth_auth_module": {
"title": "Multi-factor Authentication Modules"
},
"version-0.89.0-config_entries_index": {
"title": "Config Entries",
"sidebar_label": "Introduction"
},
"version-0.89.0-config_entries_options_flow_handler": {
"title": "Options Flow Handlers"
},
"version-0.89.0-creating_component_code_review": {
"title": "Checklist for creating a component"
},
"version-0.89.0-creating_platform_code_review": {
"title": "Checklist for creating a platform"
},
"version-0.89.0-development_checklist": {
"title": "Development Checklist"
},
"version-0.89.0-development_environment": {
"title": "Set up Development Environment"
},
"version-0.89.0-device_registry_index": {
"title": "Device Registry",
"sidebar_label": "Introduction"
},
"version-0.89.0-entity_alarm_control_panel": {
"title": "Alarm Control Panel Entity",
"sidebar_label": "Alarm Control Panel"
},
"version-0.89.0-entity_cover": {
"title": "Cover Entity",
"sidebar_label": "Cover"
},
"version-0.89.0-internationalization_translation": {
"title": "Translation"
}
},
"links": {

View File

@ -0,0 +1,13 @@
---
title: Native App Integration
sidebar_label: Introduction
id: version-0.89.0-app_integration_index
original_id: app_integration_index
---
This guide describes how to build a native Home Assistant app that communicates with Home Assistant and offers a seamless integration. Below is a list of the things that we will discuss in this guide.
- Allow the user to establish a connection and authenticate with their own Home Assistant instance.
- Send location and device info back to Home Assistant.
- Call services, fire events and render templates.
- A view to control the house via an authenticated webview.

View File

@ -0,0 +1,146 @@
---
title: Sending data home
id: version-0.89.0-app_integration_sending_data
original_id: app_integration_sending_data
---
Once you have registered your app with the mobile app component, you can start interacting with Home Assistant via the provided webhook information.
The first step is to turn the returned webhook ID into a full URL: `<instance_url>/api/webhook/<webhook_id>`. This will be the only url that we will need for all our interactions. The webhook endpoint will not require authenticated requests.
## Short note on instance URLs
Some users have configured Home Assistant to be available outside of their home network using a dynamic DNS service. There are some routers that don't support hairpinning / NAT loopback: a device sending data from inside the routers network, via the externally configured DNS service, to Home Asisstant, which also resides inside the local network.
To work around this, the app will need to record which WiFi is the home network, and use a direct connection when connected to the home WiFi network.
## Interaction basics
All interaction will be done by making HTTP POST requests to the webhook url. These requests do not need to contain authentication.
The payload format depends on the type of interaction, but it all shares a common base:
```json5
{
"type": "<type of message>",
// other info
}
```
If you received a `secret` during registration, you will need to encrypt your message and wrap it in an encrypted message:
```json5
{
"type": "encrypted",
"data": "<encrypted message>"
}
```
## Update device location
This message will inform Home Assistant of new location information.
```json
{
"type": "update_location",
"gps": [12.34, 56.78],
"gps_accuracy": 120,
"battery": 45,
}
```
| Key | Type | Description
| --- | ---- | -----------
| `gps` | latlong | Current location as latitude and longitude.
| `gps_accuracy` | int | GPS accurracy in meters.
| `battery` | int | Percentage of battery the device has left.
## Call a service
Call a service in Home Assistant.
```json
{
"type": "call_service",
"domain": "light",
"service": "turn_on",
"service_data": {
"entity_id": "light.kitchen"
}
}
```
| Key | Type | Description
| --- | ---- | -----------
| `domain` | string | The domain of the service
| `service` | string | The service namae
| `service_data` | dict | The data to send to the service
## Fire an event
Fire an event in Home Assistant.
```json
{
"type": "fire_event",
"event_type": "my_custom_event",
"event_data": {
"something": 50
}
}
```
| Key | Type | Description
| --- | ---- | -----------
| `event_type` | string | Type of the event to fire
| `event_data` | string | Date of the event to fire
## Render templates
> This API is very likely to change in an upcoming release. Support to render multiple templates at once will be added.
Renders a template and returns the result.
```json
{
"type": "render_template",
"template": "Hello {{ name }}, you are {{ states('person.paulus') }}.",
"variables": {
"name": "Paulus"
}
}
```
| Key | Type | Description
| --- | ---- | -----------
| `template` | string | The template to render
| `variables` | Dict | The extra template variables to include.
## Update registration
Update your app registration. Use this if the app version changed or any of the other values.
```json
{
"type": "update_registration",
"app_data": {
"push_notification_key": "abcd"
},
"app_version": "2.0.0",
"device_name": "Robbies iPhone",
"manufacturer": "Apple, Inc.",
"model": "iPhone XR",
"os_version": "23.02",
}
```
All keys are optional.
| Key | Type | Description
| --- | --- | --
| `app_version` | string | Version of the mobile app.
| `device_name` | string | Name of the device running the app.
| `manufacturer` | string | The manufacturer of the device running the app.
| `model` | string | The model of the device running the app.
| `os_version` | string | The OS version of the device running the app.
| `app_data` | Dict | App data can be used if the app has a supporting component that extends mobile_app functionality.

View File

@ -0,0 +1,87 @@
---
title: Connecting to an instance
id: version-0.89.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 is an experimental feature. We expect to evolve the API in the upcoming releases.
_This requires Home Assistant 0.89 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. You can do so by making an authenticated POST request to `/api/mobile_app/devices`. [More info on making authenticated requests.](auth_api.md#making-authenticated-requests)
If you get a 404 when making this request, it means the user does not have the mobile_app component enabled. Prompt the user to enable the `mobile_app` component. The mobile_app component is set up as part of the default Home Assistant configuration.
Example payload to send to the registration endpoint:
```json
{
"app_id": "awesome_home",
"app_name": "Awesome Home",
"app_version": "1.2.0",
"device_name": "Robbies iPhone",
"manufacturer": "Apple, Inc.",
"model": "iPhone X",
"os_version": "iOS 10.12",
"supports_encryption": true,
"app_data": {
"push_notification_key": "abcdef",
}
}
```
| Key | Required | Type | Description |
| --- | -------- | ---- | ----------- |
| `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_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. Store this information.
```json
{
"webhook_id": "abcdefgh",
"secret": "qwerty"
}
```
| Key | Type | Description
| --- | ---- | -----------
| `webhook_id` | string | The webhook ID that can be used to send data back.
| `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.
## Encryption
The mobile app component supports encryption to make sure all communication between the app and Home Assistant is encrypted. The encryption is powered by [libsodium](https://libsodium.gitbook.io). Example to encrypt the message using libsodium on Python:
```python
from nacl.secret import SecretBox
from nacl.encoding import Base64Encoder
data = SecretBox(key).encrypt(
payload,
encoder=Base64Encoder
).decode("utf-8")
```

View File

@ -0,0 +1,65 @@
---
title: Multi-factor Authentication Modules
id: version-0.89.0-auth_auth_module
original_id: auth_auth_module
---
Multi-factor Authentication Modules are used in conjunction with [Authentication Provider](auth_auth_provider.html) to provide a fully configurable authentication framework. Each MFA module may provide one multi-factor authentication function. User can enable multiple mfa modules, but can only select one module in login process.
## Defining an mfa auth module
> We currently only support built-in mfa auth modules. Support for custom auth modules might arrive in the future.
Multi-factor Auth modules are defined in `homeassistant/auth/mfa_modules/<name of module>.py`. The auth module will need to provide an implementation of the `MultiFactorAuthModule` class.
For an example of a fully implemented auth module, please see [insecure_example.py](https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/auth/mfa_modules/insecure_example.py).
Multi-factor Auth modules shall extend the following methods of `MultiFactorAuthModule` class.
| method | Required | Description
| ------ | -------- | -----------
| `@property def input_schema(self)` | Yes | Return a schema defined the user input form.
| `async def async_setup_flow(self, user_id)` | Yes | Return a SetupFlow to handle the setup workflow.
| `async def async_setup_user(self, user_id, setup_data)` | Yes | Set up user for use this auth module.
| `async def async_depose_user(self, user_id)` | Yes | Remove user information from this auth module.
| `async def async_is_user_setup(self, user_id)` | Yes | Return whether user is set up.
| `async def async_validate(self, user_id, user_input)` | Yes | Given a user_id and user input, return validation result.
| `async def async_initialize_login_mfa_step(self, user_id)` | No | Will be called once before display the mfa step of login flow. This is not initialization for the MFA module but the mfa step in login flow.
## Setup Flow
Before user can use a multi-factor auth module, it has to be enabled or set up. All available modules will be listed in user profile page, user can enable the module he/she wants to use. A setup data entry flow will guide user finish the necessary steps.
Each MFA module need to implement a setup flow handler extends from `mfa_modules.SetupFlow` (if only one simple setup step need, `SetupFlow` can be used as well). For example for Google Authenticator (TOTP, Time-based One Time Password) module, the flow will need to be:
- Generate a secret and store it on instance of setup flow
- Return `async_show_form` with a QR code in the description (injected as base64 via `description_placeholders`)
- User scans code and enters a code to verify it scanned correctly and clock in synced
- TOTP module saved the secret along with user_id, module is enabled for user
## Workflow
> TODO: draw a diagram
User == select auth provider ==> LoginFlow.init == input/validate username/password ==> LoginFlow.finish ==> if user enabled mfa ==> LoginFlow.select_mfa_module ==> initialize(optional) ==> LoginFlow.mfa == input/validate MFA code ==> LoginFlow.finish ==> Done
## Configuration example
```yaml
# configuration.xml
homeassistant:
auth_providers:
- type: homeassistant
- type: legacy_api_password
auth_mfa_modules:
- type: totp
- type: insecure_example
users: [{'user_id': 'a_32_bytes_length_user_id', 'pin': '123456'}]
```
In this example, user will first select from `homeassistant` or `legacy_api_password` auth provider. For `homeassistant` auth provider, user will first input username/password, if that user enabled both `totp` and `insecure_example`, then user need select one auth module, then input Google Authenticator code or input pin code base on the selection.
> insecure_example is only for demo purpose, please do not use it in production.
## Validation session
Not like auth provider, auth module use session to manage the validation. After auth provider validated, mfa module will create a validation session, include an experiation time and user_id from auth provider validate result. Mutli-factor auth module will not only verify the user input, but also verify the session is not expired. The validation session data is stored in your configuration directory.

View File

@ -0,0 +1,127 @@
---
title: Config Entries
sidebar_label: Introduction
id: version-0.89.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.
## Lifeceycle
| 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-&gt;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-&gt;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-&gt;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-&gt;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-&gt;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-&gt;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-&gt;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-&gt;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):
```
## 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)).
Platforms will not need to add any logic for unloading a config entry. The entity component will take care of this.
If you need to clean up resources used for an entity, implement the `async_will_remove_from_hass` method on the Entity ([example](https://github.com/home-assistant/home-assistant/blob/0.68.0/homeassistant/components/media_player/cast.py#L313)).

View File

@ -0,0 +1,42 @@
---
title: Options Flow Handlers
id: version-0.89.0-config_entries_options_flow_handler
original_id: config_entries_options_flow_handler
---
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 a Options Flow Handler.
## Options support
For a component 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, options):
return OptionsFlowHandler(config, options)
```
## 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(data_entry_flow.FlowHandler):
def __init__(self, config, options):
```
## 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):
```

View File

@ -0,0 +1,56 @@
---
title: Checklist for creating a component
id: version-0.89.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 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 components level
### 1. Requirements
1. Requirement version pinned: `REQUIREMENTS = ['phue==0.8.1']`
2. We no longer want requirements hosted on GitHub. Please upload to PyPi.
3. Requirements should [only be imported inside functions](creating_component_deps_and_reqs.md). This is necessary because requirements are installed on the fly.
### 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. If you need to share global data with platforms, use the dictionary `hass.data`. `hass.data[DATA_XY]` while `XY` is the component is preferred over `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()
```
### 5. Limit platforms included in initial pull request
Large pull requests mean there is a larger chance of finding problems that need to be addressed, and more code that needs to be reviewed between every requested change. If your new component will have multiple platforms, try and limit your initial pull request to a single platform. Once the initial component is merged, you can submit additional PRs for the remaining platforms. This allows reviewers to sign off on smaller chunks of code one at a time, and lets us get your new feature in sooner. Pull requests containing large code dumps will not be a priority for review.
### 6. Event names
Prefix component event names with component name itself. For example, use `xiaomi_aqara.click` instead of `click` for the `xiaomi_aqara` component.

View File

@ -0,0 +1,79 @@
---
title: Checklist for creating a platform
id: version-0.89.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. Requirement version should be pinned: `REQUIREMENTS = ['phue==0.8.1']`
2. We no longer want requirements hosted on GitHub. Please upload to PyPi.
3. Requirements should [only be imported inside functions](creating_component_deps_and_reqs.md). This is necessary because requirements are installed on the fly.
### 2. Dependencies
1. If you depend on a component for the connection, add it to your dependencies: `DEPENDENCIES = ['nest']`
### 3. Configuration
1. Voluptuous schema present for [configuration validation](development_validation.md)
2. Voluptuous schema extends schema from component<br>(e.g., `light.hue.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`
```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,
})
```
5. Never depend on users adding things to `customize` to configure behavior inside your platform.
### 4. Setup Platform
1. Test if passed in info (user/pass/host etc.) works.
2. Group your calls to `add_devices` if possible.
3. If platform adds extra services, format should be `<component>.<platform>_<service name>`.
### 5. Entity
1. Extend entity from component, e.g., `class HueLight(Light)`.
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 by the helper in entity_platform.py. 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. The state and/or attributes should not contain relative time since something happened. Instead it should store UTC timestamps.
### 6. 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()
```

View File

@ -0,0 +1,15 @@
---
title: Development Checklist
id: version-0.89.0-development_checklist
original_id: development_checklist
---
Before you commit any changes, check your work against these requirements:
- All communication to external devices or services must be wrapped in an external Python library hosted on [pypi](https://pypi.python.org/pypi).
- All dependencies from [pypi](https://pypi.python.org/pypi) are included via the `REQUIREMENTS` variable in your platform or component and [only imported inside functions](creating_component_deps_and_reqs.md) that use them
- New dependencies are added to `requirements_all.txt` (if applicable), using `script/gen_requirements_all.py`
- The `.coveragerc` file is updated to exclude your platform if there are no tests available or your new code uses a third-party library for communication with the device, service, or sensor
- Documentation is developed for [home-assistant.io](/)
* It's OK to start with adding a docstring with configuration details (for example, sample entry for `configuration.yaml` file) to the file header. Visit the [website documentation](https://www.home-assistant.io/developers/documentation/) for more information about contributing to [home-assistant.io](https://github.com/home-assistant/home-assistant.github.io).

View File

@ -0,0 +1,112 @@
---
title: Set up Development Environment
id: version-0.89.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.
```bash
$ sudo apt-get install python3-pip python3-dev python3-venv
```
In order to run `script/setup` below you will need some more dependencies.
```bash
$ sudo apt-get install autoconf libssl-dev libxml2-dev libxslt1-dev libjpeg-dev libffi-dev libudev-dev zlib1g-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
Due to 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
```bash
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.
```bash
$ sudo apt-get update
$ sudo apt-get install python3-pip python3-dev python3-venv
$ sudo apt-get install autoconf libssl-dev libxml2-dev libxslt1-dev libjpeg-dev libffi-dev libudev-dev zlib1g-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.
```bash
$ mkdir -p ../config
$ hass -c ../config
```
### Developing on OS X
Install [Homebrew](https://brew.sh/), then use that to install Python 3:
```bash
$ brew install python3 autoconf
```
## 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:
```bash
$ 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.
```bash
$ python3 -m venv venv
$ source venv/bin/activate
```
Install the requirements with a provided script named `setup`.
```bash
$ script/setup
```
Invoke your installation, adjusting the [configuration](https://www.home-assistant.io/docs/configuration/) if required.
```bash
$ 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
```

View File

@ -0,0 +1,85 @@
---
title: Device Registry
sidebar_label: Introduction
id: version-0.89.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 a 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.
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.
> 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_hub | Identifier of a device that routes messages between this device and Home Assistant. Examples of such devices are hubs. 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 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_hub': (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=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,
)
```

View File

@ -0,0 +1,129 @@
---
title: Alarm Control Panel Entity
sidebar_label: Alarm Control Panel
id: version-0.89.0-entity_alarm_control_panel
original_id: entity_alarm_control_panel
---
> 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
| ---- | ---- | ------- | -----------
| state | string | **Required** | One of the states listed in the **states** section.
| code_format | string | `None` | One of the states listed in the **code formats** section.
| changed_by | string | `None` | Last change triggered by.
### States
| Value | Description
| ----- | -----------
| `disarmed` | The alarm is disarmed (`off`).
| `armed_home` | The alarm is armed in home mode.
| `armed_away` | The alarm is armed in away mode.
| `armed_night` | The alarm is armed in night mode.
| `armed_custom_bypass` | The alarm is armed in bypass mode.
| `pending` | The alarm is pending (towards `triggered`).
| `arming` | The alarm is arming.
| `disarming` | The alarm is disarming.
| `triggered` | The alarm is triggered.
### Code Formats
| Value | Description
| ----- | -----------
| None | No code required.
| Number | Code is a number (Shows ten-key pad on frontend).
| Any | Code is a string.
## Methods
### Alarm Disarm
Send disarm command.
```python
class MyAlarm(AlarmControlPanel):
# Implement one of these methods.
def alarm_disarm(self, code=None) -> None:
"""Send disarm command."""
async def async_alarm_disarm(self, code=None) -> None:
"""Send disarm command."""
```
### Alarm Arm Home
Send arm home command.
```python
class MyAlarm(AlarmControlPanel):
# Implement one of these methods.
def alarm_arm_home(self, code=None) -> None:
"""Send arm home command."""
def async_alarm_arm_home(self, code=None) -> None:
"""Send arm home command."""
```
### Alarm Arm Away
Send arm away command.
```python
class MyAlarm(AlarmControlPanel):
# Implement one of these methods.
def alarm_arm_away(self, code=None) -> None:
"""Send arm away command."""
def async_alarm_arm_away(self, code=None) -> None:
"""Send arm away command."""
```
### Alarm Arm Night
Send arm night command.
```python
class MyAlarm(AlarmControlPanel):
# Implement one of these methods.
def alarm_arm_night(self, code=None) -> None:
"""Send arm night command."""
def async_alarm_arm_night(self, code=None) -> None:
"""Send arm night command."""
```
### Alarm Trigger
Send alarm trigger command.
```python
class MyAlarm(AlarmControlPanel):
# Implement one of these methods.
def alarm_trigger(self, code=None) -> None:
"""Send alarm trigger command."""
def async_alarm_trigger(self, code=None) -> None:
"""Send alarm trigger command."""
```
### Alarm Custom Bypass
Send arm custom bypass command.
```python
class MyAlarm(AlarmControlPanel):
# Implement one of these methods.
def alarm_arm_custom_bypass(self, code=None) -> None:
"""Send arm custom bypass command."""
def async_alarm_arm_custom_bypass(self, code=None) -> None:
"""Send arm custom bypass command."""
```

View File

@ -0,0 +1,189 @@
---
title: Cover Entity
sidebar_label: Cover
id: version-0.89.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_CLOSE` 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 metohd 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."""
```

View File

@ -0,0 +1,43 @@
---
title: Translation
id: version-0.89.0-internationalization_translation
original_id: internationalization_translation
---
## How to start
Translations for Home Assistant are managed through Lokalise, 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/)
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.co/article/KO5SZWLLsy-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).

View File

@ -0,0 +1,173 @@
{
"version-0.89.0-Architecture": {
"Architecture": [
"version-0.89.0-architecture_index",
"version-0.89.0-architecture_components",
"version-0.89.0-architecture_entities",
"version-0.89.0-architecture_hassio"
],
"Entities": [
"version-0.89.0-entity_index",
"version-0.89.0-entity_air_quality",
"version-0.89.0-entity_alarm_control_panel",
"version-0.89.0-entity_binary_sensor",
"version-0.89.0-entity_climate",
"version-0.89.0-entity_cover",
"version-0.89.0-entity_fan",
"version-0.89.0-entity_light",
"version-0.89.0-entity_lock",
"version-0.89.0-entity_media_player",
"version-0.89.0-entity_remote",
"version-0.89.0-entity_sensor",
"version-0.89.0-entity_switch",
"version-0.89.0-entity_vacuum",
"version-0.89.0-entity_water_heater",
"version-0.89.0-entity_weather"
],
"Authentication": [
"version-0.89.0-auth_index",
"version-0.89.0-auth_permissions",
"version-0.89.0-auth_api",
"version-0.89.0-auth_auth_provider",
"version-0.89.0-auth_auth_module"
],
"Configuration.yaml": [
"version-0.89.0-configuration_yaml_index"
],
"Config Entries": [
"version-0.89.0-config_entries_index",
"version-0.89.0-config_entries_config_flow_handler",
"version-0.89.0-config_entries_options_flow_handler"
],
"Data Entry Flow": [
"version-0.89.0-data_entry_flow_index"
],
"Entity Registry": [
"version-0.89.0-entity_registry_index"
],
"Device Registry": [
"version-0.89.0-device_registry_index"
],
"Area Registry": [
"version-0.89.0-area_registry_index"
]
},
"version-0.89.0-Extending Frontend": {
"Frontend": [
"version-0.89.0-frontend_index",
"version-0.89.0-frontend_architecture",
"version-0.89.0-frontend_development",
"version-0.89.0-frontend_data",
"version-0.89.0-frontend_external_auth"
],
"Extending the frontend": [
"version-0.89.0-frontend_add_card",
"version-0.89.0-frontend_add_more_info",
"version-0.89.0-frontend_add_websocket_api"
],
"Custom UI": [
"version-0.89.0-lovelace_custom_card",
"version-0.89.0-frontend_creating_custom_ui",
"version-0.89.0-frontend_creating_custom_panels"
]
},
"version-0.89.0-Extending HASS": {
"Developing a feature": [
"version-0.89.0-development_index",
"version-0.89.0-development_environment",
"version-0.89.0-development_submitting",
"version-0.89.0-development_checklist",
"version-0.89.0-development_guidelines",
"version-0.89.0-development_testing",
"version-0.89.0-development_catching_up",
"version-0.89.0-development_validation",
"version-0.89.0-development_typing"
],
"Development 101": [
"version-0.89.0-dev_101_index",
"version-0.89.0-dev_101_hass",
"version-0.89.0-dev_101_events",
"version-0.89.0-dev_101_states",
"version-0.89.0-dev_101_services",
"version-0.89.0-dev_101_config"
],
"Integration Quality Scale": [
"version-0.89.0-integration_quality_scale_index"
],
"Creating Platforms": [
"version-0.89.0-creating_platform_index",
"version-0.89.0-creating_platform_code_review",
"version-0.89.0-creating_platform_example_light",
"version-0.89.0-creating_platform_example_sensor"
],
"Creating Components": [
"version-0.89.0-creating_component_index",
"version-0.89.0-creating_component_code_review",
"version-0.89.0-creating_component_deps_and_reqs",
"version-0.89.0-creating_component_events",
"version-0.89.0-creating_component_states",
"version-0.89.0-creating_component_discovery",
"version-0.89.0-creating_component_loading",
"version-0.89.0-creating_component_generic_discovery"
]
},
"version-0.89.0-Misc": {
"Introduction": [
"version-0.89.0-misc"
],
"External API": [
"version-0.89.0-external_api_rest",
"version-0.89.0-external_api_websocket",
"version-0.89.0-external_api_server_sent_events"
],
"Internationalization": [
"version-0.89.0-internationalization_index",
"version-0.89.0-internationalization_backend_localization",
"version-0.89.0-internationalization_custom_component_localization",
"version-0.89.0-internationalization_translation"
],
"Documentation": [
"version-0.89.0-documentation_index",
"version-0.89.0-documentation_standards",
"version-0.89.0-documentation_create_page"
],
"Intents": [
"version-0.89.0-intent_index",
"version-0.89.0-intent_firing",
"version-0.89.0-intent_handling",
"version-0.89.0-intent_conversation",
"version-0.89.0-intent_builtin"
],
"Native App Integration": [
"version-0.89.0-app_integration_index",
"version-0.89.0-app_integration_setup",
"version-0.89.0-app_integration_sending_data",
"version-0.89.0-app_integration_webview"
],
"asyncio": [
"version-0.89.0-asyncio_index",
"version-0.89.0-asyncio_101",
"version-0.89.0-asyncio_categorizing_functions",
"version-0.89.0-asyncio_working_with_async"
],
"Hass.io": [
"version-0.89.0-hassio_debugging",
"version-0.89.0-hassio_hass"
],
"Hass.io Add-Ons": [
"version-0.89.0-hassio_addon_index",
"version-0.89.0-hassio_addon_tutorial",
"version-0.89.0-hassio_addon_config",
"version-0.89.0-hassio_addon_communication",
"version-0.89.0-hassio_addon_testing",
"version-0.89.0-hassio_addon_publishing",
"version-0.89.0-hassio_addon_presentation",
"version-0.89.0-hassio_addon_repository",
"version-0.89.0-hassio_addon_security"
],
"Maintainer docs": [
"version-0.89.0-maintenance",
"version-0.89.0-releasing"
]
}
}

View File

@ -1,4 +1,5 @@
[
"0.89.0",
"0.88.0",
"0.87.0",
"0.86.0",