diff --git a/src/cards/alarm-control-panel-card/alarm-control-panel-card-editor.ts b/src/cards/alarm-control-panel-card/alarm-control-panel-card-editor.ts
index 3a96be359..130203e77 100644
--- a/src/cards/alarm-control-panel-card/alarm-control-panel-card-editor.ts
+++ b/src/cards/alarm-control-panel-card/alarm-control-panel-card-editor.ts
@@ -10,13 +10,13 @@ import { assert } from "superstruct";
import setupCustomlocalize from "../../localize";
import "../../shared/editor/layout-picker";
import { configElementStyle } from "../../utils/editor-styles";
+import { stateIcon } from "../../utils/icons/state-icon";
import { EditorTarget } from "../../utils/lovelace/editor/types";
import {
alarmControlPanelCardCardConfigStruct,
AlarmControlPanelCardConfig,
} from "./alarm-control-panel-card-config";
import { ALARM_CONTROl_PANEL_CARD_EDITOR_NAME, ALARM_CONTROl_PANEL_ENTITY_DOMAINS } from "./const";
-import { getStateIcon } from "./utils";
const DOMAINS = [...ALARM_CONTROl_PANEL_ENTITY_DOMAINS];
@@ -46,8 +46,8 @@ export class SwitchCardEditor extends LitElement implements LovelaceCardEditor {
}
const dir = computeRTLDirection(this.hass);
- const entityState = this._config.entity ? this.hass.states[this._config.entity] : undefined;
- const entityIcon = entityState ? getStateIcon(entityState.state) : undefined;
+ const entity = this._config.entity ? this.hass.states[this._config.entity] : undefined;
+ const entityIcon = entity ? stateIcon(entity) : undefined;
const states = [
"armed_home",
diff --git a/src/cards/alarm-control-panel-card/alarm-control-panel-card.ts b/src/cards/alarm-control-panel-card/alarm-control-panel-card.ts
index 684841d19..846041c85 100644
--- a/src/cards/alarm-control-panel-card/alarm-control-panel-card.ts
+++ b/src/cards/alarm-control-panel-card/alarm-control-panel-card.ts
@@ -20,6 +20,8 @@ import "../../shared/state-item";
import { cardStyle } from "../../utils/card-styles";
import { registerCustomCard } from "../../utils/custom-cards";
import { actionHandler } from "../../utils/directives/action-handler-directive";
+import { alarmPanelIconAction } from "../../utils/icons/alarm-panel-icon";
+import { stateIcon } from "../../utils/icons/state-icon";
import { getLayoutFromConfig } from "../../utils/layout";
import { AlarmControlPanelCardConfig } from "./alarm-control-panel-card-config";
import "./alarm-control-panel-card-editor";
@@ -30,7 +32,6 @@ import {
} from "./const";
import {
getStateColor,
- getStateIcon,
getStateService,
isActionsAvailable,
isDisarmed,
@@ -142,7 +143,7 @@ export class AlarmControlPanelCard extends LitElement implements LovelaceCard {
const entity = this.hass.states[entity_id];
const name = this._config.name ?? entity.attributes.friendly_name;
- const icon = this._config.icon ?? getStateIcon(entity.state);
+ const icon = this._config.icon ?? stateIcon(entity);
const color = getStateColor(entity.state);
const shapePulse = shouldPulse(entity.state);
const layout = getLayoutFromConfig(this._config);
@@ -209,7 +210,7 @@ export class AlarmControlPanelCard extends LitElement implements LovelaceCard {
${actions.map(
(action) => html`
this._onTap(e, action.state)}
.disabled=${!isActionEnabled}
>
diff --git a/src/cards/alarm-control-panel-card/const.ts b/src/cards/alarm-control-panel-card/const.ts
index 07c747fda..264edf6dc 100644
--- a/src/cards/alarm-control-panel-card/const.ts
+++ b/src/cards/alarm-control-panel-card/const.ts
@@ -4,20 +4,6 @@ export const ALARM_CONTROl_PANEL_CARD_NAME = `${PREFIX_NAME}-alarm-control-panel
export const ALARM_CONTROl_PANEL_CARD_EDITOR_NAME = `${ALARM_CONTROl_PANEL_CARD_NAME}-editor`;
export const ALARM_CONTROl_PANEL_ENTITY_DOMAINS = ["alarm_control_panel"];
-export const ALARM_CONTROL_PANEL_CARD_STATE_ICON = {
- disarmed: "mdi:shield-off-outline",
- arming: "mdi:shield-sync-outline",
- armed_away: "mdi:shield-lock-outline",
- armed_home: "mdi:shield-home-outline",
- armed_night: "mdi:shield-moon-outline",
- armed_vacation: "mdi:shield-airplane-outline",
- armed_custom_bypass: "mdi:shield-half-full",
- pending: "mdi:shield-sync",
- triggered: "mdi:bell-ring-outline",
- unavailable: "mdi:bell-remove-outline",
-};
-export const ALARM_CONTROL_PANEL_CARD_DEFAULT_STATE_ICON = "mdi:shield-lock-outline";
-
export const ALARM_CONTROL_PANEL_CARD_STATE_COLOR = {
disarmed: "var(--rgb-state-alarm-disarmed)",
armed: "var(--rgb-state-alarm-armed)",
@@ -35,7 +21,3 @@ export const ALARM_CONTROL_PANEL_CARD_STATE_SERVICE = {
armed_vacation: "alarm_arm_vacation",
armed_custom_bypass: "alarm_arm_custom_bypass",
};
-
-/*
-armed_away, armed_home, armed_night, arming, disarmed, pending, triggered and unavailable
-*/
diff --git a/src/cards/alarm-control-panel-card/utils.ts b/src/cards/alarm-control-panel-card/utils.ts
index 1f4e7cf48..f182d3c03 100644
--- a/src/cards/alarm-control-panel-card/utils.ts
+++ b/src/cards/alarm-control-panel-card/utils.ts
@@ -1,9 +1,7 @@
import { HassEntity } from "home-assistant-js-websocket";
import {
ALARM_CONTROL_PANEL_CARD_DEFAULT_STATE_COLOR,
- ALARM_CONTROL_PANEL_CARD_DEFAULT_STATE_ICON,
ALARM_CONTROL_PANEL_CARD_STATE_COLOR,
- ALARM_CONTROL_PANEL_CARD_STATE_ICON,
ALARM_CONTROL_PANEL_CARD_STATE_SERVICE,
} from "./const";
@@ -14,18 +12,12 @@ export function getStateColor(state: string): string {
);
}
-export function getStateIcon(state: string): string {
- return (
- ALARM_CONTROL_PANEL_CARD_STATE_ICON[state] || ALARM_CONTROL_PANEL_CARD_DEFAULT_STATE_ICON
- );
-}
-
export function getStateService(state: string): string | undefined {
return ALARM_CONTROL_PANEL_CARD_STATE_SERVICE[state];
}
-export function shouldPulse(state:string): boolean {
- return ["arming", "triggered", "pending", "unavailable"].indexOf(state) >= 0
+export function shouldPulse(state: string): boolean {
+ return ["arming", "triggered", "pending", "unavailable"].indexOf(state) >= 0;
}
export function isActionsAvailable(entity: HassEntity) {
diff --git a/src/cards/chips-card/chips/alarm-control-panel-chip-editor.ts b/src/cards/chips-card/chips/alarm-control-panel-chip-editor.ts
index bfa4bfb76..285bca2e5 100644
--- a/src/cards/chips-card/chips/alarm-control-panel-chip-editor.ts
+++ b/src/cards/chips-card/chips/alarm-control-panel-chip-editor.ts
@@ -1,10 +1,11 @@
-import { fireEvent, HomeAssistant, stateIcon } from "custom-card-helpers";
+import { fireEvent, HomeAssistant } from "custom-card-helpers";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import setupCustomlocalize from "../../../localize";
import "../../../shared/editor/color-picker";
import "../../../shared/editor/info-picker";
import { configElementStyle } from "../../../utils/editor-styles";
+import { stateIcon } from "../../../utils/icons/state-icon";
import { computeChipEditorComponentName } from "../../../utils/lovelace/chip/chip-element";
import { AlarmControlPanelChipConfig } from "../../../utils/lovelace/chip/types";
import { EditorTarget } from "../../../utils/lovelace/editor/types";
@@ -66,6 +67,17 @@ export class AlarmControlPanelChipEditor extends LitElement implements LovelaceC
>
+
+
+
diff --git a/src/cards/cover-card/cover-card-editor.ts b/src/cards/cover-card/cover-card-editor.ts
index 502cce70b..a48a37374 100644
--- a/src/cards/cover-card/cover-card-editor.ts
+++ b/src/cards/cover-card/cover-card-editor.ts
@@ -3,7 +3,6 @@ import {
fireEvent,
HomeAssistant,
LovelaceCardEditor,
- stateIcon,
} from "custom-card-helpers";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
@@ -11,6 +10,7 @@ import { assert } from "superstruct";
import setupCustomlocalize from "../../localize";
import "../../shared/editor/layout-picker";
import { configElementStyle } from "../../utils/editor-styles";
+import { stateIcon } from "../../utils/icons/state-icon";
import { EditorTarget } from "../../utils/lovelace/editor/types";
import { COVER_CARD_EDITOR_NAME, COVER_ENTITY_DOMAINS } from "./const";
import { CoverCardConfig, coverCardConfigStruct } from "./cover-card-config";
diff --git a/src/cards/cover-card/cover-card.ts b/src/cards/cover-card/cover-card.ts
index 0f6977b1a..2f451858e 100644
--- a/src/cards/cover-card/cover-card.ts
+++ b/src/cards/cover-card/cover-card.ts
@@ -6,7 +6,6 @@ import {
HomeAssistant,
LovelaceCard,
LovelaceCardEditor,
- stateIcon,
} from "custom-card-helpers";
import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
@@ -21,6 +20,7 @@ import { cardStyle } from "../../utils/card-styles";
import { registerCustomCard } from "../../utils/custom-cards";
import { actionHandler } from "../../utils/directives/action-handler-directive";
import { isActive } from "../../utils/entity";
+import { stateIcon } from "../../utils/icons/state-icon";
import { getLayoutFromConfig, Layout } from "../../utils/layout";
import { COVER_CARD_EDITOR_NAME, COVER_CARD_NAME, COVER_ENTITY_DOMAINS } from "./const";
import "./controls/cover-buttons-control";
diff --git a/src/cards/entity-card/entity-card-editor.ts b/src/cards/entity-card/entity-card-editor.ts
index 1f45c1d40..636d02d02 100644
--- a/src/cards/entity-card/entity-card-editor.ts
+++ b/src/cards/entity-card/entity-card-editor.ts
@@ -3,7 +3,6 @@ import {
fireEvent,
HomeAssistant,
LovelaceCardEditor,
- stateIcon,
} from "custom-card-helpers";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
@@ -13,6 +12,7 @@ import "../../shared/editor/color-picker";
import "../../shared/editor/info-picker";
import "../../shared/editor/layout-picker";
import { configElementStyle } from "../../utils/editor-styles";
+import { stateIcon } from "../../utils/icons/state-icon";
import { EditorTarget } from "../../utils/lovelace/editor/types";
import { ENTITY_CARD_EDITOR_NAME } from "./const";
import { EntityCardConfig, entityCardConfigStruct } from "./entity-card-config";
diff --git a/src/cards/entity-card/entity-card.ts b/src/cards/entity-card/entity-card.ts
index f353a488b..1127993b8 100644
--- a/src/cards/entity-card/entity-card.ts
+++ b/src/cards/entity-card/entity-card.ts
@@ -6,7 +6,6 @@ import {
HomeAssistant,
LovelaceCard,
LovelaceCardEditor,
- stateIcon,
} from "custom-card-helpers";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
@@ -21,6 +20,7 @@ import { computeRgbColor } from "../../utils/colors";
import { registerCustomCard } from "../../utils/custom-cards";
import { actionHandler } from "../../utils/directives/action-handler-directive";
import { isActive, isAvailable } from "../../utils/entity";
+import { stateIcon } from "../../utils/icons/state-icon";
import { getInfo } from "../../utils/info";
import { getLayoutFromConfig } from "../../utils/layout";
import { ENTITY_CARD_EDITOR_NAME, ENTITY_CARD_NAME } from "./const";
diff --git a/src/cards/fan-card/fan-card-editor.ts b/src/cards/fan-card/fan-card-editor.ts
index 24bab0ecd..1f27c8ba4 100644
--- a/src/cards/fan-card/fan-card-editor.ts
+++ b/src/cards/fan-card/fan-card-editor.ts
@@ -3,7 +3,6 @@ import {
fireEvent,
HomeAssistant,
LovelaceCardEditor,
- stateIcon,
} from "custom-card-helpers";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
@@ -11,6 +10,7 @@ import { assert } from "superstruct";
import setupCustomlocalize from "../../localize";
import "../../shared/editor/layout-picker";
import { configElementStyle } from "../../utils/editor-styles";
+import { stateIcon } from "../../utils/icons/state-icon";
import { EditorTarget } from "../../utils/lovelace/editor/types";
import { FAN_CARD_EDITOR_NAME, FAN_ENTITY_DOMAINS } from "./const";
import { FanCardConfig, fanCardConfigStruct } from "./fan-card-config";
diff --git a/src/cards/fan-card/fan-card.ts b/src/cards/fan-card/fan-card.ts
index 2a13be770..317a780e1 100644
--- a/src/cards/fan-card/fan-card.ts
+++ b/src/cards/fan-card/fan-card.ts
@@ -6,7 +6,6 @@ import {
HomeAssistant,
LovelaceCard,
LovelaceCardEditor,
- stateIcon,
} from "custom-card-helpers";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
@@ -22,6 +21,7 @@ import { cardStyle } from "../../utils/card-styles";
import { registerCustomCard } from "../../utils/custom-cards";
import { actionHandler } from "../../utils/directives/action-handler-directive";
import { isActive, isAvailable } from "../../utils/entity";
+import { stateIcon } from "../../utils/icons/state-icon";
import { getLayoutFromConfig } from "../../utils/layout";
import { FAN_CARD_EDITOR_NAME, FAN_CARD_NAME, FAN_ENTITY_DOMAINS } from "./const";
import "./controls/fan-oscillate-control";
diff --git a/src/cards/light-card/light-card-editor.ts b/src/cards/light-card/light-card-editor.ts
index 9eb3bbf69..54de376cf 100644
--- a/src/cards/light-card/light-card-editor.ts
+++ b/src/cards/light-card/light-card-editor.ts
@@ -3,7 +3,6 @@ import {
fireEvent,
HomeAssistant,
LovelaceCardEditor,
- stateIcon,
} from "custom-card-helpers";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
@@ -11,6 +10,7 @@ import { assert } from "superstruct";
import setupCustomlocalize from "../../localize";
import "../../shared/editor/layout-picker";
import { configElementStyle } from "../../utils/editor-styles";
+import { stateIcon } from "../../utils/icons/state-icon";
import { EditorTarget } from "../../utils/lovelace/editor/types";
import { LIGHT_CARD_EDITOR_NAME, LIGHT_ENTITY_DOMAINS } from "./const";
import { LightCardConfig, lightCardConfigStruct } from "./light-card-config";
diff --git a/src/cards/light-card/light-card.ts b/src/cards/light-card/light-card.ts
index 93cd7de43..589b8312b 100644
--- a/src/cards/light-card/light-card.ts
+++ b/src/cards/light-card/light-card.ts
@@ -6,7 +6,6 @@ import {
HomeAssistant,
LovelaceCard,
LovelaceCardEditor,
- stateIcon,
} from "custom-card-helpers";
import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, PropertyValues, TemplateResult } from "lit";
@@ -22,6 +21,7 @@ import { cardStyle } from "../../utils/card-styles";
import { registerCustomCard } from "../../utils/custom-cards";
import { actionHandler } from "../../utils/directives/action-handler-directive";
import { isActive } from "../../utils/entity";
+import { stateIcon } from "../../utils/icons/state-icon";
import { getLayoutFromConfig } from "../../utils/layout";
import { LIGHT_CARD_EDITOR_NAME, LIGHT_CARD_NAME, LIGHT_ENTITY_DOMAINS } from "./const";
import "./controls/light-brightness-control";
diff --git a/src/cards/person-card/person-card-editor.ts b/src/cards/person-card/person-card-editor.ts
index 3a77f35ba..75cf28414 100644
--- a/src/cards/person-card/person-card-editor.ts
+++ b/src/cards/person-card/person-card-editor.ts
@@ -3,7 +3,6 @@ import {
fireEvent,
HomeAssistant,
LovelaceCardEditor,
- stateIcon,
} from "custom-card-helpers";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
@@ -11,6 +10,7 @@ import { assert } from "superstruct";
import setupCustomlocalize from "../../localize";
import "../../shared/editor/layout-picker";
import { configElementStyle } from "../../utils/editor-styles";
+import { stateIcon } from "../../utils/icons/state-icon";
import { EditorTarget } from "../../utils/lovelace/editor/types";
import { PERSON_CARD_EDITOR_NAME, PERSON_ENTITY_DOMAINS } from "./const";
import { PersonCardConfig, personCardConfigStruct } from "./person-card-config";
diff --git a/src/cards/person-card/person-card.ts b/src/cards/person-card/person-card.ts
index 8dbf74efd..c179c82c2 100644
--- a/src/cards/person-card/person-card.ts
+++ b/src/cards/person-card/person-card.ts
@@ -6,7 +6,6 @@ import {
HomeAssistant,
LovelaceCard,
LovelaceCardEditor,
- stateIcon as stateIconHelper,
} from "custom-card-helpers";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
@@ -19,6 +18,7 @@ import { cardStyle } from "../../utils/card-styles";
import { registerCustomCard } from "../../utils/custom-cards";
import { actionHandler } from "../../utils/directives/action-handler-directive";
import { isActive } from "../../utils/entity";
+import { stateIcon as stateIconHelper } from "../../utils/icons/state-icon";
import { getLayoutFromConfig } from "../../utils/layout";
import { PERSON_CARD_EDITOR_NAME, PERSON_CARD_NAME, PERSON_ENTITY_DOMAINS } from "./const";
import { PersonCardConfig } from "./person-card-config";
diff --git a/src/utils/icons/alarm-panel-icon.ts b/src/utils/icons/alarm-panel-icon.ts
new file mode 100644
index 000000000..8f85affa0
--- /dev/null
+++ b/src/utils/icons/alarm-panel-icon.ts
@@ -0,0 +1,42 @@
+export const alarmPanelIcon = (state?: string) => {
+ switch (state) {
+ case "armed_away":
+ return "mdi:shield-lock";
+ case "armed_vacation":
+ return "mdi:shield-airplane";
+ case "armed_home":
+ return "mdi:shield-home";
+ case "armed_night":
+ return "mdi:shield-moon";
+ case "armed_custom_bypass":
+ return "mdi:security";
+ case "pending":
+ case "arming":
+ return "mdi:shield-sync";
+ case "triggered":
+ return "mdi:bell-ring";
+ case "disarmed":
+ return "mdi:shield-off";
+ default:
+ return "mdi:shield";
+ }
+};
+
+export const alarmPanelIconAction = (state?: string) => {
+ switch (state) {
+ case "armed_away":
+ return "mdi:shield-lock-outline";
+ case "armed_vacation":
+ return "mdi:shield-airplane-outline";
+ case "armed_home":
+ return "mdi:shield-home-outline";
+ case "armed_night":
+ return "mdi:shield-moon-outline";
+ case "armed_custom_bypass":
+ return "mdi:shield-half-full";
+ case "disarmed":
+ return "mdi:shield-off-outline";
+ default:
+ return "mdi:shield-outline";
+ }
+};
diff --git a/src/utils/icons/binary-sensor-icon.ts b/src/utils/icons/binary-sensor-icon.ts
new file mode 100644
index 000000000..c97c198ef
--- /dev/null
+++ b/src/utils/icons/binary-sensor-icon.ts
@@ -0,0 +1,58 @@
+import { HassEntity } from "home-assistant-js-websocket";
+
+export const binarySensorIcon = (state?: string, entity?: HassEntity) => {
+ const isOff = state === "off";
+ switch (entity?.attributes.device_class) {
+ case "battery":
+ return isOff ? "mdi:battery" : "mdi:battery-outline";
+ case "battery_charging":
+ return isOff ? "mdi:battery" : "mdi:battery-charging";
+ case "cold":
+ return isOff ? "mdi:thermometer" : "mdi:snowflake";
+ case "connectivity":
+ return isOff ? "mdi:close-network-outline" : "mdi:check-network-outline";
+ case "door":
+ return isOff ? "mdi:door-closed" : "mdi:door-open";
+ case "garage_door":
+ return isOff ? "mdi:garage" : "mdi:garage-open";
+ case "power":
+ return isOff ? "mdi:power-plug-off" : "mdi:power-plug";
+ case "gas":
+ case "problem":
+ case "safety":
+ case "tamper":
+ return isOff ? "mdi:check-circle" : "mdi:alert-circle";
+ case "smoke":
+ return isOff ? "mdi:check-circle" : "mdi:smoke";
+ case "heat":
+ return isOff ? "mdi:thermometer" : "mdi:fire";
+ case "light":
+ return isOff ? "mdi:brightness5" : "mdi:brightness-7";
+ case "lock":
+ return isOff ? "mdi:lock" : "mdi:lock-open";
+ case "moisture":
+ return isOff ? "mdi:water-off" : "mdi:water";
+ case "motion":
+ return isOff ? "mdi:motion-sensor-off" : "mdi:motion-sensor";
+ case "occupancy":
+ return isOff ? "mdi:home-outline" : "mdi:home";
+ case "opening":
+ return isOff ? "mdi:square" : "mdi:square-outline";
+ case "plug":
+ return isOff ? "mdi:power-plug-off" : "mdi:power-plug";
+ case "presence":
+ return isOff ? "mdi:home-outline" : "mdi:home";
+ case "running":
+ return isOff ? "mdi:stop" : "mdi:play";
+ case "sound":
+ return isOff ? "mdi:music-note-off" : "mdi:music-note";
+ case "update":
+ return isOff ? "mdi:package" : "mdi:package-up";
+ case "vibration":
+ return isOff ? "mdi:crop-portrait" : "mdi:vibrate";
+ case "window":
+ return isOff ? "mdi:window-closed" : "mdi:window-open";
+ default:
+ return isOff ? "mdi:radiobox-blank" : "mdi:checkbox-marked-circle";
+ }
+};
diff --git a/src/utils/icons/cover-icon.ts b/src/utils/icons/cover-icon.ts
new file mode 100644
index 000000000..f08fa3ce3
--- /dev/null
+++ b/src/utils/icons/cover-icon.ts
@@ -0,0 +1,111 @@
+import { HassEntity } from "home-assistant-js-websocket";
+
+export const coverIcon = (state?: string, entity?: HassEntity): string => {
+ const open = state !== "closed";
+
+ switch (entity?.attributes.device_class) {
+ case "garage":
+ switch (state) {
+ case "opening":
+ return "mdi:arrow-up-box";
+ case "closing":
+ return "mdi:arrow-down-box";
+ case "closed":
+ return "mdi:garage";
+ default:
+ return "mdi:garage-open";
+ }
+ case "gate":
+ switch (state) {
+ case "opening":
+ case "closing":
+ return "mdi:gate-arrow-right";
+ case "closed":
+ return "mdi:gate";
+ default:
+ return "mdi:gate-open";
+ }
+ case "door":
+ return open ? "mdi:door-open" : "mdi:door-closed";
+ case "damper":
+ return open ? "md:circle" : "mdi:circle-slice-8";
+ case "shutter":
+ switch (state) {
+ case "opening":
+ return "mdi:arrow-up-box";
+ case "closing":
+ return "mdi:arrow-down-box";
+ case "closed":
+ return "mdi:window-shutter";
+ default:
+ return "mdi:window-shutter-open";
+ }
+ case "curtain":
+ switch (state) {
+ case "opening":
+ return "mdi:arrow-split-vertical";
+ case "closing":
+ return "mdi:arrow-collapse-horizontal";
+ case "closed":
+ return "mdi:curtains-closed";
+ default:
+ return "mdi:curtains";
+ }
+ case "blind":
+ case "shade":
+ switch (state) {
+ case "opening":
+ return "mdi:arrow-up-box";
+ case "closing":
+ return "mdi:arrow-down-box";
+ case "closed":
+ return "mdi:blinds";
+ default:
+ return "mdi:blinds-open";
+ }
+ case "window":
+ switch (state) {
+ case "opening":
+ return "mdi:arrow-up-box";
+ case "closing":
+ return "mdi:arrow-down-box";
+ case "closed":
+ return "mdi:window-closed";
+ default:
+ return "mdi:window-open";
+ }
+ }
+
+ switch (state) {
+ case "opening":
+ return "mdi:arrow-up-box";
+ case "closing":
+ return "mdi:arrow-down-box";
+ case "closed":
+ return "mdi:window-closed";
+ default:
+ return "mdi:window-open";
+ }
+};
+
+export const computeOpenIcon = (stateObj: HassEntity): string => {
+ switch (stateObj.attributes.device_class) {
+ case "awning":
+ case "door":
+ case "gate":
+ return "mdi:arrow-expand-horizontal";
+ default:
+ return "mdi:arrow-up";
+ }
+};
+
+export const computeCloseIcon = (stateObj: HassEntity): string => {
+ switch (stateObj.attributes.device_class) {
+ case "awning":
+ case "door":
+ case "gate":
+ return "mdi:arrow-collapse-horizontal";
+ default:
+ return "mdi:arrow-down";
+ }
+};
diff --git a/src/utils/icons/domain-icon.ts b/src/utils/icons/domain-icon.ts
new file mode 100644
index 000000000..fbc8c1d67
--- /dev/null
+++ b/src/utils/icons/domain-icon.ts
@@ -0,0 +1,159 @@
+import { HassEntity } from "home-assistant-js-websocket";
+import { alarmPanelIcon } from "./alarm-panel-icon";
+import { binarySensorIcon } from "./binary-sensor-icon";
+import { coverIcon } from "./cover-icon";
+import { sensorIcon } from "./sensor-icon";
+
+const DEFAULT_DOMAIN_ICON = "mdi:bookmark";
+
+const FIXED_DOMAIN_ICONS = {
+ alert: "mdi:alert",
+ air_quality: "mdi:air-filter",
+ automation: "mdi:robot",
+ calendar: "mdi:calendar",
+ camera: "mdi:video",
+ climate: "mdi:thermostat",
+ configurator: "mdi:cog",
+ conversation: "mdi:text-to-speech",
+ counter: "mdi:counter",
+ fan: "mdi:fan",
+ google_assistant: "mdi:google-assistant",
+ group: "mdi:google-circles-communities",
+ homeassistant: "mdi:home-assistant",
+ homekit: "mdi:home-automation",
+ image_processing: "mdi:image-filter-frames",
+ input_button: "mdi:gesture-tap-button",
+ input_datetime: "mdi:calendar-clock",
+ input_number: "mdi:ray-vertex",
+ input_select: "mdi:format-list-bulleted",
+ input_text: "mdi:form-textbox",
+ light: "mdi:lightbulb",
+ mailbox: "mdi:mailbox",
+ notify: "mdi:comment-alert",
+ number: "mdi:ray-vertex",
+ persistent_notification: "mdi:bell",
+ person: "mdi:account",
+ plant: "mdi:flower",
+ proximity: "mdi:apple-safari",
+ remote: "mdi:remote",
+ scene: "mdi:palette",
+ script: "mdi:script-text",
+ select: "mdi:format-list-bulleted",
+ sensor: "mdi:eye",
+ siren: "mdi:bullhorn",
+ simple_alarm: "mdi:bell",
+ sun: "mdi:white-balance-sunny",
+ timer: "mdi:timer-outline",
+ updater: "mdi:cloud-upload",
+ vacuum: "mdi:robot-vacuum",
+ water_heater: "mdi:thermometer",
+ weather: "mdi:weather-cloudy",
+ zone: "mdi:map-marker-radius",
+};
+
+export function domainIcon(domain: string, entity?: HassEntity, state?: string): string {
+ switch (domain) {
+ case "alarm_control_panel":
+ return alarmPanelIcon(state);
+
+ case "binary_sensor":
+ return binarySensorIcon(state, entity);
+
+ case "button":
+ switch (entity?.attributes.device_class) {
+ case "restart":
+ return "mdi:restart";
+ case "update":
+ return "mdi:package-up";
+ default:
+ return "mdi:gesture-tap-button";
+ }
+
+ case "cover":
+ return coverIcon(state, entity);
+
+ case "device_tracker":
+ if (entity?.attributes.source_type === "router") {
+ return state === "home" ? "mdi:lan-connect" : "mdi:lan-disconnect";
+ }
+ if (["bluetooth", "bluetooth_le"].includes(entity?.attributes.source_type)) {
+ return state === "home" ? "mdi:bluetooth-connect" : "mdi:bluetooth";
+ }
+ return state === "not_home" ? "mdi:account-arrow-right" : "mdi:account";
+
+ case "humidifier":
+ return state && state === "off" ? "mdi:air-humidifier-off" : "mdi:air-humidifier";
+
+ case "input_boolean":
+ return state === "on" ? "mdi:check-circle-outline" : "mdi:close-circle-outline";
+
+ case "lock":
+ switch (state) {
+ case "unlocked":
+ return "mdi:lock-open";
+ case "jammed":
+ return "mdi:lock-alert";
+ case "locking":
+ case "unlocking":
+ return "mdi:lock-clock";
+ default:
+ return "mdi:lock";
+ }
+
+ case "media_player":
+ return state === "playing" ? "mdi:cast-connected" : "mdi:cast";
+
+ case "switch":
+ switch (entity?.attributes.device_class) {
+ case "outlet":
+ return state === "on" ? "mdi:power-plug" : "mdi:power-plug-off";
+ case "switch":
+ return state === "on" ? "mdi:toggle-switch" : "mdi:toggle-switch-off";
+ default:
+ return "mdi:flash";
+ }
+
+ case "zwave":
+ switch (state) {
+ case "dead":
+ return "mdi:emoticon-dead";
+ case "sleeping":
+ return "mdi:sleep";
+ case "initializing":
+ return "mdi:timer-sand";
+ default:
+ return "mdi:z-wave";
+ }
+
+ case "sensor": {
+ const icon = sensorIcon(entity);
+ if (icon) {
+ return icon;
+ }
+
+ break;
+ }
+
+ case "input_datetime":
+ if (!entity?.attributes.has_date) {
+ return "mdi:clock";
+ }
+ if (!entity.attributes.has_time) {
+ return "mdi:calendar";
+ }
+ break;
+
+ case "sun":
+ return entity?.state === "above_horizon"
+ ? FIXED_DOMAIN_ICONS[domain]
+ : "mdi:weather-night";
+ }
+
+ if (domain in FIXED_DOMAIN_ICONS) {
+ return FIXED_DOMAIN_ICONS[domain];
+ }
+
+ // eslint-disable-next-line
+ console.warn(`Unable to find icon for domain ${domain}`);
+ return DEFAULT_DOMAIN_ICON;
+}
diff --git a/src/utils/icons/sensor-icon.ts b/src/utils/icons/sensor-icon.ts
new file mode 100644
index 000000000..b86735227
--- /dev/null
+++ b/src/utils/icons/sensor-icon.ts
@@ -0,0 +1,112 @@
+import { UNIT_C, UNIT_F } from "custom-card-helpers";
+import { HassEntity } from "home-assistant-js-websocket";
+
+const FIXED_DEVICE_CLASS_ICONS = {
+ apparent_power: "mdi:flash",
+ aqi: "mdi:air-filter",
+ carbon_dioxide: "mdi:molecule-co2",
+ carbon_monoxide: "mdi:molecule-co",
+ current: "mdi:current-ac",
+ date: "mdi:calendar",
+ energy: "mdi:lightning-bolt",
+ frequency: "mdi:sine-wave",
+ gas: "mdi:gas-cylinder",
+ humidity: "mdi:water-percent",
+ illuminance: "mdi:brightness-5",
+ monetary: "mdi:cash",
+ nitrogen_dioxide: "mdi:molecule",
+ nitrogen_monoxide: "mdi:molecule",
+ nitrous_oxide: "mdi:molecule",
+ ozone: "mdi:molecule",
+ pm1: "mdi:molecule",
+ pm10: "mdi:molecule",
+ pm25: "mdi:molecule",
+ power: "mdi:flash",
+ power_factor: "mdi:angle-acute",
+ pressure: "mdi:gauge",
+ reactive_power: "mdi:flash",
+ signal_strength: "mdi:wifi",
+ sulphur_dioxide: "mdi:molecule",
+ temperature: "mdi:thermometer",
+ timestamp: "mdi:clock",
+ volatile_organic_compounds: "mdi:molecule",
+ voltage: "mdi:sine-wave",
+};
+
+const SENSOR_DEVICE_CLASS_BATTERY = "battery";
+
+const BATTERY_ICONS = {
+ 10: "mdi:battery-10",
+ 20: "mdi:battery-20",
+ 30: "mdi:battery-30",
+ 40: "mdi:battery-40",
+ 50: "mdi:battery-50",
+ 60: "mdi:battery-60",
+ 70: "mdi:battery-70",
+ 80: "mdi:battery-80",
+ 90: "mdi:battery-90",
+ 100: "mdi:battery",
+};
+const BATTERY_CHARGING_ICONS = {
+ 10: "mdi:battery-charging-10",
+ 20: "mdi:battery-charging-20",
+ 30: "mdi:battery-charging-30",
+ 40: "mdi:battery-charging-40",
+ 50: "mdi:battery-charging-50",
+ 60: "mdi:battery-charging-60",
+ 70: "mdi:battery-charging-70",
+ 80: "mdi:battery-charging-80",
+ 90: "mdi:battery-charging-90",
+ 100: "mdi:battery-charging",
+};
+
+const batteryStateIcon = (batteryEntity: HassEntity, batteryChargingEntity?: HassEntity) => {
+ const battery = batteryEntity.state;
+ const batteryCharging = batteryChargingEntity?.state === "on";
+
+ return batteryIcon(battery, batteryCharging);
+};
+
+export const batteryIcon = (batteryState: number | string, batteryCharging?: boolean) => {
+ const batteryValue = Number(batteryState);
+ if (isNaN(batteryValue)) {
+ if (batteryState === "off") {
+ return "mdi:battery";
+ }
+ if (batteryState === "on") {
+ return "mdi:battery-alert";
+ }
+ return "mdi:battery-unknown";
+ }
+
+ const batteryRound = Math.round(batteryValue / 10) * 10;
+ if (batteryCharging && batteryValue >= 10) {
+ return BATTERY_CHARGING_ICONS[batteryRound];
+ }
+ if (batteryCharging) {
+ return "mdi:battery-charging-outline";
+ }
+ if (batteryValue <= 5) {
+ return "mdi:battery-alert-variant-outline";
+ }
+ return BATTERY_ICONS[batteryRound];
+};
+
+export const sensorIcon = (entity?: HassEntity): string | undefined => {
+ const dclass = entity?.attributes.device_class;
+
+ if (dclass && dclass in FIXED_DEVICE_CLASS_ICONS) {
+ return FIXED_DEVICE_CLASS_ICONS[dclass];
+ }
+
+ if (dclass === SENSOR_DEVICE_CLASS_BATTERY) {
+ return entity ? batteryStateIcon(entity) : "mdi:battery";
+ }
+
+ const unit = entity?.attributes.unit_of_measurement;
+ if (unit === UNIT_C || unit === UNIT_F) {
+ return "mdi:thermometer";
+ }
+
+ return undefined;
+};
diff --git a/src/utils/icons/state-icon.ts b/src/utils/icons/state-icon.ts
new file mode 100644
index 000000000..461e3a7c7
--- /dev/null
+++ b/src/utils/icons/state-icon.ts
@@ -0,0 +1,11 @@
+import { computeDomain } from "custom-card-helpers";
+import { HassEntity } from "home-assistant-js-websocket";
+import { domainIcon } from "./domain-icon";
+
+export function stateIcon(entity: HassEntity): string {
+ const domain = computeDomain(entity.entity_id);
+
+ const state = entity.state;
+
+ return domainIcon(domain, entity, state);
+}