Show battery charging state in the config panels (#6356)

This commit is contained in:
J. Nick Koston 2020-07-12 09:30:27 -10:00 committed by GitHub
parent def1ec3518
commit 3bc54aa9e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 124 additions and 44 deletions

View File

@ -0,0 +1,28 @@
/** Return an icon representing a battery state. */
import { HassEntity } from "home-assistant-js-websocket";
export const batteryIcon = (
batteryState: HassEntity,
batteryChargingState?: HassEntity
) => {
const battery = Number(batteryState.state);
const battery_charging =
batteryChargingState && batteryChargingState.state === "on";
if (isNaN(battery)) {
return "hass:battery-unknown";
}
var icon = "hass:battery";
const batteryRound = Math.round(battery / 10) * 10;
if (battery_charging && battery > 10) {
icon += `-charging-${batteryRound}`;
} else if (battery_charging) {
icon += "-outline";
} else if (battery <= 5) {
icon += "-alert";
} else if (battery > 5 && battery < 95) {
icon += `-${batteryRound}`;
}
return icon;
};

View File

@ -2,6 +2,7 @@
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { UNIT_C, UNIT_F } from "../const"; import { UNIT_C, UNIT_F } from "../const";
import { domainIcon } from "./domain_icon"; import { domainIcon } from "./domain_icon";
import { batteryIcon } from "./battery_icon";
const fixedDeviceClassIcons = { const fixedDeviceClassIcons = {
humidity: "hass:water-percent", humidity: "hass:water-percent",
@ -19,29 +20,7 @@ export const sensorIcon = (state: HassEntity) => {
return fixedDeviceClassIcons[dclass]; return fixedDeviceClassIcons[dclass];
} }
if (dclass === "battery") { if (dclass === "battery") {
const battery = Number(state.state); return batteryIcon(state);
if (isNaN(battery)) {
return "hass:battery-unknown";
}
const batteryRound = Math.round(battery / 10) * 10;
if (batteryRound >= 100) {
return "hass:battery";
}
if (batteryRound <= 0) {
return "hass:battery-alert";
}
// Will return one of the following icons: (listed so extractor picks up)
// hass:battery-10
// hass:battery-20
// hass:battery-30
// hass:battery-40
// hass:battery-50
// hass:battery-60
// hass:battery-70
// hass:battery-80
// hass:battery-90
// We obscure 'hass' in iconname so this name does not get picked up
return `${"hass"}:battery-${batteryRound}`;
} }
const unit = state.attributes.unit_of_measurement; const unit = state.attributes.unit_of_measurement;

View File

@ -0,0 +1,20 @@
import { batteryIcon } from "../../common/entity/battery_icon";
import "../ha-icon";
import { customElement, html, property, LitElement } from "lit-element";
@customElement("ha-battery-icon")
class HaBatteryIcon extends LitElement {
@property() public batteryStateObj;
@property() public batteryChargingStateObj;
protected render() {
return html`
<ha-icon
.icon=${batteryIcon(this.batteryStateObj, this.batteryChargingStateObj)}
></ha-icon>
`;
}
}
customElements.define("ha-battery-icon", HaBatteryIcon);

View File

@ -37,6 +37,17 @@ export const findBatteryEntity = (
hass.states[entity.entity_id].attributes.device_class === "battery" hass.states[entity.entity_id].attributes.device_class === "battery"
); );
export const findBatteryChargingEntity = (
hass: HomeAssistant,
entities: EntityRegistryEntry[]
): EntityRegistryEntry | undefined =>
entities.find(
(entity) =>
hass.states[entity.entity_id] &&
hass.states[entity.entity_id].attributes.device_class ===
"battery_charging"
);
export const computeEntityRegistryName = ( export const computeEntityRegistryName = (
hass: HomeAssistant, hass: HomeAssistant,
entry: EntityRegistryEntry entry: EntityRegistryEntry

View File

@ -14,7 +14,7 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import { createValidEntityId } from "../../../common/entity/valid_entity_id"; import { createValidEntityId } from "../../../common/entity/valid_entity_id";
import { compare } from "../../../common/string/compare"; import { compare } from "../../../common/string/compare";
import "../../../components/entity/ha-state-icon"; import "../../../components/entity/ha-battery-icon";
import "../../../components/ha-icon-next"; import "../../../components/ha-icon-next";
import { AreaRegistryEntry } from "../../../data/area_registry"; import { AreaRegistryEntry } from "../../../data/area_registry";
import { ConfigEntry } from "../../../data/config_entries"; import { ConfigEntry } from "../../../data/config_entries";
@ -26,6 +26,7 @@ import {
import { import {
EntityRegistryEntry, EntityRegistryEntry,
findBatteryEntity, findBatteryEntity,
findBatteryChargingEntity,
updateEntityRegistryEntry, updateEntityRegistryEntry,
} from "../../../data/entity_registry"; } from "../../../data/entity_registry";
import { SceneEntities, showSceneEditor } from "../../../data/scene"; import { SceneEntities, showSceneEditor } from "../../../data/scene";
@ -117,6 +118,11 @@ export class HaConfigDevicePage extends LitElement {
| EntityRegistryEntry | EntityRegistryEntry
| undefined => findBatteryEntity(this.hass, entities)); | undefined => findBatteryEntity(this.hass, entities));
private _batteryChargingEntity = memoizeOne(
(entities: EntityRegistryEntry[]): EntityRegistryEntry | undefined =>
findBatteryChargingEntity(this.hass, entities)
);
protected firstUpdated(changedProps) { protected firstUpdated(changedProps) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
loadDeviceRegistryDetailDialog(); loadDeviceRegistryDetailDialog();
@ -145,9 +151,13 @@ export class HaConfigDevicePage extends LitElement {
const integrations = this._integrations(device, this.entries); const integrations = this._integrations(device, this.entries);
const entities = this._entities(this.deviceId, this.entities); const entities = this._entities(this.deviceId, this.entities);
const batteryEntity = this._batteryEntity(entities); const batteryEntity = this._batteryEntity(entities);
const batteryChargingEntity = this._batteryChargingEntity(entities);
const batteryState = batteryEntity const batteryState = batteryEntity
? this.hass.states[batteryEntity.entity_id] ? this.hass.states[batteryEntity.entity_id]
: undefined; : undefined;
const batteryChargingState = batteryChargingEntity
? this.hass.states[batteryChargingEntity.entity_id]
: undefined;
const area = this._computeArea(this.areas, device); const area = this._computeArea(this.areas, device);
return html` return html`
@ -201,10 +211,11 @@ export class HaConfigDevicePage extends LitElement {
? html` ? html`
<div class="battery"> <div class="battery">
${batteryState.state}% ${batteryState.state}%
<ha-state-icon <ha-battery-icon
.hass=${this.hass!} .hass=${this.hass!}
.stateObj=${batteryState} .batteryStateObj=${batteryState}
></ha-state-icon> .batteryChargingStateObj=${batteryChargingState}
></ha-battery-icon>
</div> </div>
` `
: "" : ""

View File

@ -14,7 +14,7 @@ import {
DataTableRowData, DataTableRowData,
RowClickedEvent, RowClickedEvent,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
import "../../../components/entity/ha-state-icon"; import "../../../components/entity/ha-battery-icon";
import { AreaRegistryEntry } from "../../../data/area_registry"; import { AreaRegistryEntry } from "../../../data/area_registry";
import { ConfigEntry } from "../../../data/config_entries"; import { ConfigEntry } from "../../../data/config_entries";
import { import {
@ -25,6 +25,7 @@ import {
import { import {
EntityRegistryEntry, EntityRegistryEntry,
findBatteryEntity, findBatteryEntity,
findBatteryChargingEntity,
} from "../../../data/entity_registry"; } from "../../../data/entity_registry";
import { domainToName } from "../../../data/integration"; import { domainToName } from "../../../data/integration";
import "../../../layouts/hass-tabs-subpage-data-table"; import "../../../layouts/hass-tabs-subpage-data-table";
@ -35,7 +36,7 @@ interface DeviceRowData extends DeviceRegistryEntry {
device?: DeviceRowData; device?: DeviceRowData;
area?: string; area?: string;
integration?: string; integration?: string;
battery_entity?: string; battery_entity?: [string | undefined, string | undefined];
} }
@customElement("ha-config-devices-dashboard") @customElement("ha-config-devices-dashboard")
@ -167,7 +168,10 @@ export class HaConfigDeviceDashboard extends LitElement {
) )
.join(", ") .join(", ")
: "No integration", : "No integration",
battery_entity: this._batteryEntity(device.id, deviceEntityLookup), battery_entity: [
this._batteryEntity(device.id, deviceEntityLookup),
this._batteryChargingEntity(device.id, deviceEntityLookup),
],
}; };
}); });
@ -201,17 +205,25 @@ export class HaConfigDeviceDashboard extends LitElement {
sortable: true, sortable: true,
type: "numeric", type: "numeric",
width: "90px", width: "90px",
template: (batteryEntity: string) => { template: (
const battery = batteryEntity batteryEntityPair: DeviceRowData["battery_entity"]
? this.hass.states[batteryEntity] ) => {
const battery =
batteryEntityPair && batteryEntityPair[0]
? this.hass.states[batteryEntityPair[0]]
: undefined;
const batteryCharging =
batteryEntityPair && batteryEntityPair[1]
? this.hass.states[batteryEntityPair[1]]
: undefined; : undefined;
return battery return battery
? html` ? html`
${isNaN(battery.state as any) ? "-" : battery.state}% ${isNaN(battery.state as any) ? "-" : battery.state}%
<ha-state-icon <ha-battery-icon
.hass=${this.hass!} .hass=${this.hass!}
.stateObj=${battery} .batteryStateObj=${battery}
></ha-state-icon> .batteryChargingStateObj=${batteryCharging}
></ha-battery-icon>
` `
: html` - `; : html` - `;
}, },
@ -267,17 +279,25 @@ export class HaConfigDeviceDashboard extends LitElement {
type: "numeric", type: "numeric",
width: "15%", width: "15%",
maxWidth: "90px", maxWidth: "90px",
template: (batteryEntity: string) => { template: (
const battery = batteryEntity batteryEntityPair: DeviceRowData["battery_entity"]
? this.hass.states[batteryEntity] ) => {
const battery =
batteryEntityPair && batteryEntityPair[0]
? this.hass.states[batteryEntityPair[0]]
: undefined;
const batteryCharging =
batteryEntityPair && batteryEntityPair[1]
? this.hass.states[batteryEntityPair[1]]
: undefined; : undefined;
return battery && !isNaN(battery.state as any) return battery && !isNaN(battery.state as any)
? html` ? html`
${battery.state}% ${battery.state}%
<ha-state-icon <ha-battery-icon
.hass=${this.hass!} .hass=${this.hass!}
.stateObj=${battery} .batteryStateObj=${battery}
></ha-state-icon> .batteryChargingStateObj=${batteryCharging}
></ha-battery-icon>
` `
: html` - `; : html` - `;
}, },
@ -336,6 +356,17 @@ export class HaConfigDeviceDashboard extends LitElement {
return batteryEntity ? batteryEntity.entity_id : undefined; return batteryEntity ? batteryEntity.entity_id : undefined;
} }
private _batteryChargingEntity(
deviceId: string,
deviceEntityLookup: DeviceEntityLookup
): string | undefined {
const batteryChargingEntity = findBatteryChargingEntity(
this.hass,
deviceEntityLookup[deviceId] || []
);
return batteryChargingEntity ? batteryChargingEntity.entity_id : undefined;
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) { private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const deviceId = ev.detail.id; const deviceId = ev.detail.id;
navigate(this, `/config/devices/device/${deviceId}`); navigate(this, `/config/devices/device/${deviceId}`);