From 3bc54aa9e03436489bc02bc9d5a7522f5ba48a67 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 12 Jul 2020 09:30:27 -1000 Subject: [PATCH] Show battery charging state in the config panels (#6356) --- src/common/entity/battery_icon.ts | 28 ++++++++ src/common/entity/sensor_icon.ts | 25 +------ src/components/entity/ha-battery-icon.ts | 20 ++++++ src/data/entity_registry.ts | 11 ++++ .../config/devices/ha-config-device-page.ts | 19 ++++-- .../devices/ha-config-devices-dashboard.ts | 65 ++++++++++++++----- 6 files changed, 124 insertions(+), 44 deletions(-) create mode 100644 src/common/entity/battery_icon.ts create mode 100644 src/components/entity/ha-battery-icon.ts diff --git a/src/common/entity/battery_icon.ts b/src/common/entity/battery_icon.ts new file mode 100644 index 0000000000..26ffb3a3d8 --- /dev/null +++ b/src/common/entity/battery_icon.ts @@ -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; +}; diff --git a/src/common/entity/sensor_icon.ts b/src/common/entity/sensor_icon.ts index 32d7436d3b..1ec97ff5d9 100644 --- a/src/common/entity/sensor_icon.ts +++ b/src/common/entity/sensor_icon.ts @@ -2,6 +2,7 @@ import { HassEntity } from "home-assistant-js-websocket"; import { UNIT_C, UNIT_F } from "../const"; import { domainIcon } from "./domain_icon"; +import { batteryIcon } from "./battery_icon"; const fixedDeviceClassIcons = { humidity: "hass:water-percent", @@ -19,29 +20,7 @@ export const sensorIcon = (state: HassEntity) => { return fixedDeviceClassIcons[dclass]; } if (dclass === "battery") { - const battery = Number(state.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}`; + return batteryIcon(state); } const unit = state.attributes.unit_of_measurement; diff --git a/src/components/entity/ha-battery-icon.ts b/src/components/entity/ha-battery-icon.ts new file mode 100644 index 0000000000..19429bd20c --- /dev/null +++ b/src/components/entity/ha-battery-icon.ts @@ -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` + + `; + } +} + +customElements.define("ha-battery-icon", HaBatteryIcon); diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index 5abaa9084b..a5d897ca8b 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -37,6 +37,17 @@ export const findBatteryEntity = ( 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 = ( hass: HomeAssistant, entry: EntityRegistryEntry diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index fd10d1d175..9987b894cc 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -14,7 +14,7 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { createValidEntityId } from "../../../common/entity/valid_entity_id"; import { compare } from "../../../common/string/compare"; -import "../../../components/entity/ha-state-icon"; +import "../../../components/entity/ha-battery-icon"; import "../../../components/ha-icon-next"; import { AreaRegistryEntry } from "../../../data/area_registry"; import { ConfigEntry } from "../../../data/config_entries"; @@ -26,6 +26,7 @@ import { import { EntityRegistryEntry, findBatteryEntity, + findBatteryChargingEntity, updateEntityRegistryEntry, } from "../../../data/entity_registry"; import { SceneEntities, showSceneEditor } from "../../../data/scene"; @@ -117,6 +118,11 @@ export class HaConfigDevicePage extends LitElement { | EntityRegistryEntry | undefined => findBatteryEntity(this.hass, entities)); + private _batteryChargingEntity = memoizeOne( + (entities: EntityRegistryEntry[]): EntityRegistryEntry | undefined => + findBatteryChargingEntity(this.hass, entities) + ); + protected firstUpdated(changedProps) { super.firstUpdated(changedProps); loadDeviceRegistryDetailDialog(); @@ -145,9 +151,13 @@ export class HaConfigDevicePage extends LitElement { const integrations = this._integrations(device, this.entries); const entities = this._entities(this.deviceId, this.entities); const batteryEntity = this._batteryEntity(entities); + const batteryChargingEntity = this._batteryChargingEntity(entities); const batteryState = batteryEntity ? this.hass.states[batteryEntity.entity_id] : undefined; + const batteryChargingState = batteryChargingEntity + ? this.hass.states[batteryChargingEntity.entity_id] + : undefined; const area = this._computeArea(this.areas, device); return html` @@ -201,10 +211,11 @@ export class HaConfigDevicePage extends LitElement { ? html`
${batteryState.state}% - + .batteryStateObj=${batteryState} + .batteryChargingStateObj=${batteryChargingState} + >
` : "" diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts index 907756ad5f..673d0ff7f3 100644 --- a/src/panels/config/devices/ha-config-devices-dashboard.ts +++ b/src/panels/config/devices/ha-config-devices-dashboard.ts @@ -14,7 +14,7 @@ import { DataTableRowData, RowClickedEvent, } 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 { ConfigEntry } from "../../../data/config_entries"; import { @@ -25,6 +25,7 @@ import { import { EntityRegistryEntry, findBatteryEntity, + findBatteryChargingEntity, } from "../../../data/entity_registry"; import { domainToName } from "../../../data/integration"; import "../../../layouts/hass-tabs-subpage-data-table"; @@ -35,7 +36,7 @@ interface DeviceRowData extends DeviceRegistryEntry { device?: DeviceRowData; area?: string; integration?: string; - battery_entity?: string; + battery_entity?: [string | undefined, string | undefined]; } @customElement("ha-config-devices-dashboard") @@ -167,7 +168,10 @@ export class HaConfigDeviceDashboard extends LitElement { ) .join(", ") : "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, type: "numeric", width: "90px", - template: (batteryEntity: string) => { - const battery = batteryEntity - ? this.hass.states[batteryEntity] - : undefined; + template: ( + batteryEntityPair: DeviceRowData["battery_entity"] + ) => { + const battery = + batteryEntityPair && batteryEntityPair[0] + ? this.hass.states[batteryEntityPair[0]] + : undefined; + const batteryCharging = + batteryEntityPair && batteryEntityPair[1] + ? this.hass.states[batteryEntityPair[1]] + : undefined; return battery ? html` ${isNaN(battery.state as any) ? "-" : battery.state}% - + .batteryStateObj=${battery} + .batteryChargingStateObj=${batteryCharging} + > ` : html` - `; }, @@ -267,17 +279,25 @@ export class HaConfigDeviceDashboard extends LitElement { type: "numeric", width: "15%", maxWidth: "90px", - template: (batteryEntity: string) => { - const battery = batteryEntity - ? this.hass.states[batteryEntity] - : undefined; + template: ( + batteryEntityPair: DeviceRowData["battery_entity"] + ) => { + const battery = + batteryEntityPair && batteryEntityPair[0] + ? this.hass.states[batteryEntityPair[0]] + : undefined; + const batteryCharging = + batteryEntityPair && batteryEntityPair[1] + ? this.hass.states[batteryEntityPair[1]] + : undefined; return battery && !isNaN(battery.state as any) ? html` ${battery.state}% - + .batteryStateObj=${battery} + .batteryChargingStateObj=${batteryCharging} + > ` : html` - `; }, @@ -336,6 +356,17 @@ export class HaConfigDeviceDashboard extends LitElement { 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) { const deviceId = ev.detail.id; navigate(this, `/config/devices/device/${deviceId}`);