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

Release v3.2.0 #684

Merged
merged 27 commits into from
Feb 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1b1ae4c
Extended entity data
maxwroc Jan 18, 2024
ee18741
Entity data debug output
maxwroc Jan 19, 2024
e31d560
Merged vNext
maxwroc Feb 10, 2024
aaf3dc9
Removed console.log and fixed UT
maxwroc Feb 10, 2024
c8c3060
Sort by numeric state (not displayed value)
maxwroc Feb 11, 2024
ca8925e
Triggering CI
maxwroc Feb 11, 2024
5ba8359
Merge pull request #678 from maxwroc/SortAlwaysByLvl
maxwroc Feb 11, 2024
d7100ac
Fix capitalizing name when it is empty string
maxwroc Feb 11, 2024
693005f
Merge branch 'master' into vNext
maxwroc Feb 11, 2024
f56c151
Merge branch 'vNext' into CapitalizeFix
maxwroc Feb 11, 2024
c8e957d
Merge pull request #681 from maxwroc/CapitalizeFix
maxwroc Feb 11, 2024
ae3388d
Merge branch 'vNext' into ExtEntityData
maxwroc Feb 11, 2024
19443c5
Merge branch 'vNext' into ExtEntityData
maxwroc Feb 11, 2024
33cf2d5
Fixed UT
maxwroc Feb 11, 2024
6837582
Allow missing 'entities' object on HA
maxwroc Feb 11, 2024
5dc70f3
Prevent from null-refs
maxwroc Feb 11, 2024
bc3e457
Troubleshooting info
maxwroc Feb 11, 2024
3dbea71
Merge pull request #671 from maxwroc/ExtEntityData
maxwroc Feb 11, 2024
dda5898
Processing exclude filter after the update
maxwroc Feb 11, 2024
27b0e30
Merge pull request #682 from maxwroc/ExcludeAfterUpdate
maxwroc Feb 11, 2024
b162372
Allow filtering by extended entity data
maxwroc Feb 11, 2024
fa3f34c
Merge pull request #683 from maxwroc/DeepFilter
maxwroc Feb 11, 2024
d9849f8
Don't render anything if the value was not found in KString
maxwroc Feb 11, 2024
98c1e13
Merge pull request #685 from maxwroc/AllowMissing
maxwroc Feb 11, 2024
dd00b24
Respect HomeAssistant entity visibility setting
maxwroc Feb 11, 2024
d080faa
Merge pull request #686 from maxwroc/RespectHiddenSetting
maxwroc Feb 11, 2024
fbaf6e8
Fixed json listing
maxwroc Feb 11, 2024
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
10 changes: 9 additions & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ assignees: ''
**YAML configuration**
```yaml

# please paste here your card config
# please paste here your card config (before backticks below; do not remove them)

```

***Entity debug data***
<!-- Add "debug: true" in your card configuration and paste below entity data from the debug output -->

```json

```

Expand All @@ -32,5 +39,6 @@ assignees: ''
**Version**
<!-- What is the version of the card? -->
<!-- The best and most reliable way to check the version is to look at the Developer Tools Console in your browser (Win-Chrome - F12 key) -->
<!-- The other way to check it is to add "debug: true" in card config and expand debug output - version can be foud there -->

