Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add names config option for name transforms #449

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ sort: <sort_method>
- `template:` A jinja2 template evaluating to a list of entries to include
- `include:` 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
- `names:`
- `transforms:` A list of transforms to be applied to all entity names
- `show_empty:` Whether to display the card if it has no entities. Default: `true`. Note that any filter with a `type` option will be ignored.
- `else:` Card to display if main card has no entities. Overrides `show_empty`.
- `unique:` Whether to remove duplicate values after filtering and sorting. Set to `true` to remove exact duplicate entries. Set to `entity` to remove entries with the same entity id. Default: `false`.
Expand Down Expand Up @@ -79,6 +81,102 @@ Special options:

The filter section `template` takes a jinja2 template which evaluates to a list of entities or entity objects.

### Names

Names `transforms` have the following options:

- `type:` The type of the transform. Supported types are `set`, `prefix`, `suffix` and `replace`.
- `value:` For `set`, `prefix` and `suffix` types this specifies the new name value to use.
- `match:` For `replace` type, this specifies the name value to be replaced.
- `replacement:` For `replace` type, this specifies the name value to be used as the replacement (Defaults to empty string if not specified).
- `trim:` Whether or not to trim whitespace from the name after performing a transform on it.

The `value` or `match` and `replacement` options are all in term composed of the following sub options which are all optional although one must be specified:

- `text:` A plain text string value for a name.
- `group:` The entity id of a group whose name to use.
- `area:` Whether to use the area name of the entity.
- `device_manufacturer:` Whether to use the device manufacturer of the entity.
- `device_model:` Whether to use the device manufacturer of the entity.

Show a list of lights and set their name to a group:

```yaml
filter:
include:
domain: light
names:
transforms:
- type: set
value:
group: light.my_light_group
```

Show a list of lights and set their name to their area:

```yaml
filter:
include:
domain: light
names:
transforms:
- type: set
value:
area: true
```

Remove the room name from the names of a list of lights:

```yaml
filter:
include:
domain: light
names:
transforms:
- type: replace
match:
area: true
# this is the same as:
# match:
# area: true
# replace:
# text: ""
```

Replace the room name with device model from the names of a list of lights:

```yaml
filter:
include:
domain: light
names:
transforms:
- type: replace
match:
area: true
replace:
device_model: true
```

Remove area and then some text from the names of a list of lights:

```yaml
filter:
include:
domain: light
names:
transforms:
- type: replace
match:
area: true
- type: replace
match:
text: Lights
- type: replace
match:
text: Light
```

## How it works

