Release 0.91.2

This commit is contained in:
Paulus Schoutsen 2019-04-07 00:16:14 -07:00
parent fcad7a61e4
commit 3ac344d1e9
30 changed files with 2156 additions and 2 deletions

View File

@ -102,8 +102,8 @@
"sidebar_label": "Multiple platforms"
},
"creating_component_index": {
"title": "Creating components",
"sidebar_label": "Introduction"
"title": "Creating a Minimal Component",
"sidebar_label": "Minimal Component"
},
"creating_integration_file_structure": {
"title": "Integration File Structure",
@ -1239,6 +1239,101 @@
"version-0.91.0/version-0.91.0-integration_quality_scale_index": {
"title": "Integration Quality Scale",
"sidebar_label": "Introduction"
},
"version-0.91.2/version-0.91.2-app_integration_notifications": {
"title": "Push Notifications"
},
"version-0.91.2/version-0.91.2-app_integration_sending_data": {
"title": "Sending data home"
},
"version-0.91.2/version-0.91.2-app_integration_sensors": {
"title": "Sensors"
},
"version-0.91.2/version-0.91.2-app_integration_setup": {
"title": "Connecting to an instance"
},
"version-0.91.2/version-0.91.2-auth_auth_module": {
"title": "Multi-factor Authentication Modules"
},
"version-0.91.2/version-0.91.2-config_entries_config_flow_handler": {
"title": "Integration Configuration",
"sidebar_label": "Configuration"
},
"version-0.91.2/version-0.91.2-config_entries_index": {
"title": "Config Entries",
"sidebar_label": "Introduction"
},
"version-0.91.2/version-0.91.2-config_entries_options_flow_handler": {
"title": "Integration Configuration Options",
"sidebar_label": "Configuration Options"
},
"version-0.91.2/version-0.91.2-creating_component_code_review": {
"title": "Checklist for creating a component",
"sidebar_label": "Component Checklist"
},
"version-0.91.2/version-0.91.2-creating_component_deps_and_reqs": {
"title": "creating_component_deps_and_reqs"
},
"version-0.91.2/version-0.91.2-creating_component_generic_discovery": {
"title": "Integration with Multiple Platforms",
"sidebar_label": "Multiple platforms"
},
"version-0.91.2/version-0.91.2-creating_component_index": {
"title": "Creating a Minimal Component",
"sidebar_label": "Minimal Component"
},
"version-0.91.2/version-0.91.2-creating_integration_file_structure": {
"title": "Integration File Structure",
"sidebar_label": "File Structure"
},
"version-0.91.2/version-0.91.2-creating_integration_manifest": {
"title": "Integration Manifest",
"sidebar_label": "Manifest"
},
"version-0.91.2/version-0.91.2-creating_platform_code_review": {
"title": "Checklist for creating a platform",
"sidebar_label": "Platform Checklist"
},
"version-0.91.2/version-0.91.2-creating_platform_example_light": {
"title": "creating_platform_example_light"
},
"version-0.91.2/version-0.91.2-creating_platform_example_sensor": {
"title": "creating_platform_example_sensor"
},
"version-0.91.2/version-0.91.2-creating_platform_index": {
"title": "Integration Platforms",
"sidebar_label": "Platforms"
},
"version-0.91.2/version-0.91.2-dev_101_services": {
"title": "Integration Services",
"sidebar_label": "Custom Services"
},
"version-0.91.2/version-0.91.2-dev_101_states": {
"title": "Using States"
},
"version-0.91.2/version-0.91.2-development_checklist": {
"title": "Development Checklist",
"sidebar_label": "Introduction"
},
"version-0.91.2/version-0.91.2-development_guidelines": {
"title": "Style guidelines"
},
"version-0.91.2/version-0.91.2-development_index": {
"title": "Starting with Development",
"sidebar_label": "Introduction"
},
"version-0.91.2/version-0.91.2-development_validation": {
"title": "Validate the input"
},
"version-0.91.2/version-0.91.2-entity_sensor": {
"title": "Sensor Entity",
"sidebar_label": "Sensor"
},
"version-0.91.2/version-0.91.2-internationalization_translation": {
"title": "Translation"
},
"version-0.91.2/version-0.91.2-maintenance": {
"title": "Maintenance"
}
},
"links": {

View File

@ -0,0 +1,239 @@
---
title: Push Notifications
id: version-0.91.2-app_integration_notifications
original_id: app_integration_notifications
---
The `mobile_app` component has a notify platform built in that allows for a generic way to send push notifications to your users without requiring installation of a external custom component.
## Enabling push notifications
To enable the notify platform for your application, you must set two keys in the `app_data` object during the initial registration or later update of an existing registration.
| Key | Type | Description
| --- | ---- | -----------
| `push_token` | string | A push notification token unique to your users device. For example, this could be a APNS token or a FCM Instance ID/token.
| `push_url` | string | The URL on your server that push notifications will be HTTP POSTed to.
You should advise the user to restart Home Assistant after you set these keys in order for them to see the notify target. It will have the format `notify.mobile_app_<safed_device_name>`.
## Deploying a server component
The notify platform doesn't concern itself with how to notify your users. It simply forwards a notification to your external server where you should actually handle the request.
This approach allows you to maintain full control over your push notification infrastructure.
See the next section of this document for an example server implementation of a push notification forwarder that uses Firebase Cloud Functions and Firebase Cloud Messaging.
Your server should accept a HTTP POST payload like this:
```json
{
"message": "Hello World",
"title": "Test message sent via mobile_app.notify",
"push_token": "my-secure-token",
"registration_info": {
"app_id": "io.home-assistant.iOS",
"app_version": "1.0.0",
"os_version": "12.2"
},
"data": {
"key": "value"
}
}
```
It should respond with a 201 status code assuming the notification was queued for delivery successfully.
### Errors
If an error occurs you should return a description of what went wrong with a status code _other than_ 201 or 429. An error response must be a JSON object and can contain one of the following keys:
| Key | Type | Description
| --- | ---- | -----------
| `errorMessage` | string | If provided, it will be appended to a preset error message. For example, if `errorMessage` is "Could not communicate with Apple" it will be output in the log like "Internal server error, please try again later: Could not communicate with Apple"
| `message` | string | If provided, it will be output directly to the logs at the warning log level.
No matter what key you use, you should try to be as descriptive as possible about what went wrong and, if possible, how the user can fix it.
### Rate limits
The notify platform also supports exposing rate limits to users. Home Assistant suggests you implement a conservative rate limit to keep your costs low and also so that users don't overload themselves with too many notifications.
For reference, Home Assistant Companion has a maximum sendable notifications per 24 hours of 150 notifications. The rate limit resets for all users at midnight, UTC. You of course are free to use whatever configuration for your own rate limiting.
If you choose to implement rate limiting, your successful server response should look like the following:
```json
{
"rateLimits": {
"successful": 1,
"errors": 5,
"maximum": 150,
"resetsAt": "2019-04-08T00:00:00.000Z"
}
}
```
| Key | Type | Description
| --- | ---- | -----------
| `successful` | integer | The number of successful push notifications the user has sent during the rate limit period.
| `errors` | integer | The number of failed push notifications the user has sent during the rate limit period.
| `maximum` | integer | The maximum number of push notifications the user can send during the users rate limit period.
| `resetsAt` | ISO8601 timestamp | The timestamp that the users rate limit period expires at. Must be provided in the UTC timezone.
The rate limits will be output to the log at the warning log level after every notification is successfully sent. Home Assistant will also output the exact time remaining until the rate limit period resets.
Once the user hits their maximum amount of notifications sent in the rate limit period, you should start responding with a 429 status code until the rate limit period expires. The response object can optionally contain a key, `message` which will be output to the Home Assistant log instead of the standard error message.
The notify platform does not itself implement any kind of rate limit protections. Users will be able to keep sending you notifications, so you should reject them with a 429 status code as early in your logic as possible.
## Example server implementation
The below code is a Firebase Cloud Function that forwards notifications to Firebase Cloud Messaging. To deploy this, you should create a new Firestore database named `rateLimits`. Then, you can deploy the following code.
Also, ensure that you have properly configured your project with the correct authentication keys for APNS and FCM.
```javascript
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
var db = admin.firestore();
const MAX_NOTIFICATIONS_PER_DAY = 150;
exports.sendPushNotification = functions.https.onRequest(async (req, res) => {
console.log('Received payload', req.body);
var today = getToday();
var token = req.body.push_token;
var ref = db.collection('rateLimits').doc(today).collection('tokens').doc(token);
var payload = {
notification: {
body: req.body.message,
},
token: token,
};
if(req.body.title) {
payload.notification.title = req.body.title;
}
if(req.body.data) {
if(req.body.data.android) {
payload.android = req.body.data.android;
}
if(req.body.data.apns) {
payload.apns = req.body.data.apns;
}
if(req.body.data.data) {
payload.data = req.body.data.data;
}
if(req.body.data.webpush) {
payload.webpush = req.body.data.webpush;
}
}
console.log('Notification payload', JSON.stringify(payload));
var docExists = false;
var docData = {
deliveredCount: 0,
errorCount: 0,
totalCount: 0,
};
try {
let currentDoc = await ref.get();
docExists = currentDoc.exists;
if(currentDoc.exists) {
docData = currentDoc.data();
}
} catch(err) {
console.error('Error getting document!', err);
return handleError(res, 'getDoc', err);
}
if(docData.deliveredCount > MAX_NOTIFICATIONS_PER_DAY) {
return res.status(429).send({
errorType: 'RateLimited',
message: 'The given target has reached the maximum number of notifications allowed per day. Please try again later.',
target: token,
rateLimits: getRateLimitsObject(docData),
});
}
docData.totalCount = docData.totalCount + 1;
var messageId;
try {
messageId = await admin.messaging().send(payload);
docData.deliveredCount = docData.deliveredCount + 1;
} catch(err) {
docData.errorCount = docData.errorCount + 1;
await setRateLimitDoc(ref, docExists, docData, res);
return handleError(res, 'sendNotification', err);
}
console.log('Successfully sent message:', messageId);
await setRateLimitDoc(ref, docExists, docData, res);
return res.status(201).send({
messageId: messageId,
sentPayload: payload,
target: token,
rateLimits: getRateLimitsObject(docData),
});
});
async function setRateLimitDoc(ref, docExists, docData, res) {
try {
if(docExists) {
console.log('Updating existing doc!');
await ref.update(docData);
} else {
console.log('Creating new doc!');
await ref.set(docData);
}
} catch(err) {
if(docExists) {
console.error('Error updating document!', err);
} else {
console.error('Error creating document!', err);
}
return handleError(res, 'setDocument', err);
}
return true;
}
function handleError(res, step, incomingError) {
if (!incomingError) return null;
console.error('InternalError during', step, incomingError);
return res.status(500).send({
errorType: 'InternalError',
errorStep: step,
message: incomingError.message,
});
}
function getToday() {
var today = new Date();
var dd = String(today.getDate()).padStart(2, '0');
var mm = String(today.getMonth() + 1).padStart(2, '0');
var yyyy = today.getFullYear();
return yyyy + mm + dd;
}
function getRateLimitsObject(doc) {
var d = new Date();
return {
successful: (doc.deliveredCount || 0),
errors: (doc.errorCount || 0),
total: (doc.totalCount || 0),
maximum: MAX_NOTIFICATIONS_PER_DAY,
remaining: (MAX_NOTIFICATIONS_PER_DAY - doc.deliveredCount),
resetsAt: new Date(d.getFullYear(), d.getMonth(), d.getDate()+1)
};
}
```

View File

@ -0,0 +1,230 @@
---
title: Sending data home
id: version-0.91.2-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.
If you were provided a Cloudhook URL during registration, you should use that by default and only fall back to a constructed URL as described above if that request fails.
If you were provided a remote UI URL during registration, you should use that as the `instance_url` when constructing a URL and only fallback to the user provided URL if the remote UI URL fails.
To summarize, here's how requests should be made:
1. If you have a Cloudhook URL, use that until a request fails. When a request fails, go to step 2.
2. If you have a remote UI URL, use that to construct a webhook URL: `<remote_ui_url>/api/webhook/<webhook_id>`. When a request fails, go to step 3.
3. Construct a webhook URL using the instance URL provided during setup: `<instance_url>/api/webhook/<webhook_id>`.
## 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 should record which WiFi SSID is the users home network, and use a direct connection when connected to the home WiFi network.
## Interaction basics
### Request
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>",
"data": {}
}
```
If you received a `secret` during registration, you **MUST** encrypt your message and put it in the payload like this:
```json5
{
"type": "encrypted",
"encrypted": true,
"encrypted_data": "<encrypted message>"
}
```
### Response
As a general rule, expect to receive a 200 response for all your requests. There are a few cases in which you will receive another code:
- You will receive a 400 status code if your JSON is invalid. However, you will not receive this error if the encrypted JSON is invalid.
- You will receive a 201 when creating a sensor
- If you receive a 404, the `mobile_app` component most likely isn't loaded.
- Receiving a 410 means the integration has been deleted. You should notify the user and most likely register again.
## Implementing encryption
`mobile_app` supports two way encrypted communication via [Sodium](https://libsodium.gitbook.io/doc/).
> Sodium is a modern, easy-to-use software library for encryption, decryption, signatures, password hashing and more.
### Choosing a library
Libraries that wrap Sodium exist for most modern programming languages and platforms. Sodium itself is written in C.
Here are the libraries we suggest using, although you should feel free to use whatever works well for you.
- Swift/Objective-C: [swift-sodium](https://github.com/jedisct1/swift-sodium) (official library maintained by Sodium developers.
For other languages, please see the list of [Bindings for other languages](https://download.libsodium.org/doc/bindings_for_other_languages). If more than one choice is available, we recommend using the choice most recently updated.
### Configuration
We use the [secret-key cryptography](https://download.libsodium.org/doc/secret-key_cryptography) features of Sodium to encrypt and decrypt payloads. All payloads are JSON encoded in Base64. For Base64 type, use `sodium_base64_VARIANT_ORIGINAL` (that is, "original", no padding, not URL safe).
### Signaling encryption support
During registration, you must set `supports_encryption` to `true` to enable encryption. The Home Assistant instance must be able to install `libsodium` to enable encryption. Confirm that you should make all future webhook requests encrypted by the presence of the key `secret` in the initial registration response.
You must store this secret forever. There is no way to recover it via the Home Assistant UI and you should **not** ask users to investigate hidden storage files to re-enter the encryption key. You should create a new registration if encryption ever fails and alert the user.
## Update device location
This message will inform Home Assistant of new location information.
```json
{
"type": "update_location",
"data": {
"gps": [12.34, 56.78],
"gps_accuracy": 120,
"battery": 45
}
}
```
| Key | Type | Description
| --- | ---- | -----------
| `location_name` | string | Name of the zone the device is in.
| `gps` | latlong | Current location as latitude and longitude.
| `gps_accuracy` | int | GPS accurracy in meters. Must be greater than 0.
| `battery` | int | Percentage of battery the device has left. Must be greater than 0.
| `speed` | int | Speed of the device in meters per second. Must be greater than 0.
| `altitude` | int | Altitude of the device in meters. Must be greater than 0.
| `course` | int | The direction in which the device is traveling, measured in degrees and relative to due north. Must be greater than 0.
| `vertical_accuracy` | int | The accuracy of the altitude value, measured in meters. Must be greater than 0.
## Call a service
Call a service in Home Assistant.
```json
{
"type": "call_service",
"data": {
"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 name
| `service_data` | dict | The data to send to the service
## Fire an event
Fire an event in Home Assistant.
```json
{
"type": "fire_event",
"data": {
"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
Renders one or more templates and returns the result(s).
```json
{
"type": "render_template",
"data": {
"my_tpl": {
"template": "Hello {{ name }}, you are {{ states('person.paulus') }}.",
"variables": {
"name": "Paulus"
}
}
}
}
```
`data` must contain a map of `key`: `dictionary`. Results will be returned like `{"my_tpl": "Hello Paulus, you are home"}`. This allows for rendering multiple templates in a single call.
| 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",
"data": {
"app_data": {
"push_token": "abcd",
"push_url": "https://push.mycool.app/push"
},
"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_data` | Dict | App data can be used if the app has a supporting component that extends mobile_app functionality or wishes to enable the notification platform.
| `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.
## Get zones
Get all enabled zones.
```json
{
"type": "get_zones"
}
```
## Get config
Returns a version of `/api/config` with values useful for configuring your app.
```json
{
"type": "get_config"
}
```

View File

@ -0,0 +1,80 @@
---
title: Sensors
id: version-0.91.2-app_integration_sensors
original_id: app_integration_sensors
---
The `mobile_app` component supports exposing custom sensors that can be managed entirely via your app.
## Registering a sensor
All sensors must be registered before they can get updated. You can only register one sensor at a time, unlike updating sensors.
To register a sensor, make a request to the webhook like this:
```json
{
"data": {
"attributes": {
"foo": "bar"
},
"device_class": "battery",
"icon": "mdi:battery",
"name": "Battery State",
"state": "12345",
"type": "sensor",
"unique_id": "battery_state",
"unit_of_measurement": "%"
},
"type": "register_sensor"
}
```
The valid keys are:
| Key | Type | Required | Description |
|---------------------|-------------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| attributes | object | No | Attributes to attach to the sensor |
| device_class | string | No | One of the valid device classes. [Binary Sensor Classes](https://www.home-assistant.io/components/binary_sensor/#device-class), [Sensor Classes](https://www.home-assistant.io/components/sensor/#device-class) |
| icon | Material Design Icon (string) | No | Must be prefixed `mdi:`. If not provided, default value is `mdi:cellphone` |
| name | string | Yes | The name of the sensor |
| state | bool, float, int, string | Yes | The state of the sensor |
| type | string | Yes | The type of the sensor. Must be one of `binary_sensor` or `sensor` |
| unique_id | string | Yes | A identifier unique to this installation of your app. You'll need this later. Usually best when its a safe version of the sensor name |
| unit_of_measurement | string | No | The unit of measurement for the sensor |
Sensors will appear as soon as they are registered.
## Updating a sensor
Once a sensor has been registered, you need to update it. This is very similar to registering it, but you can update all your sensors at the same time.
For example, to update the sensor we registered above, you would send this:
```json
{
"data": [
{
"attributes": {
"hello": "world"
},
"icon": "mdi:battery",
"state": 123,
"type": "sensor",
"unique_id": "battery_state"
}
],
"type": "update_sensor_states"
}
```
Only some of the keys are allowed during updates:
| Key | Type | Required | Description |
|---------------------|-------------------------------|----------|---------------------------------------------------------------------------------------------------------------------------------------|
| attributes | object | No | Attributes to attach to the sensor |
| icon | Material Design Icon (string) | No | Must be prefixed `mdi:` |
| state | bool, float, int, string | Yes | The state of the sensor |
| type | string | Yes | The type of the sensor. Must be one of `binary_sensor` or `sensor` |
| unique_id | string | Yes | A identifier unique to this installation of your app. You'll need this later. Usually best when its a safe version of the sensor name |

View File

@ -0,0 +1,85 @@
---
title: Connecting to an instance
id: version-0.91.2-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/devices`. [More info on making authenticated requests.](auth_api.md#making-authenticated-requests)
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. 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.

View File

@ -0,0 +1,63 @@
---
title: Multi-factor Authentication Modules
id: version-0.91.2-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
![Multi Factor Authentication Workflow](/img/en/auth/mfa_workflow.png)
## 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,93 @@
---
title: Integration Configuration
sidebar_label: Configuration
id: version-0.91.2-config_entries_config_flow_handler
original_id: config_entries_config_flow_handler
---
> This option is currently only available for built-in components.
Integrations can be set up via the user interface by adding support for config entries. Config entries uses the [data flow entry framework](data_entry_flow_index.md) to allow users to create entries. 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.
To register your config flow handler with Home Assistant, register it with the config entries `HANDLERS` registry:
```python
from homeassistant import config_entries
@config_entries.HANDLERS.register(DOMAIN)
class ExampleConfigFlow(config_entries.ConfigFlow):
```
All config flow handlers will also need to add their domain name to the `FLOWS` constant in `homeassistant/config_entries.py`.
## Discovering your config flow
Home Assistant has a discovery integration that scans the network for available devices and services and will trigger the config flow of the appropriate integration. Discovery is limited to UPnP and zeroconf/mDNS.
To have your integration be discovered, you will have to extend the [NetDisco library](https://github.com/home-assistant/netdisco) to be able to find your device. This is done by adding a new discoverable. [See the repository for examples of existing discoverable.](https://github.com/home-assistant/netdisco/tree/master/netdisco/discoverables)
Once done, you will have to update the discovery integration to make it aware which discovery maps to which integration, by updating [this list](https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/discovery/__init__.py#L55).
Finally, you will have to add support to your config flow to be triggered from discovery. This is done by adding a new discovery step. Make sure that your discovery step does not automatically create an entry. All discovered config flows are required to have a confirmation from the user.
Once discovered, the user will be notified that they can continue the flow from the config panel.
```python
@config_entries.HANDLERS.register(DOMAIN)
class ExampleConfigFlow(data_entry_flow.FlowHandler):
async def async_step_discovery(self, info):
# Handle discovery info
```
## 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![Location of button on bridge](/static/images/config_philips_hue.jpg)"
}
},
"error": {
"register_failed": "Failed to register, please try again",
"linking": "Unknown linking error occurred."
},
"abort": {
"discover_timeout": "Unable to discover Hue bridges",
"no_bridges": "No Philips Hue bridges discovered",
"all_configured": "All Philips Hue bridges are already configured",
"unknown": "Unknown error occurred",
"cannot_connect": "Unable to connect to the bridge",
"already_configured": "Bridge is already configured"
}
}
}
```
When the translations are merged into Home Assistant, they will be automatically uploaded to [Lokalise](https://lokalise.co/) where the translation team will help to translate them in other languages. [More info on translating Home Assistant.](internationalization_translation.md)
## Triggering other config flows
If you are writing an integration that discovers other integrations, you will want to trigger their config flows so the user can set them up. Do this by passing a source parameter and optional user input when initializing the config entry:
```python
await hass.config_entries.flow.async_init(
'hue', data=discovery_info,
context={'source': config_entries.SOURCE_DISCOVERY})
```

View File

@ -0,0 +1,139 @@
---
title: Config Entries
sidebar_label: Introduction
id: version-0.91.2-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-&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)).
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."""
```

View File

@ -0,0 +1,47 @@
---
title: Integration Configuration Options
sidebar_label: Configuration Options
id: version-0.91.2-config_entries_options_flow_handler
original_id: config_entries_options_flow_handler
---
> This option is currently only available for built-in components.
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 a 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, 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,70 @@
---
title: Checklist for creating a component
sidebar_label: Component Checklist
id: version-0.91.2-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. 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.
4. Requirements should only be imported inside functions. 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. 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.

View File

@ -0,0 +1,9 @@
---
title: creating_component_deps_and_reqs
id: version-0.91.2-creating_component_deps_and_reqs
original_id: creating_component_deps_and_reqs
---
[This page has moved.](creating_integration_manifest.md)
<script>document.location = 'creating_integration_manifest.html';</script>

View File

@ -0,0 +1,23 @@
---
title: Integration with Multiple Platforms
sidebar_label: Multiple platforms
id: version-0.91.2-creating_component_generic_discovery
original_id: creating_component_generic_discovery
---
Most integrations consist of a single platform. And in that case, it's fine to just define that one platform. However, if you are going to add a second platform, you will want to centralize your connection logic. This is done inside the component (`__init__.py`).
If your integration is configurable via `configuration.yaml`, it will cause the entry point of your configuration to change, as now users will need to set up your integration directly, and it is up to your integration to set up the platforms.
## Loading platforms when configured via a config entry
If your integration is set up via a config entry, you will need to forward the config entry to the appropriate integration to set up your platform. For more info, see the [config entry documentation](config_entries_index.md#for-platforms).
## Loading platforms when configured via configuration.yaml
If your integration is not using config entries, it will have to use our discovery helpers to set up its platforms. Note, this approach does not support unloading.
To do this, you will need to use the `load_platform` and `async_load_platform` methods from the discovery helper.
- See also a [full example that implementing this logic](https://github.com/home-assistant/example-custom-config/tree/master/custom_components/example_load_platform/)

View File

@ -0,0 +1,40 @@
---
title: Creating a Minimal Component
sidebar_label: Minimal Component
id: version-0.91.2-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.
More extensive examples of integrations are available from [our example repository](https://github.com/home-assistant/example-custom-config/tree/master/custom_components/).
## The code
Each component needs to have 2 basic parts: it needs to 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. So let's take a look at how this looks:
```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
```
That's it! If you load this, you will see a new state in the state machine.
To load this, add `hello_state:` to your `configuration.yaml` file and create a file `<config_dir>/custom_components/hello_state/__init__.py` with the below code to test it locally.

View File

@ -0,0 +1,33 @@
---
title: Integration File Structure
sidebar_label: File Structure
id: version-0.91.2-creating_integration_file_structure
original_id: creating_integration_file_structure
---
Each integration is stored inside a directory named after the integration 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`. So all files for this integration are in the folder `mobile_app/`.
The bare minimum content of this folder looks like this:
- `manifest.json`: The manifest file describes the integration and its dependencies. [More info](creating_integration_manifest.md)
- `__init__.py`: The component file. If the integration only offers a platform, you can keep this file limited to a docstring introducing the integration `"""The Mobile App integration."""`.
## Integrating devices - `light.py`, `switch.py` etc
If your integration is going to integrate one or more devices, you will need to do this by creating a platform that interacts with an entity integration. For example, if you want to represent a light device inside Home Assistant, you will create `light.py`, which will contain a light platform for the light integration.
- More info on [available entity integrations](entity_index.md).
- More info on [creating platforms](creating_platform_index.md).
## Integrating services - `services.yaml`
If your integration is going to register services, it will need to provide a description of the available services. The description is stored in `services.yaml`. [More information about `services.yaml`.](dev_101_services.md)
## Where Home Assistant looks for integrations
Home Assistant will look for an integration when it sees the domain referenced in the config file (i.e. `mobile_app:`) or if it is a dependency of another integration. Home Assistant will look at the following locations:
* `<config directory>/custom_components/<domain>`
* `homeassistant/components/<domain>` (built-in integrations)
You can override a built-in integration by having an integration with the same domain in your `config/custom_components` folder. Note that overriding built-in components is not recommended as you will no longer get updates. It is recommended to pick a unique name.

View File

@ -0,0 +1,79 @@
---
title: Integration Manifest
sidebar_label: Manifest
id: version-0.91.2-creating_integration_manifest
original_id: creating_integration_manifest
---
Each 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, including 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"]
}
```
Or a minimal example that you can copy into your project:
```json
{
"domain": "your_domain_name",
"name": "Your Intgration",
"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.
## 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 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 a list of strings. Each entry is a `pip` compatible string. For example, the media player Cast platform depends on the Python package PyChromecast v0.6.12: `['pychromecast==0.6.12']`.
> Because of how Home Assistant installs requirements on demand, actual Python imports of your requirements should be done inside functions instead of at the root level of your Python files.
### 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:
```bash
pip install pychromecast==0.6.13 --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`:
```bash
git clone https://github.com/balloob/pychromecast.git
pip install ./pychromecast
hass --skip-pip
```

View File

@ -0,0 +1,93 @@
---
title: Checklist for creating a platform
sidebar_label: Platform Checklist
id: version-0.91.2-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.
4. Requirements should only be imported inside functions. 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 in [`manifest.json`](creating_integration_manifest.md): `"dependencies": ['nest']`. The `DEPENDENCIES` constant is deprecated.
### 3. Configuration
1. Voluptuous schema present 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,
})
```
### 4. 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).
### 5. Entity
1. Extend the entity from the integration you're building a platform for.
```python
from homeassistant.components.light import Light
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 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.
### 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()
```
[Tutorial on publishing your own PyPI package](https://jeffknupp.com/blog/2013/08/16/open-sourcing-a-python-project-the-right-way/)

View File

@ -0,0 +1,9 @@
---
title: creating_platform_example_light
id: version-0.91.2-creating_platform_example_light
original_id: creating_platform_example_light
---
[This page has moved.](https://github.com/home-assistant/example-custom-config/tree/master/custom_components/example_light)
<script>document.location = 'https://github.com/home-assistant/example-custom-config/tree/master/custom_components/example_light';</script>

View File

@ -0,0 +1,9 @@
---
title: creating_platform_example_sensor
id: version-0.91.2-creating_platform_example_sensor
original_id: creating_platform_example_sensor
---
[This page has moved.](https://github.com/home-assistant/example-custom-config/tree/master/custom_components/example_sensor)
<script>document.location = 'https://github.com/home-assistant/example-custom-config/tree/master/custom_components/example_sensor';</script>

View File

@ -0,0 +1,23 @@
---
title: Integration Platforms
sidebar_label: Platforms
id: version-0.91.2-creating_platform_index
original_id: creating_platform_index
---
Home Assistant has various built-in integrations that abstract device types. There are [lights](entity_light.md), [switches](entity_switch.md), [covers](entity_cover.md), [climate devices](entity_climate.md), and [many more](entity_index.md). Your integration can hook into these integrations by creating a platform. You will need a platform for each integration that you are integrating with.
To create a platform, you will need to create a file with the domain name of the integration that you are building a platform for. So if you are building a light, you will add a new file `light.py` to your integration folder.
We have created two example integrations that should give you a look at how this works:
- [Example sensor platform](https://github.com/home-assistant/example-custom-config/tree/master/custom_components/example_sensor/): hello world of platforms.
- [Example light platform](https://github.com/home-assistant/example-custom-config/tree/master/custom_components/example_light/): showing best practices.
### Interfacing with devices
One Home Assistant rule is that the integration should never interface directly with devices. Instead, it should interact with a third-party Python 3 library. This way, Home Assistant can share code with the Python community and keep the project maintainable.
Once you have your Python library ready and published to PyPi, add it to the [manifest](creating_integration_manifest.md). It will now be time to implement the Entity base class that is provided by the integration that you are creating a platform for.
Find your integration at the [entity index](entity_index.md) to see what methods and properties are available to implement.

View File

@ -0,0 +1,78 @@
---
title: Integration Services
sidebar_label: Custom Services
id: version-0.91.2-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'
```

View File

@ -0,0 +1,130 @@
---
title: Using States
id: version-0.91.2-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://home-assistant.io/cookbook/python_component_basic_state/
"""
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.
```bash
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:
```
@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 in the `homeassistant/component/` directory of your fork and create a Pull Request.

View File

@ -0,0 +1,17 @@
---
title: Development Checklist
sidebar_label: Introduction
id: version-0.91.2-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).
- New dependencies are added to `requirements_all.txt` (if applicable), using `script/gen_requirements_all.py`
- New codeowners are added to `CODEOWNERS` (if applicable), using `script/manifest/codeowners.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](https://home-assistant.io/)
* 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).
- All dependencies are only imported inside functions that use them.

View File

@ -0,0 +1,84 @@
---
title: Style guidelines
id: version-0.91.2-development_guidelines
original_id: development_guidelines
---
Home Assistant enforces 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 automatically test every pull request as part of the linting process.
Summary of the most relevant points:
- Line length is limited to 79 characters (see below).
- Use 4 spaces per indentation level. We don't use tabs.
- 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.
- Avoid trailing whitespace but surround binary operators with a single space.
- Line separator should be set to `LF`.
The maximum line length comes directly from the [PEP8 style guide](https://www.python.org/dev/peps/pep-0008/#maximum-line-length), and is also used by the Python standard library. All code must pass these linting checks, and no exceptions will be made. There have already been numerous requests to increase the maximum line length, but after evaluating the options, the Home Assistant maintainers have decided to stay at 79 characters. This decision is final.
Those points may require that you adjust your IDE or editor settings.
## 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 unified.
### Quotes
Use single quotes `'` for single word and `"` for multiple words or sentences.
```python
ATTR_WATERLEVEL = 'level'
CONF_ATTRIBUTION = "Data provided by the WUnderground weather service"
SENSOR_TYPES = {
'alerts': ['Alerts', None],
}
```
### 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. Try to avoid brackets and additional quotes around the output to make it easier for users to parse the log. A widely 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)
```
```bash
2017-05-01 14:28:07 ERROR [homeassistant.components.sensor.arest] No route to device: 192.168.0.18
```
Don't print out wrong API keys, tokens, usernames, or passwords.
Also note that `_LOGGER.info` is reserved for the core, use `_LOGGER.debug` in anything else.
### Ordering of imports
Instead of order the imports manually, use [`isort`](https://github.com/timothycrosley/isort).
```bash
$ pip3 install isort
$ isort homeassistant/components/sensor/fixer.py
```
### Use new style string formatting
Prefer [new style string formatting](https://www.python.org/dev/peps/pep-3101/) over old.
```python
"{} {}".format('New', 'style')
"%s %s" % ('Old', 'style')
```
Except when doing logging here the format is:
```python
_LOGGER.info("Can't connect to the webservice %s at %s", string1, string2)
```

View File

@ -0,0 +1,13 @@
---
title: Starting with Development
sidebar_label: Introduction
id: version-0.91.2-development_index
original_id: development_index
---
Home Assistant is built from the ground up to be easily extensible using integrations. In this section, we're focusing on how to develop integrations.
Before you start, make sure that you have read up on the [Home Assistant architecture](architecture_index.md) so that you are familiar with the concepts that make up Home Assistant.
If you run into trouble following this documentation, don't hesitate to join our #devs_backend channel on [Discord](https://www.home-assistant.io/join-chat/).

View File

@ -0,0 +1,82 @@
---
title: Validate the input
id: version-0.91.2-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)]),
```

View File

@ -0,0 +1,33 @@
---
title: Sensor Entity
sidebar_label: Sensor
id: version-0.91.2-entity_sensor
original_id: entity_sensor
---
A sensor is a read-only entity that provides some information. Information has a value and optionally, a unit of measurement.
## 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** | The value of the sensor.
| unit_of_measurement | string | `None` | The unit of measurement that the sensor is expressed in.
| device_class | string | `None` | Type of sensor.
### Available device classes
If specifying a device class, your sensor entity will need to also return the correct unit of measurement.
| Type | Unit | Description
| ---- | ---- | -----------
| battery | % | % of battery that is left.
| humidity | % | % of humidity in the air.
| illuminance | lx/lm | Light level.
| signal_strength | dB/dBm | Signal strength.
| temperature | °C/°F | Temperature.
| timestamp | ISO8601 | Timestamp.
| power | W,kW | Power.
| pressure | hPa,mbar | Pressure.

View File

@ -0,0 +1,42 @@
---
title: Translation
id: version-0.91.2-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/)
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,47 @@
---
title: Maintenance
id: version-0.91.2-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`.
```bash
$ 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`.
```bash
$ 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_*.txt` files are tracked by [Requires.io](https://requires.io/github/home-assistant/home-assistant/requirements/?branch=dev).
If you update the requirements of a component/platform by updating `manifest.json`, run the provided script to update the `requirements_*.txt` file(s).
```bash
$ 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.

View File

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

View File

@ -1,4 +1,5 @@
[
"0.91.2",
"0.91.0",
"0.90.0",
"0.89.0",