This commit is contained in:
Paulus Schoutsen 2019-02-20 09:22:02 -08:00
parent f4a40234f7
commit d700fb24d7
6 changed files with 548 additions and 0 deletions

View File

@ -1107,6 +1107,20 @@
"version-0.87.0-frontend_development": {
"title": "Frontend development",
"sidebar_label": "Development"
},
"version-0.88.0-development_guidelines": {
"title": "Style guidelines"
},
"version-0.88.0-entity_index": {
"title": "Entity",
"sidebar_label": "Introduction"
},
"version-0.88.0-frontend_development": {
"title": "Frontend development",
"sidebar_label": "Development"
},
"version-0.88.0-lovelace_custom_card": {
"title": "Lovelace: Custom Cards"
}
},
"links": {

View File

@ -0,0 +1,95 @@
---
title: Style guidelines
id: version-0.88.0-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 with [Coveralls](https://coveralls.io/github/home-assistant/home-assistant) and [Travis CI](https://travis-ci.org/home-assistant/home-assistant).
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."""
```
### Requirements
Please place [Platform requirements](creating_platform_code_review.md#1-requirements) right after the imports.
```python
[...]
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['xmltodict==0.11.0']
```
### 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,104 @@
---
title: Entity
sidebar_label: Introduction
id: version-0.88.0-entity_index
original_id: entity_index
---
Each device is represented in Home Assistant as an entity. An entity abstracts away the internal working of Home Assistant. As an integrator you don't have to worry about how services or the state machine work. Instead, you extend an entity class and implement the necessary properties and methods for the device type that you're integrating.
Below is an example switch entity that keeps track of their state in memory.
```python
from homeassistant.components.switch import SwitchDevice
class MySwitch(SwitchDevice):
def __init__(self):
self._is_on = False
@property
def name(self):
"""Name of the device."""
return 'My Switch'
@property
def is_on(self):
"""If the switch is currently on or off."""
return self._is_on
def turn_on(self, **kwargs):
"""Turn the switch on."""
self._is_on = True
def turn_off(self, **kwargs):
"""Turn the switch off."""
self._is_on = False
```
That's all there is to it to build a switch entity! Continue reading to learn more or check out the [video tutorial](https://youtu.be/Cfasc9EgbMU?t=737).
## Updating the entity
An entity represents a device. There are various strategies to keep your entity in sync with the state of the device, the most popular one being polling.
### Polling
With polling, Home Assistant will ask the entity from time to time (depending on the update interval of the component) to fetch the latest state. Home Assistant will poll an entity when the `should_poll` property returns `True` (the default value). You can either implement your update logic using `update()` or the async method `async_update()`. This method should fetch the latest state from the device and store it in an instance variable for the properties to return it.
### Subscribing to updates
When you subscribe to updates, your code is responsible for letting Home Assistant know that an update is available. Make sure you have the `should_poll` property return `False`.
Whenever you receive new state from your subscription, you can tell Home Assistant that an update is available by calling `schedule_update_ha_state()` or async callback `async_schedule_update_ha_state()`. Pass in the boolean `True` to the method if you want Home Assistant to call your update method before writing the update to Home Assistant.
## Generic properties
The entity base class has a few properties that are common among all entities in Home Assistant. These can be added to any entity regardless of the type. All these properties are optional and don't need to be implemented.
> Properties should always only return information from memory and not do I/O (like network requests). Implement `update()` or `async_update()` to fetch data.
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| assumed_state | boolean | `False` | Return `True` if the state is based on our assumption instead of reading it from the device.
| available | boolean | `True` | Indicate if Home Assistant is able to read the state and control the underlying device.
| device_state_attributes | dict | `None` | Extra information to store in the state machine. It needs to be information that further explains the state, it should not be static information like firmware version. See [below](entity_index.md#standard_attributes) for details of standard attributes.
| entity_picture | URL | `None` | Url of a picture to show for the entity.
| name | string | `None` | Name of the entity
| should_poll | boolean | `True` | Should Home Assistant check with the entity for an updated state. If set to `False`, entity will need to notify Home Assistant of new updates by calling one of the [schedule update methods](#methods).
| unique_id | string | `None` | A unique identifier for this entity. Needs to be unique within a platform (ie `light.hue`). Should not be configurable by the user or be changeable. [Learn more.](entity_registry_index.md#unique-id-requirements)
## Advanced properties
The following properties are also available on entities. However, they are for advanced use only and should be used with caution.
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| force_update | boolean | `False` | Write each update to the state machine, even if the data is the same. Example use: when you are directly reading the value from a connected sensor instead of a cache. Use with caution, will spam the state machine.
| hidden | boolean | `False` | Indicate if the entity should not be shown on the frontend.
| icon | icon | `None` | Icon to use in the frontend. Icons start with `mdi:` plus an [identifier](https://materialdesignicons.com/). You probably don't need this since Home Assistant already provides default icons for all devices.
## Standard attributes
The following `device_state_attributes` are considered standard and should follow the convention below. The constant should be imported from `homeassistant/const.py`.
| Name | Type | Unit | Constant | Description
| ---- | ---- | ---- | -------- | -----------
| battery_charging | boolean | N/A | `ATTR_BATTERY_CHARGING` | Battery charging status of the entity, shown as a boolean `true` or `false`. If charging is not supported, then this attribute should not be created.
| battery_level | integer | % | `ATTR_BATTERY_LEVEL` | Battery level of the entity, shown as an integer percentage between 0-100.
## Lifecycle hooks
Use these lifecycle hooks to execute code when certain events happen to the entity. All lifecycle hooks are async methods.
### `async_added_to_hass()`
Called when an entity has their entity_id and hass object assigned, before it is written to the state machine for the first time. Example uses: restore the state or subscribe to updates.
### `async_will_remove_from_hass()`
Called when an entity is about to be removed from Home Assistant. Example use: disconnect from the server or unsubscribe from updates.
## Changing the entity model
If you want to add a new feature to an entity or any of its subtypes (light, switch, etc), you will need to propose it first in our [architecture repo](https://github.com/home-assistant/architecture/issues). Only additions will be considered that are common features among various vendors.

View File

@ -0,0 +1,107 @@
---
title: Frontend development
sidebar_label: Development
id: version-0.88.0-frontend_development
original_id: frontend_development
---
The Home Assistant frontend is built using web components and powered by the [Polymer](https://www.polymer-project.org/) framework.
> Do not use development mode in production. Home Assistant uses aggressive caching to improve the mobile experience. This is disabled during development so that you do not have to restart the server in between changes.
## Setting up the environment
> All commands below need to be run from inside the home-assistant-polymer repository.
### Getting the code
First step is to fork the [home-assistant-polymer repository][hass-polymer] and add the upstream remote. You can place the forked repository anywhere on your system.
```bash
$ git clone git@github.com:YOUR_GIT_USERNAME/home-assistant-polymer.git
$ cd home-assistant-polymer
$ git remote add upstream https://github.com/home-assistant/home-assistant-polymer.git
```
### Configuring Home Assistant
You will need to have an instance of Home Assistant set up. See our guide on [setting up a development environment](https://developers.home-assistant.io/docs/en/development_environment.html).
Next step is to configure Home Assistant to use the development mode for the frontend. Do this by updating the frontend config in your `configuration.yaml` and set the path to the home-assistant-polymer repository that you cloned in the last step:
```yaml
frontend:
# Example absolute path: /home/paulus/dev/hass/home-assistant-polymer
development_repo: <absolute path to home-assistant-polymer repo>
```
### Installing Node.js
Node.js is required to build the frontend. The preferred method of installing node.js is with [nvm](https://github.com/creationix/nvm). Install nvm using the instructions in the [README](https://github.com/creationix/nvm#install-script), and install the correct node.js by running the following command:
```bash
$ nvm install
```
[Yarn](https://yarnpkg.com/en/) is used as the package manager for node modules. [Install yarn using the instructions here.](https://yarnpkg.com/en/docs/install)
Next, development dependencies need to be installed to bootstrap the frontend development environment. First activate the right Node version and then download all the dependencies:
```bash
$ nvm use
$ script/bootstrap
```
## Development
During development, you will need to run the development script to maintain a development build of the frontend that auto updates when you change any of the source files. To run this server, run:
```bash
$ nvm use
$ script/develop
```
Make sure you have cache disabled and correct settings to avoid stale content:
> Instructions are for Google Chrome
1. Disable cache by ticking the box in `Network` > `Disable cache`
<p class='img'>
<img src='/img/en/development/disable-cache.png' />
</p>
2. Enable Bypass for network in `Application` > `Service Workers` > `Bypass for network`
<p class='img'>
<img src='/img/en/development/bypass-for-network.png' />
</p>
## Creating pull requests
If you're planning on issuing a PR back to the Home Assistant codebase you need to fork the polymer project and add your fork as a remote to the Home Assistant Polymer repo.
```bash
$ git remote add fork <github URL to your fork>
```
When you've made your changes and are ready to push them change to the working directory for the polymer project and then push your changes
``` bash
$ git add -A
$ git commit -m "Added new feature X"
$ git push -u fork HEAD
```
## Building the Polymer frontend
If you're making changes to the way the frontend is packaged, it might be necessary to try out a new packaged build of the frontend in the main repository (instead of pointing it at the frontend repo). To do so, first build a production version of the frontend by running `script/build_frontend`.
To test it out inside Home assistant, run the following command from the main Home Assistant repository:
```bash
$ pip3 install -e /path/to/home-assistant-polymer/
$ hass --skip-pip
```
[hass-polymer]: https://github.com/home-assistant/home-assistant-polymer

View File

@ -0,0 +1,227 @@
---
title: Lovelace: Custom Cards
id: version-0.88.0-lovelace_custom_card
original_id: lovelace_custom_card
---
[Lovelace](https://www.home-assistant.io/lovelace/) is our new approach to defining your user interface for Home Assistant. We offer a lot of built-in cards, but you're not just limited to the ones that we decided to include in the Lovelace UI. You can build and use your own!
## API
You define your custom card as a [custom element](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements). It's up to you to decide how to render your DOM inside your element. You can use Polymer, Angular, Preact or any other popular framework (except for React [more info on React here](https://custom-elements-everywhere.com/#react)).
```js
const element = document.createElement('some-custom-card');
```
Home Assistant will call `setConfig(config)` when the configuration changes (rare). If you throw an exception if the configuration is invalid, Lovelace will render an error card to notify the user.
```js
try {
element.setConfig(config);
} catch (err) {
showErrorCard(err.message, config);
}
```
Home Assistant will set the `hass` property when the state of Home Assistant changes (frequent). Whenever the state changes, the component will have to update itself to represent the latest state.
```js
element.hass = hass;
```
Your card can define a `getCardSize` method that returns the size of your card as a number. A height of 1 is equivalent to 50 pixels. This will help Home Assistant distribute the cards evenly over the columns. A card size of `1` will be assumed if the method is not defined.
```js
if ('getCardSize' in element) {
return element.getCardSize();
} else {
return 1;
}
```
## Defining your card
Create a new file in your Home Assistant config dir as `<config>/www/content-card-example.js` and put in the following contents:
```js
class ContentCardExample extends HTMLElement {
set hass(hass) {
if (!this.content) {
const card = document.createElement('ha-card');
card.header = 'Example card';
this.content = document.createElement('div');
this.content.style.padding = '0 16px 16px';
card.appendChild(this.content);
this.appendChild(card);
}
const entityId = this.config.entity;
const state = hass.states[entityId];
const stateStr = state ? state.state : 'unavailable';
this.content.innerHTML = `
The state of ${entityId} is ${stateStr}!
<br><br>
<img src="http://via.placeholder.com/350x150">
`;
}
setConfig(config) {
if (!config.entity) {
throw new Error('You need to define an entity');
}
this.config = config;
}
// The height of your card. Home Assistant uses this to automatically
// distribute all cards over the available columns.
getCardSize() {
return 3;
}
}
customElements.define('content-card-example', ContentCardExample);
```
## Referencing your new card
In our example card we defined a card with the tag `content-card-example` (see last line), so our card type will be `custom:content-card-example`. And because you created the file in your `<config>/www` directory, it will be accessible in your browser via the url `/local/` (if you have recently added the www folder you will need to re-start home assistant for files to be picked up).
```yaml
# Example Lovelace configuration
resources:
- url: /local/content-card-example.js
type: js
views:
- name: Example
cards:
- type: "custom:content-card-example"
entity: input_boolean.switch_tv
```
## Advanced example
Resources to load in Lovelace can be imported as a JS script, an HTML import or as a JS module import. Below is an example of a custom card using JS modules that does all the fancy things.
![Screenshot of the wired card](/img/en/frontend/lovelace-ui-custom-card-screenshot.png)
Create a new file in your Home Assistant config dir as `<config>/www/wired-cards.js` and put in the following contents:
```js
import "https://unpkg.com/wired-card@0.8.1/wired-card.js?module";
import "https://unpkg.com/wired-toggle@0.8.0/wired-toggle.js?module";
import {
LitElement,
html,
css
} from "https://unpkg.com/lit-element@2.0.1/lit-element.js?module";
function loadCSS(url) {
const link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = url;
document.head.appendChild(link);
}
loadCSS("https://fonts.googleapis.com/css?family=Gloria+Hallelujah");
class WiredToggleCard extends LitElement {
static get properties() {
return {
hass: {},
config: {}
};
}
render() {
return html`
<wired-card elevation="2">
${this.config.entities.map(ent => {
const stateObj = this.hass.states[ent];
return stateObj
? html`
<div class="state">
${stateObj.attributes.friendly_name}
<wired-toggle
.checked="${stateObj.state === "on"}"
@change="${ev => this._toggle(stateObj)}"
></wired-toggle>
</div>
`
: html`
<div class="not-found">Entity ${ent} not found.</div>
`;
})}
</wired-card>
`;
}
setConfig(config) {
if (!config.entities) {
throw new Error("You need to define entities");
}
this.config = config;
}
// The height of your card. Home Assistant uses this to automatically
// distribute all cards over the available columns.
getCardSize() {
return this.config.entities.length + 1;
}
_toggle(state) {
this.hass.callService("homeassistant", "toggle", {
entity_id: state.entity_id
});
}
static get styles() {
return css`
:host {
font-family: "Gloria Hallelujah", cursive;
}
wired-card {
background-color: white;
padding: 16px;
display: block;
font-size: 18px;
}
.state {
display: flex;
justify-content: space-between;
padding: 8px;
align-items: center;
}
.not-found {
background-color: yellow;
font-family: sans-serif;
font-size: 14px;
padding: 8px;
}
wired-toggle {
margin-left: 8px;
}
`;
}
}
customElements.define("wired-toggle-card", WiredToggleCard);
```
And for your configuration:
```yaml
# Example Lovelace configuration
resources:
- url: /local/wired-cards.js
type: module
views:
- name: Example
cards:
- type: "custom:wired-toggle-card"
entities:
- input_boolean.switch_ac_kitchen
- input_boolean.switch_ac_livingroom
- input_boolean.switch_tv
```

View File

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