`auto-entities` creates a list of entities by:
Expand Down
161 changes: 161 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { bind_template, unbind_template } from "./templates";
import { filter_entity } from "./filter";
import { get_sorter } from "./sort";
import {
EntityNameConfig,
AutoEntitiesConfig,
EntityList,
HuiErrorCard,
Expand Down Expand Up @@ -59,6 +60,9 @@ class AutoEntities extends LitElement {
if (!config.filter && !config.entities) {
throw new Error("No filters specified.");
}
if (config.names && !config.names.transforms) {
throw new Error("No names transforms specified.");
}
config = JSON.parse(JSON.stringify(config));
this._config = config;

Expand Down Expand Up @@ -276,6 +280,163 @@ class AutoEntities extends LitElement {
}
}

if (this._config.names) {
const { transforms } = this._config.names;
if (transforms && transforms.length > 0) {
const ents = await getEntities(this.hass);
const devs = await getDevices(this.hass);
const areas = await getAreas(this.hass);

const findEnt = entity => ents.find((e) => e.entity_id === entity.entity);
const findDev = ent => devs.find((e) => e.id === ent.device_id);
const findArea = ent => areas.find((a) => a.area_id === ent.area_id);

const getEntName = (entity: LovelaceRowConfig, ent: any) => {
let name = "";
if (entity.name) {
name = entity.name;
} else if (ent.friendly_name) {
name = ent.friendly_name;
} else if (ent.name) {
name = ent.name;
} else if (ent.device_id) {
const device = findDev(ent);
if (device && device.name) {
if (ent.original_name) {
if (ent.original_name !== device.name) {
if (ent.original_name.startsWith(device.name)) {
name = ent.original_name;
} else {
name = device.name + " " + ent.original_name;
}
} else {
name = device.name;
}
} else {
name = device.name;
}
} else if (ent.original_name) {
name = ent.original_name;
}
} else if (ent.original_name) {
name = ent.original_name;
}
return name;
};

const getEntAreaName = ent => {
let area;
if (ent.area_id) {
area = findArea(ent);
}
if (!area && ent.device_id !== undefined) {
const device = findDev(ent);
if (device) {
area = findArea(device);
}
}
if (area && area.name) {
return area.name;
}
return "";
};

const getEntGroupName = group => {
const groupEnt = this.hass.states[group];
if (groupEnt && groupEnt.attributes.friendly_name) {
return groupEnt.attributes.friendly_name;
}
return "";
};

const getEntDeviceManufacturerName = ent => {
if (ent.device_id !== undefined) {
const device = findDev(ent);
if (device && device.manufacturer) {
return device.manufacturer;
}
}
return "";
};

const getEntDeviceModelName = ent => {
if (ent.device_id !== undefined) {
const device = findDev(ent);
if (device && device.model) {
return device.model;
}
}
return "";
};

const getEntNameForConfig = (ent: any, config: EntityNameConfig) => {
const { text, area, group, device_manufacturer, device_model } = config;
if (text) {
return text;
} else if (area) {
return getEntAreaName(ent);
} else if (group) {
return getEntGroupName(group);
} else if (device_manufacturer) {
return getEntDeviceManufacturerName(ent);
} else if (device_model) {
return getEntDeviceModelName(ent);
}
};

const newEntities = entities.map(e => ({...e}));

for (let transform of transforms) {
const { type, trim } = transform;
if (type === "set" || type === "prefix" || type === "suffix") {
const { value } = transform;
if (value) {
let getEntityName = (oldName, newName) => oldName;
if (type === "set") {
getEntityName = (oldName, newName) => newName;
} else if (type === "prefix") {
getEntityName = (oldName, newName) => newName + oldName;
} else if (type === "suffix") {
getEntityName = (oldName, newName) => oldName + newName;
}
for (let entity of newEntities) {
const ent = findEnt(entity);
if (ent) {
const oldName = getEntName(entity, ent);
const newName = getEntNameForConfig(ent, value);
if (oldName && newName) {
entity.name = getEntityName(oldName, newName);
if (trim) {
entity.name = entity.name.trim();
}
}
}
}
}
} else if (type === "replace") {
const { match, replacement } = transform;
if (match) {
for (let entity of newEntities) {
const ent = findEnt(entity);
if (ent) {
const oldName = getEntName(entity, ent);
const matchName = getEntNameForConfig(ent, match);
if (oldName && matchName) {
const replaceName = replacement ? getEntNameForConfig(ent, replacement) : "";
entity.name = oldName.replace(matchName, replaceName);
if (trim) {
entity.name = entity.name.trim();
}
}
}
}
}
}
}
entities = newEntities;
}
}

if (this._config.sort) {
entities = entities.sort(get_sorter(this.hass, this._config.sort));
if (this._config.sort.count) {
Expand Down
41 changes: 41 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,45 @@ interface FilterConfig {
type?: string;
}

export interface EntityNameConfig {
text?: string;
group?: string;
area?: boolean;
device_manufacturer?: boolean;
device_model?: boolean;
}

interface NamesTransformSetConfig {
type: "set";
value: EntityNameConfig;
trim?: boolean;
}

interface NamesTransformPrefixConfig {
type: "prefix";
value: EntityNameConfig;
trim?: boolean;
}

interface NamesTransformSuffixConfig {
type: "suffix";
value: EntityNameConfig;
trim?: boolean;
}

interface NamesTransformReplaceConfig {
type: "replace";
match: EntityNameConfig;
replacement?: EntityNameConfig;
trim?: boolean;
}

export type NamesTransformConfig = NamesTransformSetConfig | NamesTransformPrefixConfig | NamesTransformSuffixConfig | NamesTransformReplaceConfig;

export interface NamesConfig {
transforms: NamesTransformConfig[];
}

export interface AutoEntitiesConfig {
card: any;
entities: Array<LovelaceRowConfig | string>;
Expand All @@ -47,6 +86,7 @@ export interface AutoEntitiesConfig {
include?: FilterConfig[];
exclude?: FilterConfig[];
};
names?: NamesConfig;

card_param?: string;

Expand All @@ -61,6 +101,7 @@ export interface AutoEntitiesConfig {
export interface LovelaceRowConfig {
entity?: string;
type?: string;
name?: string;
}
export interface LovelaceCard extends HTMLElement {
hass: any;
Expand Down