Skip to content

Commit

Permalink
Merge pull request #82 from maxwroc/vNext
Browse files Browse the repository at this point in the history
Release v1.4.0
  • Loading branch information
maxwroc authored Jun 15, 2020
2 parents c05ad9f + 831db07 commit ca63226
Show file tree
Hide file tree
Showing 11 changed files with 566 additions and 107 deletions.
100 changes: 96 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ This card was inspired by [another great card](https://github.com/cbulock/lovela
| Name | Type | Default | Since | Description |
|:-----|:-----|:-----|:-----|:-----|
| type | string | **(required)** | v0.9.0 | Must be `custom:battery-state-card` |
| entities | list([Entity](#entity-object)) | **(required)** | v0.9.0 | List of entities
| entities | list([Entity](#entity-object)) | **(required)** | v0.9.0 | List of entities. It can be collection of entity/group IDs (strings) instead of Entity objects.
| title | string | | v0.9.0 | Card title
| sort_by_level | string | | v0.9.0 | Values: `asc`, `desc`
| collapse | number | | v1.0.0 | Number of entities to show. Rest will be available in expandable section ([example](#sorted-list-and-collapsed-view))
| filter | [FilterGroups](#filter-groups) | | v1.3.0 | Filter groups to automatically include or exclude entities ([example](#entity-filtering-and-bulk-renaming))
| collapse | number \| [Group](#group-object) | | v1.0.0 | Number of entities to show. Rest will be available in expandable section ([example](#sorted-list-and-collapsed-view))
| filter | [Filters](#filters) | | v1.3.0 | Filter groups to automatically include or exclude entities ([example](#entity-filtering-and-bulk-renaming))
| bulk_rename | list([Convert](#convert)) | | v1.3.0 | Rename rules applied for all entities ([example](#entity-filtering-and-bulk-renaming))
| style | string | | v1.4.0 | Extra CSS code to change/adjust the appearance ([example](#extra-styles))

+[common options](#common-options) (if specified they will be apllied to all entities)

Expand Down Expand Up @@ -67,7 +68,7 @@ This card was inspired by [another great card](https://github.com/cbulock/lovela
Note: the exact color is taken from CSS variable and it depends on your current template.


### Filter groups
### Filters
| Name | Type | Default | Description |
|:-----|:-----|:-----|:-----|
| include | list([Filter](#filter-object)) | | Filters for auto adding entities
Expand Down Expand Up @@ -135,6 +136,15 @@ Note: All of these values are optional but at least `entity_id` or `state` or `a
| name | string | **(required)** | Name of the attribute
| value | string | | Value of the attribute

### Group object

| Name | Type | Default | Since | Description |
|:-----|:-----|:-----|:-----|:-----|
| name | string | | v1.4.0 | Name of the group. Keywords available: `{min}`, `{max}`, `{count}`, `{range}`
| secondary_info | string | | v1.4.0 | Secondary info text, shown in the second line. Same keywords available as in `name`
| icon | string | | v1.4.0 | Group icon
| min | number | | v1.4.0 | Minimal battery level. Batteries below that level won't be assigned to this group.
| max | number | | v1.4.0 | Maximal battery level. Batteries above that level won't be assigned to this group.
## Examples

You can use this component as a card or as an entity (e.g. in `entities card`);
Expand Down Expand Up @@ -281,6 +291,40 @@ You can setup as well colors only for lower battery levels and leave the default
- sensor.bedroom_balcony_battery_level
- sensor.bedroom_switch_battery_level
```
### Battery groups

Battery groups allow you to group together set of batteries/entities based on couple conditions. You can use HA group entities to tell which entities should go to the group, or you can set min/max battery levels, or specify explicit list of entities which should be assigned to particular group.

Note: If you have battery groups defined in Home Assistant you can use their IDs instead of single entity ID (in `entities` collection).

![image](https://user-images.githubusercontent.com/8268674/84313600-aa42c700-ab5e-11ea-829e-394b292f3cbe.png)

```yaml
type: 'custom:battery-state-card'
title: Battery state card
sort_by_level: asc
collapse:
- name: 'Door sensors (min: {min}%, count: {count})' # special keywords in group name
secondary_info: 'Battery levels {range}%' # special keywords in group secondary info
icon: 'mdi:door'
entities: # explicit list of entities
- sensor.bedroom_balcony_battery_level
- sensor.main_door_battery_level
- sensor.living_room_balcony_battery_level
- group_id: group.motion_sensors_batteries # using HA group
secondary_info: No icon # Secondary info text
icon: null # removing default icon for this group (from HA group definition)
- group_id: group.temp_sensors_batteries
min: 99 # all entities below that level should show up ungroupped
icon: 'mdi:thermometer' # override for HA group icon
entities:
# if you need to specify some properties for any entity in the group
- entity: sensor.bedroom_balcony_battery_level
name: "Bedroom balkony door"
multiplier: 10
# entities from below HA group won't be grouped as there is no corresponding collapsed group
- group.switches_batteries
```

### Non-numeric state values

Expand Down Expand Up @@ -409,6 +453,54 @@ If you add entities automatically you cannot specify properties for individual e
secondary_info: "Battery state" # Static text
```

### Extra styles

You can add CSS code which can change the appearance of the card.

Note: HTML code (including CSS class names) can change in next releases so your custom styles may require adjustments after card update.

![image](https://user-images.githubusercontent.com/8268674/84653185-db2b4f00-af04-11ea-97a9-07f0dbb0800e.png)

```yaml
- title: Glance view with custom CSS
type: 'custom:battery-state-card'
entities:
- group.all_battery_sensors
sort_by_level: asc
style: |
.card-content {
display: grid;
grid-template-columns: auto auto auto auto
}
.entity-row.entity-spacing {
margin: 8px 0;
}
.entity-row {
display: flex;
flex-direction: column;
}
.entity-row .name {
order: 1;
overflow: hidden;
width: 80px;
font-size: 12px
}
.entity-row.non-numeric-state .state {
display: none;
}
.entity-row:not(.non-numeric-state) .state {
position: absolute;
text-shadow: 1px 1px black;
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
font-size: 12px;
overflow: hidden;
}
```

## Installation

Once added to [HACS](https://community.home-assistant.io/t/custom-component-hacs/121727) add the following to your lovelace configuration
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "battery-state-card",
"version": "1.3.7",
"version": "1.4.0",
"description": "Battery State card for Home Assistant",
"main": "dist/battery-state-card.js",
"repository": {
Expand Down
132 changes: 113 additions & 19 deletions src/battery-provider.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import BatteryViewModel from "./battery-vm";
import { IBatteryStateCardConfig, IFilter, FilterOperator, IBatteryEntity } from "./types";
import { IBatteryStateCardConfig, IFilter, FilterOperator, IBatteryEntity, IHomeAssistantGroupProps, IBatteriesResultViewData, IGroupDataMap } from "./types";
import { HassEntity, HomeAssistant } from "./ha-types";
import { log } from "./utils";
import { ActionFactory } from "./action";
import { getBatteryCollections } from "./grouping";

/**
* Properties which should be copied over to individual entities from the card
Expand Down Expand Up @@ -139,6 +140,16 @@ export class BatteryProvider {
*/
private batteries: BatteryViewModel[] = [];

/**
* Groups to be resolved on HA state update.
*/
private groupsToResolve: string[] = [];

/**
* Collection of groups and their properties taken from HA
*/
private groupsData: IGroupDataMap = {};

/**
* Whether include filters were processed already.
*/
Expand All @@ -155,25 +166,32 @@ export class BatteryProvider {
this.processExplicitEntities();
}

/**
* Return batteries
* @param hass Home Assistant instance
*/
getBatteries(hass?: HomeAssistant): BatteryViewModel[] {
update(hass: HomeAssistant): boolean {
let updated = false;
if (!this.initialized) {
// groups and includes should be processed just once
this.initialized = true;

if (hass) {
if (!this.initialized) {
this.processIncludes(hass);
}
updated = this.processGroups(hass) || updated;

const updated = this.updateBatteries(hass);
updated = this.processIncludes(hass) || updated;
}

if (updated) {
this.processExcludes(hass);
}
updated = this.updateBatteries(hass) || updated;

if (updated) {
this.processExcludes(hass);
}

return this.batteries;
return updated;
}

/**
* Return batteries
* @param hass Home Assistant instance
*/
getBatteries(): IBatteriesResultViewData {
return getBatteryCollections(this.config.collapse, this.batteries, this.groupsData);
}

/**
Expand Down Expand Up @@ -210,19 +228,53 @@ export class BatteryProvider {
return entity;
});

// remove groups to add them later
entities = entities.filter(e => {
if (!e.entity) {
throw new Error("Invalid configuration - missing property 'entity' on:\n" + JSON.stringify(e));
}

if (e.entity.startsWith("group.")) {
this.groupsToResolve.push(e.entity);
return false;
}

return true;
});

// processing groups and entities from collapse property
// this way user doesn't need to put same IDs twice in the configuration
if (this.config.collapse && Array.isArray(this.config.collapse)) {
this.config.collapse.forEach(group => {
if (group.group_id) {
// check if it's not there already
if (this.groupsToResolve.indexOf(group.group_id) == -1) {
this.groupsToResolve.push(group.group_id);
}
}
else if (group.entities) {
group.entities.forEach(entity_id => {
// check if it's not there already
if (!entities.some(e => e.entity == entity_id)) {
entities.push({ entity: entity_id });
}
});
}
});
}

this.batteries = entities.map(entity => this.createBattery(entity));
}

/**
* Adds batteries based on filter.include config.
* @param hass Home Assistant instance
*/
private processIncludes(hass: HomeAssistant) {
// avoiding processing filter.include again
this.initialized = true;
private processIncludes(hass: HomeAssistant): boolean {

let updated = false;
if (!this.include) {
return;
return updated;
}

Object.keys(hass.states).forEach(entity_id => {
Expand All @@ -231,9 +283,51 @@ export class BatteryProvider {
// check if battery is not added already (via explicit entities)
!this.batteries.some(b => b.entity_id == entity_id)) {

updated = true;
this.batteries.push(this.createBattery({ entity: entity_id }));
}
});

return updated;
}

/**
* Adds batteries from group entities (if they were on the list)
* @param hass Home Assistant instance
*/
private processGroups(hass: HomeAssistant): boolean {

let updated = false;

this.groupsToResolve.forEach(group_id => {
const groupEntity = hass.states[group_id];
if (!groupEntity) {
log(`Group "${group_id}" not found`);
return;
}

const groupData = groupEntity.attributes as IHomeAssistantGroupProps;
if (!Array.isArray(groupData.entity_id)) {
log(`Entities not found in "${group_id}"`);
return;
}

groupData.entity_id.forEach(entity_id => {
// check if battery is on the list already
if (this.batteries.some(b => b.entity_id == entity_id)) {
return;
}

updated = true;
this.batteries.push(this.createBattery({ entity: entity_id }));
});

this.groupsData[group_id] = groupData;
});

this.groupsToResolve = [];

return updated;
}

/**
Expand Down
11 changes: 7 additions & 4 deletions src/battery-vm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,10 @@ class BatteryViewModel {
}

get classNames(): string {
return this.action ? "clickable" : "";
const classNames = [];
this.action && classNames.push("clickable");
!isNumber(this.level) && classNames.push("non-numeric-state");
return classNames.join(" ");
}

/**
Expand All @@ -186,7 +189,7 @@ class BatteryViewModel {

this.name = this.config.name || entityData.attributes.friendly_name

this.level = this.getLevel(entityData);
this.level = this.getLevel(entityData, hass);

// must be called after getting battery level
this.charging = this.getChargingState(hass);
Expand All @@ -199,8 +202,8 @@ class BatteryViewModel {
* Gets battery level
* @param entityData Entity state data
*/
private getLevel(entityData: HassEntity): string {
const UnknownLevel = "Unknown";
private getLevel(entityData: HassEntity, hass: HomeAssistant): string {
const UnknownLevel = hass.localize("state.default.unknown");
let level: string;

if (this.config.attribute) {
Expand Down
Loading

0 comments on commit ca63226

Please sign in to comment.