mirror of
https://github.com/home-assistant/developers.home-assistant.git
synced 2025-07-19 07:16:29 +00:00
Mobile App Documentation Improvements (#213)
* Update app_integration_sending_data.md * Provide detailed connection workflow * Add more docs
This commit is contained in:
parent
ba924f7330
commit
fcad7a61e4
237
docs/app_integration_notifications.md
Normal file
237
docs/app_integration_notifications.md
Normal file
@ -0,0 +1,237 @@
|
||||
---
|
||||
title: "Push 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)
|
||||
};
|
||||
}
|
||||
```
|
@ -6,14 +6,26 @@ Once you have registered your app with the mobile app component, you can start i
|
||||
|
||||
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 will need to record which WiFi is the home network, and use a direct connection when connected to the home WiFi 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:
|
||||
@ -21,37 +33,78 @@ The payload format depends on the type of interaction, but it all shares a commo
|
||||
```json5
|
||||
{
|
||||
"type": "<type of message>",
|
||||
// other info
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
If you received a `secret` during registration, you will need to encrypt your message and wrap it in an encrypted message:
|
||||
If you received a `secret` during registration, you **MUST** encrypt your message and put it in the payload like this:
|
||||
|
||||
```json5
|
||||
{
|
||||
"type": "encrypted",
|
||||
"data": "<encrypted message>"
|
||||
"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",
|
||||
"gps": [12.34, 56.78],
|
||||
"gps_accuracy": 120,
|
||||
"battery": 45,
|
||||
"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.
|
||||
| `battery` | int | Percentage of battery the device has left.
|
||||
| `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
|
||||
|
||||
@ -59,12 +112,14 @@ Call a service in Home Assistant.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "call_service",
|
||||
"domain": "light",
|
||||
"service": "turn_on",
|
||||
"service_data": {
|
||||
"entity_id": "light.kitchen"
|
||||
}
|
||||
"type": "call_service",
|
||||
"data": {
|
||||
"domain": "light",
|
||||
"service": "turn_on",
|
||||
"service_data": {
|
||||
"entity_id": "light.kitchen"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -80,11 +135,13 @@ Fire an event in Home Assistant.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "fire_event",
|
||||
"event_type": "my_custom_event",
|
||||
"event_data": {
|
||||
"something": 50
|
||||
}
|
||||
"type": "fire_event",
|
||||
"data": {
|
||||
"event_type": "my_custom_event",
|
||||
"event_data": {
|
||||
"something": 50
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -95,20 +152,24 @@ Fire an event in Home Assistant.
|
||||
|
||||
## Render templates
|
||||
|
||||
> This API is very likely to change in an upcoming release. Support to render multiple templates at once will be added.
|
||||
|
||||
Renders a template and returns the result.
|
||||
Renders one or more templates and returns the result(s).
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "render_template",
|
||||
"template": "Hello {{ name }}, you are {{ states('person.paulus') }}.",
|
||||
"variables": {
|
||||
"name": "Paulus"
|
||||
}
|
||||
"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
|
||||
@ -120,15 +181,18 @@ Update your app registration. Use this if the app version changed or any of the
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "update_registration",
|
||||
"app_data": {
|
||||
"push_notification_key": "abcd"
|
||||
},
|
||||
"app_version": "2.0.0",
|
||||
"device_name": "Robbies iPhone",
|
||||
"manufacturer": "Apple, Inc.",
|
||||
"model": "iPhone XR",
|
||||
"os_version": "23.02",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -136,9 +200,29 @@ 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.
|
||||
| `app_data` | Dict | App data can be used if the app has a supporting component that extends mobile_app functionality.
|
||||
|
||||
## 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"
|
||||
}
|
||||
```
|
||||
|
78
docs/app_integration_sensors.md
Normal file
78
docs/app_integration_sensors.md
Normal file
@ -0,0 +1,78 @@
|
||||
---
|
||||
title: "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 |
|
||||
|
@ -15,15 +15,24 @@ When the address of the instance is known, the app will ask the user to authenti
|
||||
|
||||
## Registering the device
|
||||
|
||||
> This is an experimental feature. We expect to evolve the API in the upcoming releases.
|
||||
|
||||
_This requires Home Assistant 0.89 or later._
|
||||
_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. You can do so by making an authenticated POST request to `/api/mobile_app/devices`. [More info on making authenticated requests.](auth_api.md#making-authenticated-requests)
|
||||
Once you have tokens to authenticate as a user, it's time to register the app with the mobile app component in Home Assistant.
|
||||
|
||||
If you get a 404 when making this request, it means the user does not have the mobile_app component enabled. Prompt the user to enable the `mobile_app` component. The mobile_app component is set up as part of the default Home Assistant configuration.
|
||||
### 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:
|
||||
|
||||
@ -53,33 +62,22 @@ Example payload to send to the registration endpoint:
|
||||
| `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.
|
||||
| `app_data` | | Dict | App data can be used if the app has a supporting component that extends `mobile_app` functionality.
|
||||
|
||||
When you get a 200 response, the mobile app is registered with Home Assistant. The response is a JSON document and will contain the urls on how to interact with the Home Assistant instance. Store this information.
|
||||
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
|
||||
{
|
||||
"webhook_id": "abcdefgh",
|
||||
"secret": "qwerty"
|
||||
"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.
|
||||
| `secret` | string | The secret to use for encrypted communication. Will only be included if encryption is supported by both the app and the Home Assistant instance.
|
||||
|
||||
|
||||
## Encryption
|
||||
|
||||
The mobile app component supports encryption to make sure all communication between the app and Home Assistant is encrypted. The encryption is powered by [libsodium](https://libsodium.gitbook.io). Example to encrypt the message using libsodium on Python:
|
||||
|
||||
```python
|
||||
from nacl.secret import SecretBox
|
||||
from nacl.encoding import Base64Encoder
|
||||
|
||||
data = SecretBox(key).encrypt(
|
||||
payload,
|
||||
encoder=Base64Encoder
|
||||
).decode("utf-8")
|
||||
```
|
||||
|
@ -9,9 +9,15 @@
|
||||
"title": "Native App Integration",
|
||||
"sidebar_label": "Introduction"
|
||||
},
|
||||
"app_integration_notifications": {
|
||||
"title": "Push Notifications"
|
||||
},
|
||||
"app_integration_sending_data": {
|
||||
"title": "Sending data home"
|
||||
},
|
||||
"app_integration_sensors": {
|
||||
"title": "Sensors"
|
||||
},
|
||||
"app_integration_setup": {
|
||||
"title": "Connecting to an instance"
|
||||
},
|
||||
|
@ -135,6 +135,8 @@
|
||||
"app_integration_index",
|
||||
"app_integration_setup",
|
||||
"app_integration_sending_data",
|
||||
"app_integration_sensors",
|
||||
"app_integration_notifications",
|
||||
"app_integration_webview"
|
||||
],
|
||||
"asyncio": [
|
||||
|
Loading…
x
Reference in New Issue
Block a user