Total overhaul!
This commit is contained in:
		
							parent
							
								
									8057bdfd51
								
							
						
					
					
						commit
						05a3d0a9c5
					
				
							
								
								
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
auto-entities.js binary
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
node_modules/
 | 
			
		||||
							
								
								
									
										21
									
								
								LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
MIT License
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2019 Thomas Lovén
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
							
								
								
									
										209
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										209
									
								
								README.md
									
									
									
									
									
								
							@ -1,60 +1,84 @@
 | 
			
		||||
auto-entities
 | 
			
		||||
=============
 | 
			
		||||
 | 
			
		||||
**Kinda-sorta-experimental-ish**
 | 
			
		||||
[](https://github.com/custom-components/hacs)
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
Automatically populate lovelace cards with entities matching certain criteria.
 | 
			
		||||
 | 
			
		||||
For installation instructions [see this guide](https://github.com/thomasloven/hass-config/wiki/Lovelace-Plugins).
 | 
			
		||||
 | 
			
		||||
Install `auto-entities.js` as a `module`.
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
resources:
 | 
			
		||||
  - url: /local/auto-entities.js
 | 
			
		||||
    type: module
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Usage
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
type: custom:auto-entities
 | 
			
		||||
card:
 | 
			
		||||
  <card>
 | 
			
		||||
entities:
 | 
			
		||||
  - <entity>
 | 
			
		||||
  - <entity>
 | 
			
		||||
filter:
 | 
			
		||||
  include:
 | 
			
		||||
    - <filter>
 | 
			
		||||
    - <filter>
 | 
			
		||||
  exclude:
 | 
			
		||||
    - <filter>
 | 
			
		||||
    - <filter>
 | 
			
		||||
 | 
			
		||||
show_empty: <show_empty>
 | 
			
		||||
sort: <sort_method>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 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
 | 
			
		||||
- `card:` **Required.** The card to display. Specify this as you would specify any normal lovelace card, but ommit the `entities:` parameter.
 | 
			
		||||
- `entities:` Any entities added here will be added to the card before any filters are applied.
 | 
			
		||||
- `filter:`
 | 
			
		||||
  - `include:` **Required.** A list of filters specifying which entities to add to the card
 | 
			
		||||
  - `exclude:` A list of filters specifying which entities to remove from the card
 | 
			
		||||
- `show_empty:` Whether to display the card if it has no entities. Default: `true`.
 | 
			
		||||
- `sort:` How to sort the entities of the card. Default: `none`.
 | 
			
		||||
 | 
			
		||||
### filters
 | 
			
		||||
The `filter` options has two sections, `include` and `exclude`. Each section contains a list of filters.
 | 
			
		||||
### Filters
 | 
			
		||||
The two filter sections `include` and `exclude` each takes a list of filters.
 | 
			
		||||
 | 
			
		||||
Each section has the following options.
 | 
			
		||||
All options are optional, and filters will match any entity matching **ALL** options.
 | 
			
		||||
Filters have the following options, and will match any entity fulfilling **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**
 | 
			
		||||
| area | Entity belongs in given area (Home Assistant 0.87 or later)
 | 
			
		||||
| options | Additional options to attach to entities matching this filter (only makes sense in `include`)
 | 
			
		||||
- `domain:` Match entity domain (e.g. `light`, `binary_sensor`, `media_player`)
 | 
			
		||||
- `state:` Match entity state (e.g. `"on"`, `home`, `"3.14"`)
 | 
			
		||||
- `entity_id:` Match entity id (e.g. `light.bed_light`, `input_binary.weekdays_only`)
 | 
			
		||||
- `name:` Match friendly name attribute (e.g. `Kitchen lights`, `Front door`)
 | 
			
		||||
- `group:` Match entities in given group (e.g. `group.living_room_lights`)
 | 
			
		||||
- `area:` Match entities in given area (e.g. `Kitchen`)
 | 
			
		||||
- `device:` Match entities belonging to given device (e.g. `Thomas iPhone`)
 | 
			
		||||
- `attributes:` Map of `attribute: value` pairs to match.
 | 
			
		||||
 | 
			
		||||
The attributes option takes an object with `attribute: value` combinations and matches any entity which matches all of those attributes.
 | 
			
		||||
Special options:
 | 
			
		||||
- `options:` Map of options to apply to entity when passed to card.
 | 
			
		||||
- `type:` Type of special entries to include in entity list. Entries with a `type:` will not be filtered.
 | 
			
		||||
- `not:` Specifies a filter that entities must *not* match.
 | 
			
		||||
- `sort:` Specifies a method to sort entities matched by *this filter only*.
 | 
			
		||||
 | 
			
		||||
## 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.
 | 
			
		||||
1. Including every entitiy given in `entities:` (this allow 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. The same entity may be included several times by different filters.
 | 
			
		||||
3. Remove all entities that matches **ALL** options on **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.
 | 
			
		||||
It then creates a card based on the configuration given in `card:`, and fills in `entities:` of that card with the entities from above.
 | 
			
		||||
 | 
			
		||||
## Matching rules
 | 
			
		||||
 | 
			
		||||
### Wildcards
 | 
			
		||||
Any filter option can use `*` as a wildcard for string comparison. Note that strings must be quoted when doing this:
 | 
			
		||||
 | 
			
		||||
### Matching rules
 | 
			
		||||
Any filter option can use `*` as a wildcard for string comparison. Remember to quote your strings when doing this:
 | 
			
		||||
```yaml
 | 
			
		||||
filter:
 | 
			
		||||
  include:
 | 
			
		||||
@ -62,15 +86,19 @@ filter:
 | 
			
		||||
    - entity_id: "sensor.temperature_*_max"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Regular expressions
 | 
			
		||||
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 .*/"
 | 
			
		||||
    - name: "/^.* [Ll]ight$/"
 | 
			
		||||
    - entity_id: "/sensor.temperature_4[abd]/"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Numerical comparison
 | 
			
		||||
Any filter option dealing with numerical quantities can use comparison operators if specified as a string (must be quoted):
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
filter:
 | 
			
		||||
  include:
 | 
			
		||||
@ -83,7 +111,9 @@ filter:
 | 
			
		||||
    - state: 12 # State is exactly 12 but not "12"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Repeating options
 | 
			
		||||
Any option can be used more than once by appending a number or string to the option name:
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
filter:
 | 
			
		||||
  include:
 | 
			
		||||
@ -91,26 +121,49 @@ filter:
 | 
			
		||||
      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 `:`:
 | 
			
		||||
### Object attributes
 | 
			
		||||
Some entity attributes actually contain several values. One example is `hs_color` for a light, which has one value for Hue and one for Saturation. Such values can be stepped into using keys or indexes separated by a colon (`:`):
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
filter:
 | 
			
		||||
  include:
 | 
			
		||||
    - attributes:
 | 
			
		||||
      hs_color: "1:> 30"
 | 
			
		||||
      hs_color:1: ">30"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The example above matches lights with a `hs_color` saturation value greater than 30.
 | 
			
		||||
 | 
			
		||||
## Sorting entities
 | 
			
		||||
Entities can be sorted, either on a filter-by-filter basis by adding a `sort:` option to the filter, or all at once after all filters have been applied using the `sort:` option of `auto-entities` itself.
 | 
			
		||||
 | 
			
		||||
Sorting methods are specified as:
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
sort:
 | 
			
		||||
  method: <method>
 | 
			
		||||
  reverse: <reverse>
 | 
			
		||||
  ignore_case: <ignore_case>
 | 
			
		||||
  attribute: <attribute>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- `method:` **Required** One of `domain`, `entity_id`, `name`, `state` or `attribute`
 | 
			
		||||
- `reverse:` Set to `true` to reverse the order. Default: `false`.
 | 
			
		||||
- `ignore_case:` Set to `true` to make the sort case-insensitive. Default: `false`.
 | 
			
		||||
- `attribute:` Attribute to sort by if `method: attribute`. Can be an *object attribute* as above (e.g. `attribute: rgb_color:2`)
 | 
			
		||||
 | 
			
		||||
## Examples
 | 
			
		||||
 | 
			
		||||
Show all with some exceptions
 | 
			
		||||
Show all entities, except yahoo weather, groups and zones in a glance card:
 | 
			
		||||
```yaml
 | 
			
		||||
type: custom:auto-entities
 | 
			
		||||
card:
 | 
			
		||||
@ -123,7 +176,7 @@ filter:
 | 
			
		||||
    - domain: zone
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Show all in `device_tracker` with battery less than 50:
 | 
			
		||||
Show all gps `device_tracker`s with battery level less than 50:
 | 
			
		||||
```yaml
 | 
			
		||||
type: custom:auto-entities
 | 
			
		||||
card:
 | 
			
		||||
@ -170,61 +223,23 @@ filter:
 | 
			
		||||
  - 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.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
Show everything that has "light" in its name, but isn't a light, and all switches in the living room:
 | 
			
		||||
```yaml
 | 
			
		||||
type: custom:auto-entities
 | 
			
		||||
show_empty: false
 | 
			
		||||
card:
 | 
			
		||||
  type: entities
 | 
			
		||||
title: Combination
 | 
			
		||||
entities:
 | 
			
		||||
  - type: custom:auto-entities
 | 
			
		||||
    card:
 | 
			
		||||
      type: custom:fold-entity-row
 | 
			
		||||
      head:
 | 
			
		||||
        type: section
 | 
			
		||||
        label: All lights
 | 
			
		||||
  title: Lights on
 | 
			
		||||
  show_header_toggle: false
 | 
			
		||||
filter:
 | 
			
		||||
  include:
 | 
			
		||||
        - domain: light
 | 
			
		||||
  - type: custom:auto-entities
 | 
			
		||||
    card:
 | 
			
		||||
      type: custom:fold-entity-row
 | 
			
		||||
      head:
 | 
			
		||||
        type: section
 | 
			
		||||
        label: Lights that are on
 | 
			
		||||
    filter:
 | 
			
		||||
      include:
 | 
			
		||||
        - domain: light
 | 
			
		||||
          state: "on"
 | 
			
		||||
  - type: custom:auto-entities
 | 
			
		||||
    card:
 | 
			
		||||
      type: custom:fold-entity-row
 | 
			
		||||
      head:
 | 
			
		||||
        type: section
 | 
			
		||||
        label: Lights that are dimmed below 50%
 | 
			
		||||
    filter:
 | 
			
		||||
      include:
 | 
			
		||||
        - domain: light
 | 
			
		||||
          attributes:
 | 
			
		||||
            brightness: "< 125"
 | 
			
		||||
  - type: custom:auto-entities
 | 
			
		||||
    card:
 | 
			
		||||
      type: custom:fold-entity-row
 | 
			
		||||
      head:
 | 
			
		||||
        type: section
 | 
			
		||||
        label: Lights that are kinda blue-ish
 | 
			
		||||
    filter:
 | 
			
		||||
      include:
 | 
			
		||||
        - domain: light
 | 
			
		||||
          attributes:
 | 
			
		||||
            hs_color 1: "0:> 195"
 | 
			
		||||
            hs_color 2: "0:< 255"
 | 
			
		||||
    - name: /[Ll]ight/
 | 
			
		||||
      not:
 | 
			
		||||
        domain: light
 | 
			
		||||
    - type: section
 | 
			
		||||
    - domain: switch
 | 
			
		||||
      area: Living Room
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
<a href="https://www.buymeacoffee.com/uqD6KHCdJ" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/white_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										230
									
								
								auto-entities.js
									
									
									
									
									
								
							
							
						
						
									
										230
									
								
								auto-entities.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										4051
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										4051
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										20
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "auto-entities",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "build": "webpack",
 | 
			
		||||
    "watch": "webpack --watch --mode=development",
 | 
			
		||||
    "test": "echo \"Error: no test specified\" && exit 1"
 | 
			
		||||
  },
 | 
			
		||||
  "author": "Thomas Lovén",
 | 
			
		||||
  "license": "MIT",
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "webpack": "^4.41.2",
 | 
			
		||||
    "webpack-cli": "^3.3.10"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "card-tools": "github:thomasloven/lovelace-card-tools"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										113
									
								
								src/filter.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/filter.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,113 @@
 | 
			
		||||
import { areaByName, areaDevices, deviceByName, deviceEntities } from "card-tools/src/devices";
 | 
			
		||||
 
 | 
			
		||||
function match(pattern, value) {
 | 
			
		||||
    if(typeof(value) === "string" && typeof(pattern) === "string") {
 | 
			
		||||
        if((pattern.startsWith('/') && pattern.endsWith('/')) || pattern.indexOf('*') !== -1) {
 | 
			
		||||
            if(!pattern.startsWith('/')) { // Convert globs to regex
 | 
			
		||||
                pattern = pattern
 | 
			
		||||
                    .replace(/\./g, '\.')
 | 
			
		||||
                    .replace(/\*/g, '.*');
 | 
			
		||||
                pattern = `/^${pattern}$/`;
 | 
			
		||||
            }
 | 
			
		||||
            let regex = new RegExp(pattern.slice(1,-1));
 | 
			
		||||
            return regex.test(value);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(typeof(pattern) === "string") {
 | 
			
		||||
        // Comparisons assume numerical values
 | 
			
		||||
        if(pattern.startsWith("<=")) return parseFloat(value) <= parseFloat(pattern.substr(2));
 | 
			
		||||
        if(pattern.startsWith(">=")) return parseFloat(value) >= parseFloat(pattern.substr(2));
 | 
			
		||||
        if(pattern.startsWith("<")) return parseFloat(value) < parseFloat(pattern.substr(1));
 | 
			
		||||
        if(pattern.startsWith(">")) return parseFloat(value) > parseFloat(pattern.substr(1));
 | 
			
		||||
        if(pattern.startsWith("!")) return parseFloat(value) != parseFloat(pattern.substr(1));
 | 
			
		||||
        if(pattern.startsWith("=")) return parseFloat(value) == parseFloat(pattern.substr(1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return pattern === value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function entity_filter(hass, filter) {
 | 
			
		||||
    return function(e) {
 | 
			
		||||
        const entity = typeof(e) === "string"
 | 
			
		||||
            ? hass.states[e]
 | 
			
		||||
            : hass.states[e.entity];
 | 
			
		||||
        if(!e) return false;
 | 
			
		||||
        for (const [key, value] of Object.entries(filter)) {
 | 
			
		||||
            switch(key.split(" ")[0]) {
 | 
			
		||||
                case "options":
 | 
			
		||||
                case "sort":
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case "domain": 
 | 
			
		||||
                    if(!match(value, entity.entity_id.split('.')[0]))
 | 
			
		||||
                        return false;
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case "entity_id":
 | 
			
		||||
                    if(!match(value, entity.entity_id))
 | 
			
		||||
                        return false;
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case "state":
 | 
			
		||||
                    if(!match(value, entity.state))
 | 
			
		||||
                        return false;
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case "name":
 | 
			
		||||
                    if(!entity.attributes.friendly_name
 | 
			
		||||
                        || !match(value, entity.attributes.friendly_name))
 | 
			
		||||
                        return false;
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case "group":
 | 
			
		||||
                    if(!value.startsWith("group.")
 | 
			
		||||
                        || !hass.states[value]
 | 
			
		||||
                        || !hass.states[value].attributes.entity_id
 | 
			
		||||
                        || !hass.states[value].attributes.entity_id.includes(entity.entity_id)
 | 
			
		||||
                    )
 | 
			
		||||
                        return false;
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case "attributes":
 | 
			
		||||
                    for(const [k, v] of Object.entries(value)) {
 | 
			
		||||
                        let attr = k.split(" ")[0];
 | 
			
		||||
                        let entityAttribute = entity.attributes;
 | 
			
		||||
                        while(attr && entityAttribute) {
 | 
			
		||||
                            let step;
 | 
			
		||||
                            [step, attr] = attr.split(":");
 | 
			
		||||
                            entityAttribute = entityAttribute[step];
 | 
			
		||||
                        }
 | 
			
		||||
                        if(entityAttribute === undefined
 | 
			
		||||
                            || (v && !match(v, entityAttribute))
 | 
			
		||||
                            )
 | 
			
		||||
                            return false;
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                
 | 
			
		||||
                case "not":
 | 
			
		||||
                    if(entity_filter(hass,value)(e))
 | 
			
		||||
                        return false;
 | 
			
		||||
                    break;
 | 
			
		||||
                
 | 
			
		||||
                case "device":
 | 
			
		||||
                    const _deviceEntities = deviceEntities(deviceByName(value));
 | 
			
		||||
                    if(!_deviceEntities.includes(entity.entity_id))
 | 
			
		||||
                        return false;
 | 
			
		||||
                    break;
 | 
			
		||||
                
 | 
			
		||||
                case "area":
 | 
			
		||||
                    const _areaDevices = areaDevices(areaByName(value));
 | 
			
		||||
                    const _areaEntities = _areaDevices.flatMap(deviceEntities);
 | 
			
		||||
                    if(!_areaEntities.includes(entity.entity_id))
 | 
			
		||||
                        return false;
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                default:
 | 
			
		||||
                    return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										132
									
								
								src/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								src/main.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,132 @@
 | 
			
		||||
import { LitElement, html, css } from "card-tools/src/lit-element";
 | 
			
		||||
import "card-tools/src/card-maker";
 | 
			
		||||
import { entity_filter } from "./filter";
 | 
			
		||||
import { entity_sorter } from "./sort";
 | 
			
		||||
import { getData } from "card-tools/src/devices";
 | 
			
		||||
 | 
			
		||||
class AutoEntities extends LitElement {
 | 
			
		||||
 | 
			
		||||
    static get properties() {
 | 
			
		||||
        return {
 | 
			
		||||
            hass: {},
 | 
			
		||||
            cardConfig: {},
 | 
			
		||||
            entities: {},
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    async setConfig(config) {
 | 
			
		||||
        if(!config || !config.card) {
 | 
			
		||||
            throw new Error("Invalid configuration");
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        this._config = config;
 | 
			
		||||
        this.entities = [];
 | 
			
		||||
        this.cardConfig = {entities: this.entities, ...config.card};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async _getEntities()
 | 
			
		||||
    {
 | 
			
		||||
        let entities = [];
 | 
			
		||||
        // Start with any entities added by the `entities` parameter
 | 
			
		||||
        if(this._config.entities)
 | 
			
		||||
            entities = entities.concat(this._config.entities)
 | 
			
		||||
                .map((e) => {
 | 
			
		||||
                    if(typeof(e) === "string")
 | 
			
		||||
                        return {entity: e};
 | 
			
		||||
                    return e;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
        if(!this.hass || !this._config.filter) return entities;
 | 
			
		||||
 | 
			
		||||
        if(this._config.filter.include) {
 | 
			
		||||
            const all_entities = Object.keys(this.hass.states)
 | 
			
		||||
                .map((e) => new Object({entity: e}));
 | 
			
		||||
 | 
			
		||||
            for(const f of this._config.filter.include) {
 | 
			
		||||
                if(f.type !== undefined) {
 | 
			
		||||
                    // If the filter has a type, it's a special entry
 | 
			
		||||
                    entities.push(f);
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                if(f.device || f.area) {
 | 
			
		||||
                    await getData();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let add = all_entities.filter(entity_filter(this.hass, f))
 | 
			
		||||
                    .map((e) => new Object({...e, ...f.options}));
 | 
			
		||||
 | 
			
		||||
                if(f.sort !== undefined) {
 | 
			
		||||
                    // Sort per filter
 | 
			
		||||
                    add = add.sort(entity_sorter(this.hass, f.sort));
 | 
			
		||||
                }
 | 
			
		||||
                entities = entities.concat(add);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if(this._config.filter.exclude) {
 | 
			
		||||
            for(const f of this._config.filter.exclude) {
 | 
			
		||||
                entities = entities.filter((e) => {
 | 
			
		||||
                    // Don't exclude special entries
 | 
			
		||||
                    if(typeof(e) !== "string" && e.entity === undefined) return true;
 | 
			
		||||
                    return !entity_filter(this.hass,f)(e)
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(this._config.sort) {
 | 
			
		||||
            // Sort everything
 | 
			
		||||
            entities = entities.sort(entity_sorter(this.hass, this._config.sort));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return entities;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async updated(changedProperties) {
 | 
			
		||||
        if(changedProperties.has("hass") && this.hass) {
 | 
			
		||||
            function compare(a,b) {
 | 
			
		||||
                if( a === b ) return true;
 | 
			
		||||
                if( a == null || b == null) return false;
 | 
			
		||||
                if(a.length != b.length) return false;
 | 
			
		||||
                for(var i = 0; i < a.length; i++)
 | 
			
		||||
                    if(a[i] !== b[i])
 | 
			
		||||
                        return false;
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const oldEntities = this.entities;
 | 
			
		||||
            const newEntities = await this._getEntities();
 | 
			
		||||
            if(!compare(oldEntities, newEntities)) {
 | 
			
		||||
                this.entities = newEntities;
 | 
			
		||||
                this.cardConfig = { 
 | 
			
		||||
                    ...this.cardConfig,
 | 
			
		||||
                    entities: newEntities,
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    createRenderRoot() {
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        if(this.entities.length === 0 && this._config.show_empty === false) {
 | 
			
		||||
            return html``;
 | 
			
		||||
        }
 | 
			
		||||
        return html`
 | 
			
		||||
        <card-maker
 | 
			
		||||
            .config=${this.cardConfig}
 | 
			
		||||
            .hass=${this.hass}
 | 
			
		||||
        ></card-maker>`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getCardSize() {
 | 
			
		||||
        if(this.querySelector("card-maker") && this.querySelector("card-maker").getCardSize)
 | 
			
		||||
            return this.querySelector("card-maker").getCardSize();
 | 
			
		||||
        if(this.entities.length)
 | 
			
		||||
            return this.entities.length;
 | 
			
		||||
        if(this._config.filter && this._config.filter.include)
 | 
			
		||||
            return Object.keys(this._config.filter.include).length;
 | 
			
		||||
        return 1
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
customElements.define('auto-entities', AutoEntities)
 | 
			
		||||
							
								
								
									
										66
									
								
								src/sort.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/sort.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,66 @@
 | 
			
		||||
export function entity_sorter(hass, method) {
 | 
			
		||||
    if(typeof(method) === "string") {
 | 
			
		||||
        method = {method};
 | 
			
		||||
    }
 | 
			
		||||
    return function(a, b) {
 | 
			
		||||
        const entityA = typeof(a) === "string"
 | 
			
		||||
            ? hass.states[a]
 | 
			
		||||
            : hass.states[a.entity];
 | 
			
		||||
        const entityB = typeof(b) === "string"
 | 
			
		||||
            ? hass.states[b]
 | 
			
		||||
            : hass.states[b.entity];
 | 
			
		||||
 | 
			
		||||
        if(entityA === undefined || entityB === undefined) return 0;
 | 
			
		||||
 | 
			
		||||
        const [lt, gt] = method.reverse ? [-1, 1] : [1, -1];
 | 
			
		||||
        function compare(_a, _b) {
 | 
			
		||||
            if(method.ignore_case && _a.toLowerCase) _a = _a.toLowerCase();
 | 
			
		||||
            if(method.ignore_case && _b.toLowerCase) _b = _b.toLowerCase();
 | 
			
		||||
            if(_a === undefined && _b === undefined) return 0;
 | 
			
		||||
            if(_a === undefined) return lt;
 | 
			
		||||
            if(_b === undefined) return gt;
 | 
			
		||||
            if(_a < _b) return gt;
 | 
			
		||||
            if(_a > _b) return lt;
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
        switch(method.method) {
 | 
			
		||||
            case "domain":
 | 
			
		||||
                return compare(
 | 
			
		||||
                    entityA.entity_id.split(".")[0],
 | 
			
		||||
                    entityB.entity_id.split(".")[0]
 | 
			
		||||
                );
 | 
			
		||||
            case "entity_id":
 | 
			
		||||
                return compare(
 | 
			
		||||
                    entityA.entity_id,
 | 
			
		||||
                    entityB.entity_id
 | 
			
		||||
                );
 | 
			
		||||
            case "friendly_name":
 | 
			
		||||
            case "name":
 | 
			
		||||
                return compare(
 | 
			
		||||
                    entityA.attributes.friendly_name || entityA.entity_id.split(".")[1],
 | 
			
		||||
                    entityB.attributes.friendly_name || entityB.entity_id.split(".")[1]
 | 
			
		||||
                );
 | 
			
		||||
            case "state":
 | 
			
		||||
                return compare(
 | 
			
		||||
                    entityA.state,
 | 
			
		||||
                    entityB.state
 | 
			
		||||
                );
 | 
			
		||||
            case "attribute":
 | 
			
		||||
                let _a = entityA.attributes;
 | 
			
		||||
                let _b = entityB.attributes;
 | 
			
		||||
                let attr = method.attribute;
 | 
			
		||||
                while(attr) {
 | 
			
		||||
                    let k;
 | 
			
		||||
                    [k, attr] = attr.split(":");
 | 
			
		||||
                    _a = _a[k];
 | 
			
		||||
                    _b = _b[k];
 | 
			
		||||
                    if(_a === undefined && _b === undefined) return 0;
 | 
			
		||||
                    if(_a === undefined) return lt;
 | 
			
		||||
                    if(_b === undefined) return gt;
 | 
			
		||||
                }
 | 
			
		||||
                return compare(_a, _b);
 | 
			
		||||
            default:
 | 
			
		||||
                return 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								webpack.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								webpack.config.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
const path = require('path')
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    entry: './src/main.js',
 | 
			
		||||
    mode: 'production',
 | 
			
		||||
    output: {
 | 
			
		||||
        filename: 'auto-entities.js',
 | 
			
		||||
        path: path.resolve(__dirname)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user