<!-- If regression happened after updating Home assistant please add HA version as well. -->
88 changes: 88 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ These options can be specified both per-entity and at the top level (affecting a
| value_override | [KString](#keyword-string-kstring) | | v3.0.0 | Allows to override the battery level value. Note: when used the `multiplier`, `round`, `state_map` setting is ignored
| non_battery_entity | boolean | `false` | v3.0.0 | Disables default battery state sources e.g. "battery_level" attribute
| default_state_formatting | bollean | `true` | v3.1.0 | Can be used to disable default state formatting e.g. entity display precission setting
| debug | boolean \| string | `false` | v3.2.0 | Whether to show debug otuput (all available entity data). You can use entity_id if you want to debug specific one.

### Keyword string (KString)

Expand Down Expand Up @@ -781,6 +782,93 @@ resources:
type: module
```

## Troubleshooting

You can turn on the debug output via `debug` setting. It can be turned on for all of the entities:

```yaml
debug: true
```

Or single entity by specifying entity_id:
```yaml
debug: sensor.owl_energy_signal_strength
```

![image](https://github.com/maxwroc/battery-state-card/assets/8268674/04a2b1c8-662a-4067-9231-1d8314914ed3)

Note: "Copy to clipboard" is available only if you access your HA via https.

After clicking on show/hide you will see the entity data which is available for the card to process.

<details>
<summary>Click to see example output</summary>

```json
{
"entity_id": "sensor.owl_energy_signal_strength",
"state": "-72",
"attributes": {
"state_class": "measurement",
"event": "115a011a32e20100000172000031bbc85d69",
"unit_of_measurement": "dBm",
"assumed_state": true,
"device_class": "signal_strength",
"friendly_name": "Owl energy Signal strength"
},
"context": {
"id": "01HPC8X76DDZ4D3XK5BMH8KKFW",
"parent_id": null,
"user_id": null
},
"last_changed": "2024-02-11T14:24:59.597Z",
"last_updated": "2024-02-11T14:24:59.597Z",
"display": {
"entity_id": "sensor.owl_energy_signal_strength",
"device_id": "91b4ffe9a73db4d1ee9482d0e7d94a84",
"platform": "rfxtrx",
"entity_category": "diagnostic",
"name": "Signal strength"
},
"device": {
"area_id": "outside",
"configuration_url": null,
"config_entries": [
"2c67d4fe27613df1b3de59a1f042dc5c"
],
"connections": [],
"disabled_by": null,
"entry_type": null,
"hw_version": null,
"id": "91b4ffe9a73db4d1ee9482d0e7d94a84",
"identifiers": [
[
"rfxtrx",
"5a",
"1",
"32:e2"
]
],
"manufacturer": null,
"model": "ELEC2, CM119/160",
"name_by_user": "Owl energy",
"name": "ELEC2, CM119/160 32:e2",
"serial_number": null,
"sw_version": null,
"via_device_id": null
},
"area": {
"aliases": [],
"area_id": "outside",
"name": "Outside",
"picture": null
}
}
```
</details>

When you look at the entity data you can for example figure out what you can display using KString e.g. `Area: {area.name}, Device: {device.name_by_user}`

## Development
<details>
<summary>Click to expand</summary>
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": "3.1.6",
"version": "3.2.0",
"description": "Battery State card for Home Assistant",
"main": "dist/battery-state-card.js",
"author": "Max Chodorowski",
Expand Down
36 changes: 27 additions & 9 deletions src/battery-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,27 @@ import { log, safeGetConfigArrayOfObjects } from "./utils";
import { HomeAssistant } from "custom-card-helpers";
import { BatteryStateEntity } from "./custom-elements/battery-state-entity";
import { Filter } from "./filter";
import { HomeAssistantExt } from "./type-extensions";
import { EntityRegistryDisplayEntry, HomeAssistantExt } from "./type-extensions";

/**
* Properties which should be copied over to individual entities from the card
*/
const entititesGlobalProps: (keyof IBatteryEntityConfig)[] = [ "tap_action", "state_map", "charging_state", "secondary_info", "colors", "bulk_rename", "icon", "round", "unit", "value_override", "non_battery_entity", "default_state_formatting" ];
const entititesGlobalProps: (keyof IBatteryEntityConfig)[] = [
"bulk_rename",
"charging_state",
"colors",
"debug",
"default_state_formatting",
"extend_entity_data",
"icon",
"non_battery_entity",
"round",
"secondary_info",
"state_map",
"tap_action",
"value_override",
"unit",
];

/**
* Class responsible for intializing Battery view models based on given configuration.
Expand Down Expand Up @@ -63,15 +78,15 @@ export class BatteryProvider {
this.processIncludes(hass);
}

this.processExcludes(hass);

const updateComplete = Object.keys(this.batteries).map(id => {
const battery = this.batteries[id];
battery.hass = hass;
return battery.cardUpdated;
});

await Promise.all(updateComplete);

this.processExcludes();
}

/**
Expand Down Expand Up @@ -199,10 +214,14 @@ export class BatteryProvider {

/**
* Removes or hides batteries based on filter.exclude config.
* @param hass Home Assistant instance
*/
private processExcludes(hass: HomeAssistant) {
private processExcludes() {
if (this.exclude == undefined) {
Object.keys(this.batteries).forEach((entityId) => {
const battery = this.batteries[entityId];
battery.isHidden = (<EntityRegistryDisplayEntry>battery.entityData?.display)?.hidden;
});

return;
}

Expand All @@ -213,9 +232,8 @@ export class BatteryProvider {
const battery = this.batteries[entityId];
let isHidden = false;
for (let filter of filters) {
const entityState = hass.states[entityId];
// we want to show batteries for which entities are missing in HA
if (entityState !== undefined && filter.isValid(entityState, battery.state)) {
if (filter.isValid(battery.entityData, battery.state)) {
if (filter.is_permanent) {
// permanent filters have conditions based on static values so we can safely
// remove such battery to avoid updating them unnecessarily
Expand All @@ -231,7 +249,7 @@ export class BatteryProvider {

// we keep the view model to keep updating it
// it might be shown/not-hidden next time
battery.isHidden = isHidden;
battery.isHidden = isHidden || (<EntityRegistryDisplayEntry>battery.entityData?.display)?.hidden;
});

toBeRemoved.forEach(entityId => delete this.batteries[entityId]);
Expand Down
3 changes: 3 additions & 0 deletions src/custom-elements/battery-state-entity.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
display: flex;
align-items: center;
}
:host > ha-alert {
flex: 1 0 auto;
}
56 changes: 50 additions & 6 deletions src/custom-elements/battery-state-entity.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { css } from "lit";
import { property } from "lit/decorators.js"
import { safeGetConfigObject } from "../utils";
import { batteryHtml } from "./battery-state-entity.views";
import { batteryHtml, debugOutput } from "./battery-state-entity.views";
import { LovelaceCard } from "./lovelace-card";
import sharedStyles from "./shared.css"
import entityStyles from "./battery-state-entity.css";
Expand All @@ -12,6 +12,7 @@ import { getChargingState } from "../entity-fields/charging-state";
import { getBatteryLevel } from "../entity-fields/battery-level";
import { getName } from "../entity-fields/get-name";
import { getIcon } from "../entity-fields/get-icon";
import { DeviceRegistryEntry } from "../type-extensions";

/**
* Battery entity element
Expand Down Expand Up @@ -63,7 +64,12 @@ export class BatteryStateEntity extends LovelaceCard<IBatteryEntityConfig> {
/**
* Raw entity data
*/
public entityData: IMap<string>;
public entityData: IMap<any> = {};

/**
* Numeric representation of the state
*/
public stateNumeric: number | undefined;

/**
* Entity CSS styles
Expand All @@ -77,13 +83,27 @@ export class BatteryStateEntity extends LovelaceCard<IBatteryEntityConfig> {
...this.hass?.states[this.config.entity]
};

this.name = getName(this.config, this.hass);
var { state, level, unit} = getBatteryLevel(this.config, this.hass);
if (this.config.extend_entity_data !== false) {
this.extendEntityData();
}

if (this.config.debug === true || this.config.debug === this.config.entity) {
this.alert = {
title: `Debug: ${this.config.entity}`,
content: debugOutput(JSON.stringify(this.entityData, null, 2)),
}
}

var { state, level, unit} = getBatteryLevel(this.config, this.hass, this.entityData);
this.state = state;
this.unit = unit;
this.stateNumeric = level;

const isCharging = getChargingState(this.config, this.state, this.hass);
this.secondaryInfo = getSecondaryInfo(this.config, this.hass, isCharging);
this.entityData["charging"] = isCharging ? (this.config.charging_state?.secondary_info_text || "Charging") : "" // todo: think about i18n

this.name = getName(this.config, this.hass, this.entityData);
this.secondaryInfo = getSecondaryInfo(this.config, this.hass, this.entityData);
this.icon = getIcon(this.config, level, isCharging, this.hass);
this.iconColor = getColorForBatteryLevel(this.config, level, isCharging);
}
Expand Down Expand Up @@ -112,7 +132,7 @@ export class BatteryStateEntity extends LovelaceCard<IBatteryEntityConfig> {
* @param enable Whether to enable/add the tap action
*/
private setupAction(enable: boolean = true) {
if (enable) {
if (enable && !this.error && !this.alert) {
let tapAction = this.config.tap_action || "more-info";
if (tapAction != "none" && !this.action) {
this.action = evt => {
Expand All @@ -136,4 +156,28 @@ export class BatteryStateEntity extends LovelaceCard<IBatteryEntityConfig> {
}
}
}

/**
* Adds display, device and area objects to entityData
*/
private extendEntityData(): void {

if (!this.hass) {
return;
}

const entityDisplayEntry = this.hass.entities && this.hass.entities[this.config.entity];

if (entityDisplayEntry) {
this.entityData["display"] = entityDisplayEntry;
this.entityData["device"] = entityDisplayEntry.device_id
? this.hass.devices && this.hass.devices[entityDisplayEntry.device_id]
: undefined;

const area_id = entityDisplayEntry.area_id || (<DeviceRegistryEntry>this.entityData["device"])?.area_id;
if (area_id) {
this.entityData["area"] = this.hass.areas && this.hass.areas[area_id];
}
}
}
}
28 changes: 27 additions & 1 deletion src/custom-elements/battery-state-entity.views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,30 @@ ${icon(model.icon, model.iconColor)}
</div>
`;

const unit = (unit: string | undefined) => unit && html`&nbsp;${unit}`;
const unit = (unit: string | undefined) => unit && html`&nbsp;${unit}`;

export const debugOutput = (content: string) => {

const actions = [{
text: "Show / hide",
action: (e: MouseEvent) => {
const debugContent = <HTMLElement>(<HTMLElement>e.currentTarget)?.parentElement?.parentElement?.querySelector(".debug_expand");
if (debugContent) {
debugContent.style.display = debugContent.style.display === "none" ? "block" : "none";
}
}
}];

if (navigator.clipboard) {
actions.push({
text: "Copy to clipboard",
action: () => navigator.clipboard.writeText(content),
});
}

return html`<div>${actions.length && html`${ actions.map(a => html`[<a href="javascript:void(0);" @click="${(e: MouseEvent) => a.action(e)}">${a.text}</a>] `) }`}</div>
<div class="debug_expand" style="display: none;">
<p>Version: [VI]{version}[/VI]</p>
<pre style="user-select: all">${content}</pre>
</div>`
};
12 changes: 12 additions & 0 deletions src/custom-elements/lovelace-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ export abstract class LovelaceCard<TConfig> extends LitElement {
@property({ attribute: false })
public error: Error | undefined;

/**
* Warning
*/
@property({ attribute: false })
public alert: { type?: "error" | "warning", title?: string, content?: TemplateResult | string } | undefined;

/**
* HomeAssistant object
*/
Expand Down Expand Up @@ -48,6 +54,8 @@ export abstract class LovelaceCard<TConfig> extends LitElement {
*/
private triggerUpdate = throttledCall(async () => {

this.alert = undefined;

try {
await this.internalUpdate(this.configUpdated, this.hassUpdated);
this.error = undefined;
Expand Down Expand Up @@ -129,6 +137,10 @@ export abstract class LovelaceCard<TConfig> extends LitElement {
return errorHtml(this.tagName, "Exception: " + this.error.message, this.error.stack);
}

if (this.alert) {
return html`<ha-alert alert-type="${this.alert.type || "warning"}" title="${this.alert.title || this.tagName}">${this.alert.content}</ha-alert>`;
}

return this.internalRender();
}
}
Expand Down
Loading
Loading