Initial commit.
This commit is contained in:
commit
c098c93abe
183
README.md
Normal file
183
README.md
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
auto-entities
|
||||||
|
=============
|
||||||
|
|
||||||
|
**Kinda-sorta-experimental-ish**
|
||||||
|
|
||||||
|
This plugin can automatically populate the `entities:` list of a card or entity-row with entities matching a filter.
|
||||||
|
|
||||||
|
> If you've been around the custom lovelace stuff scene for a while,
|
||||||
|
> this function probably feels familliar to you. This plugin is a
|
||||||
|
> reimplementation of the fantastic [`monster-card`](https://github.com/ciotlosm/custom-lovelace/tree/master/monster-card)
|
||||||
|
> by Marius Ciotlos. Differences are outlined below.
|
||||||
|
|
||||||
|
## Installation instructions
|
||||||
|
|
||||||
|
This plugin requires [card-tools](https://github.com/thomasloven/lovelace-card-tools) to be installed.
|
||||||
|
|
||||||
|
For installation instructions [see this guide](https://github.com/thomasloven/hass-config/wiki/Lovelace-Plugins).
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description
|
||||||
|
| ---- | ---- | ------- | -----------
|
||||||
|
| type | string | **Required** | `custom:auto-entities`
|
||||||
|
| card | object | **Required** | The card to display
|
||||||
|
| filter | object | None | Filters for including and excluding entities
|
||||||
|
| entities | list | None | Enties to include
|
||||||
|
| show\_empty | boolean | true | Show/hide empty card
|
||||||
|
|
||||||
|
### filters
|
||||||
|
The `filter` options has two sections, `include` and `exclude`. Each section contains a list of filters.
|
||||||
|
|
||||||
|
Each section has the following options.
|
||||||
|
All options are optional, and filters will match any entity matching **ALL** options.
|
||||||
|
|
||||||
|
| Name | Description
|
||||||
|
| ---- | -----------
|
||||||
|
| domain | Match entity domain (e.g. `light`, `binary_sensor`, `media_player`)
|
||||||
|
| state | Match entity state (e.g. "on", "off", 3.14)
|
||||||
|
| entity\_id | Match entity id (e.g. `light.bed_light`, `binary_sensor.weekdays_only`, `media_player.kitchen`)
|
||||||
|
| name | Match friendly name attribute (e.g. "Kitchen lights", "Front door")
|
||||||
|
| group | Match entities in given group
|
||||||
|
| attributes | Match attributes. **See below**
|
||||||
|
| options | Additional options to attach to entities matching this filter (only makes sense in `include`)
|
||||||
|
|
||||||
|
The attributes option takes an object with `attribute: value` combinations and matches any entity which matches all of those attributes.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
`auto-entities` creates a list of entities by:
|
||||||
|
1. Including every entity given in `entities:` (this allows nesting of `auto-entities` if you'd want to do that for some reason...)
|
||||||
|
2. Include all entities that matches **ALL** options of **ANY** filter in the `filter.include` section.
|
||||||
|
3. Remove all entities that matches **ALL** options of **ANY** filter in the `filter.exclude` section.
|
||||||
|
|
||||||
|
It then creates a card based on the configuration given in `card:` but adds the `entities:` option populated with the entities from above.
|
||||||
|
|
||||||
|
### Matching rules
|
||||||
|
Any filter option can use `*` as a wildcard for string comparison. Remember to quote your strings when doing this:
|
||||||
|
```yaml
|
||||||
|
filter:
|
||||||
|
include:
|
||||||
|
- name: "Bedroom *"
|
||||||
|
- entity_id: "sensor.temperature_*_max"
|
||||||
|
```
|
||||||
|
|
||||||
|
Any filter option can use [javascript Regular Expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) for string comparison. To do this, enclose the regex in `/`. Also make sure to quote the string:
|
||||||
|
```yaml
|
||||||
|
filter:
|
||||||
|
include:
|
||||||
|
- name: "/Bedroom .*/"
|
||||||
|
- entity_id: "/sensor.temperature_4[abd]/"
|
||||||
|
```
|
||||||
|
|
||||||
|
Any filter option dealing with numerical quantities can use comparison operators if specified as a string (must be quoted):
|
||||||
|
```yaml
|
||||||
|
filter:
|
||||||
|
include:
|
||||||
|
- attributes:
|
||||||
|
battery: "<= 50" # Attribute battery_level is 50 or less
|
||||||
|
- state: "> 25" # State is greater than 25
|
||||||
|
- attributes:
|
||||||
|
count: "! 2" # Attribute count is not equal to 2
|
||||||
|
- state: "= 12" # State is exactly 12 (also matches "12", "12.0" etc.)
|
||||||
|
- state: 12 # State is exactly 12 but not "12"
|
||||||
|
```
|
||||||
|
|
||||||
|
Any option can be used more than once by appending a number or string to the option name:
|
||||||
|
```yaml
|
||||||
|
filter:
|
||||||
|
include:
|
||||||
|
- state 1: "> 100"
|
||||||
|
state 2: "< 200"
|
||||||
|
```
|
||||||
|
The filter above matches entities where the state is above 100 **AND** below 200. Compare to the following:
|
||||||
|
```yaml
|
||||||
|
filter:
|
||||||
|
include:
|
||||||
|
- state: "< 100"
|
||||||
|
- state: "> 200"
|
||||||
|
```
|
||||||
|
The two filters above together match entities where the state is below 100 **OR** above 200.
|
||||||
|
|
||||||
|
*Advanced stuff:* You can drill into attributes that are object using keys or indexes separated by `:`:
|
||||||
|
```yaml
|
||||||
|
filter:
|
||||||
|
include:
|
||||||
|
- attributes:
|
||||||
|
hs_color: "1:> 30"
|
||||||
|
```
|
||||||
|
The example above matches lights with a `hs_color` saturation value greater than 30.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Show all with some exceptions
|
||||||
|
```yaml
|
||||||
|
type: custom:auto-entities
|
||||||
|
card:
|
||||||
|
type: glance
|
||||||
|
filter:
|
||||||
|
include: [{}]
|
||||||
|
exclude:
|
||||||
|
- entity_id: "*yweather*"
|
||||||
|
- domain: group
|
||||||
|
- domain: zone
|
||||||
|
```
|
||||||
|
|
||||||
|
Show all in `device_tracker` with battery less than 50:
|
||||||
|
```yaml
|
||||||
|
type: custom:auto-entities
|
||||||
|
card:
|
||||||
|
type: entities
|
||||||
|
title: Battery warning
|
||||||
|
filter:
|
||||||
|
include:
|
||||||
|
- domain: device_tracker
|
||||||
|
options:
|
||||||
|
secondary_info: last-changed
|
||||||
|
attributes:
|
||||||
|
battery: "< 50"
|
||||||
|
source_type: gps
|
||||||
|
```
|
||||||
|
|
||||||
|
Show all lights that are on:
|
||||||
|
```yaml
|
||||||
|
type: custom:auto-entities
|
||||||
|
show_empty: false
|
||||||
|
card:
|
||||||
|
type: glance
|
||||||
|
title: Lights on
|
||||||
|
filter:
|
||||||
|
include:
|
||||||
|
- domain: light
|
||||||
|
state: "on" # Remember that "on" and "off" are magic in yaml, and must always be quoted
|
||||||
|
options:
|
||||||
|
tap_action:
|
||||||
|
action: toggle
|
||||||
|
```
|
||||||
|
Also show all lights that are on:
|
||||||
|
```yaml
|
||||||
|
type: custom:auto-entities
|
||||||
|
show_empty: false
|
||||||
|
card:
|
||||||
|
type: entities
|
||||||
|
title: Lights on
|
||||||
|
show_header_toggle: false
|
||||||
|
filter:
|
||||||
|
include:
|
||||||
|
- domain: light
|
||||||
|
exclude:
|
||||||
|
- state: "off"
|
||||||
|
- state: "unavailable"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## About monster-card
|
||||||
|
This card works very much like [`monster-card`](https://github.com/ciotlosm/custom-lovelace/tree/master/monster-card) with the following exceptions:
|
||||||
|
|
||||||
|
- `auto-entities` has no `when` option. Hiding the card based on the state of an entity is better done with [`conditional`](https://www.home-assistant.io/lovelace/conditional/).
|
||||||
|
- `auto-entities` supports Regular Expressions.
|
||||||
|
- `auto-entities` supports comparison operators for states as well as attributes.
|
||||||
|
- `auto-entities` can add all entities from a group
|
||||||
|
- `auto-entities` works with custom cards.
|
||||||
|
|
||||||
|

|
||||||
|
|
159
auto-entities.js
Normal file
159
auto-entities.js
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
customElements.whenDefined('card-tools').then(() => {
|
||||||
|
class AutoEntities extends cardTools.litElement() {
|
||||||
|
|
||||||
|
setConfig(config) {
|
||||||
|
if(!config || !config.card)
|
||||||
|
throw new Error("Invalid configuration");
|
||||||
|
|
||||||
|
this._config = config;
|
||||||
|
|
||||||
|
this.entities = this.get_entities() || [];
|
||||||
|
this.card = cardTools.createCard({entities: this.entities, ...config.card});
|
||||||
|
}
|
||||||
|
|
||||||
|
match(pattern, str){
|
||||||
|
if (typeof(str) === "string" && typeof(pattern) === "string") {
|
||||||
|
if((pattern.startsWith('/') && pattern.endsWith('/')) || pattern.indexOf('*') !== -1) {
|
||||||
|
if(pattern[0] !== '/')
|
||||||
|
pattern = `/${pattern.replace(/\*/g, '.*')}/`;
|
||||||
|
var regex = new RegExp(pattern.substr(1).slice(0,-1));
|
||||||
|
return regex.test(str);
|
||||||
|
}
|
||||||
|
} else if(typeof(pattern) === "string") {
|
||||||
|
if(pattern.indexOf(":") !== -1 && typeof(str) === "object") {
|
||||||
|
while(pattern.indexOf(":") !== -1)
|
||||||
|
{
|
||||||
|
str = str[pattern.split(":")[0]];
|
||||||
|
pattern = pattern.substr(pattern.indexOf(":")+1, pattern.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(pattern.startsWith("<=")) return str <= parseFloat(pattern.substr(2));
|
||||||
|
if(pattern.startsWith(">=")) return str >= parseFloat(pattern.substr(2));
|
||||||
|
if(pattern.startsWith("<")) return str < parseFloat(pattern.substr(1));
|
||||||
|
if(pattern.startsWith(">")) return str > parseFloat(pattern.substr(1));
|
||||||
|
if(pattern.startsWith("!")) return str != parseFloat(pattern.substr(1));
|
||||||
|
if(pattern.startsWith("=")) return str == parseFloat(pattern.substr(1));
|
||||||
|
}
|
||||||
|
return str === pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
match_filter(hass, entities, filter) {
|
||||||
|
let retval = [];
|
||||||
|
let count = -1;
|
||||||
|
entities.forEach((i) => {
|
||||||
|
count++;
|
||||||
|
if(!hass.states) return;
|
||||||
|
const e = (typeof(i) === "string")?hass.states[i]:hass.states[i.entity];
|
||||||
|
if(!e) return;
|
||||||
|
|
||||||
|
let unmatched = false;
|
||||||
|
Object.keys(filter).forEach((filterKey) => {
|
||||||
|
const key = filterKey.split(" ")[0];
|
||||||
|
const value = filter[filterKey];
|
||||||
|
switch(key) {
|
||||||
|
case "options":
|
||||||
|
break;
|
||||||
|
case "domain":
|
||||||
|
if(!this.match(value, e.entity_id.split('.')[0]))
|
||||||
|
unmatched = true;
|
||||||
|
break;
|
||||||
|
case "state":
|
||||||
|
if(!this.match(value, e.state))
|
||||||
|
unmatched = true;
|
||||||
|
break;
|
||||||
|
case "entity_id":
|
||||||
|
if(!this.match(value, e.entity_id))
|
||||||
|
unmatched = true;
|
||||||
|
break;
|
||||||
|
case "name":
|
||||||
|
if(!e.attributes.friendly_name
|
||||||
|
|| !this.match(value, e.attributes.friendly_name)
|
||||||
|
)
|
||||||
|
unmatched = true;
|
||||||
|
break;
|
||||||
|
case "group":
|
||||||
|
if(!value.startsWith("group.")
|
||||||
|
|| !hass.states[value]
|
||||||
|
|| !hass.states[value].attributes.entity_id
|
||||||
|
|| !hass.states[value].attributes.entity_id.includes(e.entity_id)
|
||||||
|
)
|
||||||
|
unmatched = true;
|
||||||
|
break;
|
||||||
|
case "attributes":
|
||||||
|
Object.keys(value).forEach((entityKey) => {
|
||||||
|
const k = entityKey.split(" ")[0];
|
||||||
|
const v = value[entityKey];
|
||||||
|
if(!e.attributes[k]
|
||||||
|
|| !this.match(v, e.attributes[k])
|
||||||
|
)
|
||||||
|
unmatched = true;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(!unmatched) retval.push(count);
|
||||||
|
});
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_entities()
|
||||||
|
{
|
||||||
|
let entities = [];
|
||||||
|
if(this._config.entities)
|
||||||
|
this._config.entities.forEach((e) => entities.push(e));
|
||||||
|
|
||||||
|
if(this._hass && this._config.filter) {
|
||||||
|
|
||||||
|
if(this._config.filter.include){
|
||||||
|
this._config.filter.include.forEach((f) => {
|
||||||
|
const add = this.match_filter(this._hass, Object.keys(this._hass.states), f)
|
||||||
|
add.forEach((i) => {
|
||||||
|
entities.push({entity: Object.keys(this._hass.states)[i], ...f.options});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this._config.filter.exclude) {
|
||||||
|
this._config.filter.exclude.forEach((f) => {
|
||||||
|
const remove = this.match_filter(this._hass, entities, f);
|
||||||
|
for(var i = remove.length-1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
entities.splice(remove[i],1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if(this.entities.length === 0 && this._config.show_empty === false)
|
||||||
|
return cardTools.litHtml()``;
|
||||||
|
return cardTools.litHtml()`
|
||||||
|
${this.card}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
set hass(hass) {
|
||||||
|
this._hass = hass;
|
||||||
|
const oldlen = this.entities.length;
|
||||||
|
this.entities = this.get_entities() || [];
|
||||||
|
if(this.card)
|
||||||
|
{
|
||||||
|
this.card.hass = this._hass;
|
||||||
|
this.card.setConfig({entities: this.entities, ...this._config.card});
|
||||||
|
}
|
||||||
|
if(this.entities.length != oldlen) this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('auto-entities', AutoEntities);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
if(customElements.get('card-tools')) return;
|
||||||
|
customElements.define('auto-entities', class extends HTMLElement{
|
||||||
|
setConfig() { throw new Error("Can't find card-tools. See https://github.com/thomasloven/lovelace-card-tools");}
|
||||||
|
});
|
||||||
|
}, 2000);
|
Loading…
x
Reference in New Issue
Block a user