From aec0eb3c78b158d61e5d6a27e81e83eb0e7f546d Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 28 Nov 2022 14:37:07 +0100 Subject: [PATCH] Improve state colors and add documentation for entity state (#14437) * Add documentation for entity icons and colors * Add domains * Add domains * improve colors * Add fan color * Change fan to cyan * color rules adjustments --- gallery/src/pages/misc/entity-state.markdown | 3 + gallery/src/pages/misc/entity-state.ts | 376 ++++++++++++++++++ .../entity/color/alarm_control_panel_color.ts | 5 +- .../entity/color/binary_sensor_color.ts | 24 +- src/common/entity/color/cover_color.ts | 10 - src/common/entity/color/lock_color.ts | 2 - src/common/entity/color/sensor_color.ts | 22 +- src/common/entity/state_active.ts | 4 + src/common/entity/state_color.ts | 18 +- src/resources/ha-style.ts | 33 +- 10 files changed, 424 insertions(+), 73 deletions(-) create mode 100644 gallery/src/pages/misc/entity-state.markdown create mode 100644 gallery/src/pages/misc/entity-state.ts delete mode 100644 src/common/entity/color/cover_color.ts diff --git a/gallery/src/pages/misc/entity-state.markdown b/gallery/src/pages/misc/entity-state.markdown new file mode 100644 index 0000000000..c12b0fc5fe --- /dev/null +++ b/gallery/src/pages/misc/entity-state.markdown @@ -0,0 +1,3 @@ +--- +title: Entity State +--- diff --git a/gallery/src/pages/misc/entity-state.ts b/gallery/src/pages/misc/entity-state.ts new file mode 100644 index 0000000000..16e2d7c73f --- /dev/null +++ b/gallery/src/pages/misc/entity-state.ts @@ -0,0 +1,376 @@ +import { + HassEntity, + HassEntityAttributeBase, +} from "home-assistant-js-websocket"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import { styleMap } from "lit/directives/style-map"; +import memoizeOne from "memoize-one"; +import { computeDomain } from "../../../../src/common/entity/compute_domain"; +import { computeStateDisplay } from "../../../../src/common/entity/compute_state_display"; +import { stateColorCss } from "../../../../src/common/entity/state_color"; +import { stateIconPath } from "../../../../src/common/entity/state_icon_path"; +import "../../../../src/components/data-table/ha-data-table"; +import type { DataTableColumnContainer } from "../../../../src/components/data-table/ha-data-table"; +import "../../../../src/components/ha-chip"; +import { provideHass } from "../../../../src/fake_data/provide_hass"; +import { HomeAssistant } from "../../../../src/types"; + +const SENSOR_DEVICE_CLASSES = [ + "apparent_power", + "aqi", + // "battery" + "carbon_dioxide", + "carbon_monoxide", + "current", + "date", + "distance", + "duration", + "energy", + "frequency", + "gas", + "humidity", + "illuminance", + "moisture", + "monetary", + "nitrogen_dioxide", + "nitrogen_monoxide", + "nitrous_oxide", + "ozone", + "pm1", + "pm10", + "pm25", + "power_factor", + "power", + "precipitation", + "precipitation_intensity", + "pressure", + "reactive_power", + "signal_strength", + "speed", + "sulphur_dioxide", + "temperature", + "timestamp", + "volatile_organic_compounds", + "voltage", + "volume", + "water", + "weight", + "wind_speed", +]; + +const BINARY_SENSOR_DEVICE_CLASSES = [ + "battery", + "battery_charging", + "carbon_monoxide", + "cold", + "connectivity", + "door", + "garage_door", + "gas", + "heat", + "light", + "lock", + "moisture", + "motion", + "moving", + "occupancy", + "opening", + "plug", + "power", + "presence", + "problem", + "running", + "safety", + "smoke", + "sound", + "tamper", + "update", + "vibration", + "window", +]; + +const ENTITIES: HassEntity[] = [ + // Alarm control panel + createEntity("alarm_control_panel.disarmed", "disarmed"), + createEntity("alarm_control_panel.armed_home", "armed_home"), + createEntity("alarm_control_panel.armed_away", "armed_away"), + createEntity("alarm_control_panel.armed_night", "armed_night"), + createEntity("alarm_control_panel.armed_vacation", "armed_vacation"), + createEntity( + "alarm_control_panel.armed_custom_bypass", + "armed_custom_bypass" + ), + createEntity("alarm_control_panel.pending", "pending"), + createEntity("alarm_control_panel.arming", "arming"), + createEntity("alarm_control_panel.disarming", "disarming"), + createEntity("alarm_control_panel.triggered", "triggered"), + // Binary Sensor + ...BINARY_SENSOR_DEVICE_CLASSES.map((dc) => + createEntity(`binary_sensor.${dc}`, "on", dc) + ), + // Button + createEntity("button.restart", "unknown", "restart"), + createEntity("button.update", "unknown", "update"), + // Calendar + createEntity("calendar.on", "on"), + createEntity("calendar.off", "off"), + // Climate + createEntity("climate.off", "off"), + createEntity("climate.heat", "heat"), + createEntity("climate.cool", "cool"), + createEntity("climate.heat_cool", "heat_cool"), + createEntity("climate.auto", "auto"), + createEntity("climate.dry", "dry"), + createEntity("climate.fan_only", "fan_only"), + // Cover + createEntity("cover.opening", "opening"), + createEntity("cover.open", "open"), + createEntity("cover.closing", "closing"), + createEntity("cover.closed", "closed"), + createEntity("cover.awning", "open", "awning"), + createEntity("cover.blind", "open", "blind"), + createEntity("cover.curtain", "open", "curtain"), + createEntity("cover.damper", "open", "damper"), + createEntity("cover.door", "open", "door"), + createEntity("cover.garage", "open", "garage"), + createEntity("cover.gate", "open", "gate"), + createEntity("cover.shade", "open", "shade"), + createEntity("cover.shutter", "open", "shutter"), + createEntity("cover.window", "open", "window"), + // Device tracker/person + createEntity("device_tracker.home", "home"), + createEntity("device_tracker.not_home", "not_home"), + createEntity("device_tracker.work", "work"), + createEntity("person.home", "home"), + createEntity("person.not_home", "not_home"), + createEntity("person.work", "work"), + // Fan + createEntity("fan.on", "on"), + createEntity("fan.off", "off"), + // Humidifier + createEntity("humidifier.on", "on"), + createEntity("humidifier.off", "off"), + // Light + createEntity("light.on", "on"), + createEntity("light.off", "off"), + // Locks + createEntity("lock.locked", "locked"), + createEntity("lock.unlocked", "unlocked"), + createEntity("lock.locking", "locking"), + createEntity("lock.unlocking", "unlocking"), + createEntity("lock.jammed", "jammed"), + // Media Player + createEntity("media_player.off", "off"), + createEntity("media_player.on", "on"), + createEntity("media_player.idle", "idle"), + createEntity("media_player.playing", "playing"), + createEntity("media_player.paused", "paused"), + createEntity("media_player.standby", "standby"), + createEntity("media_player.buffering", "buffering"), + createEntity("media_player.tv_off", "off", "tv"), + createEntity("media_player.tv_playing", "playing", "tv"), + createEntity("media_player.tv_paused", "paused", "tv"), + createEntity("media_player.tv_standby", "standby", "tv"), + createEntity("media_player.receiver_off", "off", "receiver"), + createEntity("media_player.receiver_playing", "playing", "receiver"), + createEntity("media_player.receiver_paused", "paused", "receiver"), + createEntity("media_player.receiver_standby", "standby", "receiver"), + createEntity("media_player.speaker_off", "off", "speaker"), + createEntity("media_player.speaker_playing", "playing", "speaker"), + createEntity("media_player.speaker_paused", "paused", "speaker"), + createEntity("media_player.speaker_standby", "standby", "speaker"), + // Sensor + ...SENSOR_DEVICE_CLASSES.map((dc) => createEntity(`sensor.${dc}`, "10", dc)), + // Battery sensor + ...[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100].map((value) => + createEntity(`sensor.battery_${value}`, value.toString(), "battery") + ), + // Siren + createEntity("siren.off", "off"), + createEntity("siren.on", "on"), + // Switch + createEntity("switch.off", "off"), + createEntity("switch.on", "on"), + createEntity("switch.outlet_off", "off", "outlet"), + createEntity("switch.outlet_on", "on", "outlet"), + createEntity("switch.switch_off", "off", "switch"), + createEntity("switch.switch_on", "on", "switch"), + // Vacuum + createEntity("vacuum.cleaning", "cleaning"), + createEntity("vacuum.docked", "docked"), + createEntity("vacuum.paused", "paused"), + createEntity("vacuum.idle", "idle"), + createEntity("vacuum.returning", "returning"), + createEntity("vacuum.error", "error"), + createEntity("vacuum.cleaning", "cleaning"), + createEntity("vacuum.off", "off"), + createEntity("vacuum.on", "on"), + // Update + createEntity("update.off", "off", undefined, { + installed_version: "1.0.0", + latest_version: "2.0.0", + }), + createEntity("update.on", "on", undefined, { + installed_version: "1.0.0", + latest_version: "2.0.0", + }), + createEntity("update.installing", "on", undefined, { + installed_version: "1.0.0", + latest_version: "2.0.0", + in_progress: true, + }), + createEntity("update.off", "off", "firmware", { + installed_version: "1.0.0", + latest_version: "2.0.0", + }), + createEntity("update.on", "on", "firmware", { + installed_version: "1.0.0", + latest_version: "2.0.0", + }), +]; + +function createEntity( + entity_id: string, + state: string, + device_class?: string, + attributes?: HassEntityAttributeBase | HassEntity["attributes"] +): HassEntity { + return { + entity_id, + state, + attributes: { + ...attributes, + device_class: device_class, + }, + last_changed: new Date().toString(), + last_updated: new Date().toString(), + context: { + id: "1", + parent_id: null, + user_id: null, + }, + }; +} + +type EntityRowData = { + stateObj: HassEntity; + entity_id: string; + state: string; + device_class?: string; + domain: string; +}; + +function createRowData(stateObj: HassEntity): EntityRowData { + return { + stateObj, + entity_id: stateObj.entity_id, + state: stateObj.state, + device_class: stateObj.attributes.device_class, + domain: computeDomain(stateObj.entity_id), + }; +} + +@customElement("demo-misc-entity-state") +export class DemoEntityState extends LitElement { + @property({ attribute: false }) hass?: HomeAssistant; + + private _columns = memoizeOne( + (hass: HomeAssistant): DataTableColumnContainer => { + const columns: DataTableColumnContainer = { + icon: { + title: "Icon", + template: (_, entry) => { + const cssColor = stateColorCss(entry.stateObj); + return html` + + + `; + }, + }, + entity_id: { + title: "Entity id", + width: "30%", + filterable: true, + sortable: true, + }, + state: { + title: "State", + width: "20%", + sortable: true, + template: (_, entry) => + html`${computeStateDisplay( + hass.localize, + entry.stateObj, + hass.locale + )}`, + }, + device_class: { + title: "Device class", + template: (dc) => html`${dc ?? "-"}`, + width: "20%", + filterable: true, + sortable: true, + }, + domain: { + title: "Domain", + template: (_, entry) => html`${computeDomain(entry.entity_id)}`, + width: "20%", + filterable: true, + sortable: true, + }, + }; + + return columns; + } + ); + + private _rows = memoizeOne((): EntityRowData[] => + ENTITIES.map(createRowData) + ); + + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + const hass = provideHass(this); + hass.updateTranslations(null, "en"); + hass.updateTranslations("config", "en"); + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + return html` + + `; + } + + static get styles() { + return css` + .color { + display: block; + height: 20px; + width: 20px; + border-radius: 10px; + background-color: rgb(--color); + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "demo-misc-entity-state": DemoEntityState; + } +} diff --git a/src/common/entity/color/alarm_control_panel_color.ts b/src/common/entity/color/alarm_control_panel_color.ts index 2b862af450..ef72805c11 100644 --- a/src/common/entity/color/alarm_control_panel_color.ts +++ b/src/common/entity/color/alarm_control_panel_color.ts @@ -8,10 +8,11 @@ export const alarmControlPanelColor = (state?: string): string | undefined => { return "alarm-armed"; case "pending": return "alarm-pending"; + case "arming": + case "disarming": + return "alarm-arming"; case "triggered": return "alarm-triggered"; - case "disarmed": - return "alarm-disarmed"; default: return undefined; } diff --git a/src/common/entity/color/binary_sensor_color.ts b/src/common/entity/color/binary_sensor_color.ts index b5f9b05e9c..a542abf263 100644 --- a/src/common/entity/color/binary_sensor_color.ts +++ b/src/common/entity/color/binary_sensor_color.ts @@ -1,20 +1,20 @@ import { HassEntity } from "home-assistant-js-websocket"; -const NORMAL_DEVICE_CLASSES = new Set([ - "battery_charging", - "connectivity", - "light", - "moving", - "plug", - "power", - "presence", - "running", +const ALERTING_DEVICE_CLASSES = new Set([ + "battery", + "carbon_monoxide", + "gas", + "heat", + "problem", + "safety", + "smoke", + "tamper", ]); export const binarySensorColor = (stateObj: HassEntity): string | undefined => { const deviceClass = stateObj?.attributes.device_class; - return deviceClass && NORMAL_DEVICE_CLASSES.has(deviceClass) - ? "binary-sensor" - : "binary-sensor-danger"; + return deviceClass && ALERTING_DEVICE_CLASSES.has(deviceClass) + ? "binary-sensor-alerting" + : "binary-sensor"; }; diff --git a/src/common/entity/color/cover_color.ts b/src/common/entity/color/cover_color.ts deleted file mode 100644 index 1ff2bc6dc8..0000000000 --- a/src/common/entity/color/cover_color.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { HassEntity } from "home-assistant-js-websocket"; - -const SECURE_DEVICE_CLASSES = new Set(["door", "gate", "garage", "window"]); - -export const coverColor = (stateObj?: HassEntity): string | undefined => { - const isSecure = - stateObj?.attributes.device_class && - SECURE_DEVICE_CLASSES.has(stateObj.attributes.device_class); - return isSecure ? "cover-secure" : "cover"; -}; diff --git a/src/common/entity/color/lock_color.ts b/src/common/entity/color/lock_color.ts index dc677d3662..faecef1288 100644 --- a/src/common/entity/color/lock_color.ts +++ b/src/common/entity/color/lock_color.ts @@ -2,8 +2,6 @@ export const lockColor = (state?: string): string | undefined => { switch (state) { case "locked": return "lock-locked"; - case "unlocked": - return "lock-unlocked"; case "jammed": return "lock-jammed"; case "locking": diff --git a/src/common/entity/color/sensor_color.ts b/src/common/entity/color/sensor_color.ts index ad79ac43b5..3422a68704 100644 --- a/src/common/entity/color/sensor_color.ts +++ b/src/common/entity/color/sensor_color.ts @@ -8,25 +8,5 @@ export const sensorColor = (stateObj: HassEntity): string | undefined => { return batteryStateColor(stateObj); } - switch (deviceClass) { - case "apparent_power": - case "current": - case "energy": - case "gas": - case "power_factor": - case "power": - case "reactive_power": - case "voltage": - return "sensor-energy"; - case "temperature": - return "sensor-temperature"; - case "humidity": - return "sensor-humidity"; - case "illuminance": - return "sensor-illuminance"; - case "moisture": - return "sensor-moisture"; - } - - return "sensor"; + return undefined; }; diff --git a/src/common/entity/state_active.ts b/src/common/entity/state_active.ts index f5030ee2dd..2e4b788d5b 100644 --- a/src/common/entity/state_active.ts +++ b/src/common/entity/state_active.ts @@ -21,6 +21,10 @@ export function stateActive(stateObj: HassEntity, state?: string): boolean { case "device_tracker": case "person": return compareState !== "not_home"; + case "alarm_control_panel": + return compareState !== "disarmed"; + case "lock": + return compareState !== "unlocked"; case "media_player": return compareState !== "standby"; case "vacuum": diff --git a/src/common/entity/state_color.ts b/src/common/entity/state_color.ts index eb88e65673..56317027e1 100644 --- a/src/common/entity/state_color.ts +++ b/src/common/entity/state_color.ts @@ -4,7 +4,6 @@ import { UpdateEntity, updateIsInstalling } from "../../data/update"; import { alarmControlPanelColor } from "./color/alarm_control_panel_color"; import { binarySensorColor } from "./color/binary_sensor_color"; import { climateColor } from "./color/climate_color"; -import { coverColor } from "./color/cover_color"; import { lockColor } from "./color/lock_color"; import { sensorColor } from "./color/sensor_color"; import { computeDomain } from "./compute_domain"; @@ -21,7 +20,7 @@ export const stateColorCss = (stateObj?: HassEntity, state?: string) => { return `var(--rgb-state-${color}-color)`; } - return `var(--rgb-primary-color)`; + return `var(--rgb-state-default-color)`; }; export const stateColor = (stateObj: HassEntity, state?: string) => { @@ -36,11 +35,14 @@ export const stateColor = (stateObj: HassEntity, state?: string) => { return binarySensorColor(stateObj); case "cover": - return coverColor(stateObj); + return "cover"; case "climate": return climateColor(compareState); + case "fan": + return "fan"; + case "lock": return lockColor(compareState); @@ -53,19 +55,21 @@ export const stateColor = (stateObj: HassEntity, state?: string) => { case "media_player": return "media-player"; - case "person": - case "device_tracker": - return "person"; - case "sensor": return sensorColor(stateObj); case "vacuum": return "vacuum"; + case "siren": + return "siren"; + case "sun": return compareState === "above_horizon" ? "sun-day" : "sun-night"; + case "switch": + return "switch"; + case "update": return updateIsInstalling(stateObj as UpdateEntity) ? "update-installing" diff --git a/src/resources/ha-style.ts b/src/resources/ha-style.ts index 49e3e8a8f6..f0b32c00ed 100644 --- a/src/resources/ha-style.ts +++ b/src/resources/ha-style.ts @@ -136,22 +136,21 @@ documentContainer.innerHTML = ` --rgb-white-color: 255, 255, 255; /* rgb state color */ - --rgb-state-alarm-armed-color: var(--rgb-green-color); - --rgb-state-alarm-disarmed-color: var(--rgb-primary-color); + --rgb-state-default-color: 68, 115, 158; + --rgb-state-alarm-armed-color: var(--rgb-red-color); --rgb-state-alarm-pending-color: var(--rgb-orange-color); + --rgb-state-alarm-arming-color: var(--rgb-orange-color); --rgb-state-alarm-triggered-color: var(--rgb-red-color); - --rgb-state-binary-sensor-color: var(--rgb-green-color); - --rgb-state-binary-sensor-danger-color: var(--rgb-red-color); - --rgb-state-cover-color: var(--rgb-blue-color); - --rgb-state-cover-secure-color: var(--rgb-red-color); - --rgb-state-humidifier-color: var(--rgb-deep-purple-color); - --rgb-state-light-color: var(--rgb-orange-color); + --rgb-state-binary-sensor-color: var(--rgb-primary-color); + --rgb-state-binary-sensor-alerting-color: var(--rgb-red-color); + --rgb-state-cover-color: var(--rgb-purple-color); + --rgb-state-fan-color: var(--rgb-cyan-color); + --rgb-state-humidifier-color: var(--rgb-blue-color); + --rgb-state-light-color: var(--rgb-amber-color); --rgb-state-lock-jammed-color: var(--rgb-red-color); - --rgb-state-lock-locked-color: var(--rgb-green-color); + --rgb-state-lock-locked-color: var(--rgb-red-color); --rgb-state-lock-pending-color: var(--rgb-orange-color); - --rgb-state-lock-unlocked-color: var(--rgb-red-color); --rgb-state-media-player-color: var(--rgb-indigo-color); - --rgb-state-person-color: var(--rgb-blue-grey-color); --rgb-state-person-home-color: var(--rgb-green-color); --rgb-state-person-not-home-color: var(--rgb-red-color); --rgb-state-person-zone-color: var(--rgb-blue-color); @@ -159,23 +158,19 @@ documentContainer.innerHTML = ` --rgb-state-sensor-battery-low-color: var(--rgb-red-color); --rgb-state-sensor-battery-medium-color: var(--rgb-orange-color); --rgb-state-sensor-battery-unknown-color: var(--rgb-disabled-color); - --rgb-state-sensor-color: var(--rgb-blue-grey-color); - --rgb-state-sensor-energy-color: var(--rgb-amber-color); - --rgb-state-sensor-humidity-color: var(--rgb-deep-purple-color); - --rgb-state-sensor-illuminance-color: var(--rgb-amber-color); - --rgb-state-sensor-moisture-color: var(--rgb-light-blue-color); - --rgb-state-sensor-temperature-color: var(--rgb-deep-orange-color); + --rgb-state-siren-color: var(--rgb-red-color); --rgb-state-sun-day-color: var(--rgb-amber-color); --rgb-state-sun-night-color: var(--rgb-deep-purple-color); + --rgb-state-switch-color: var(--rgb-amber-color); --rgb-state-update-color: var(--rgb-green-color); --rgb-state-update-installing-color: var(--rgb-orange-color); --rgb-state-vacuum-color: var(--rgb-teal-color); --rgb-state-climate-auto-color: var(--rgb-green-color); --rgb-state-climate-cool-color: var(--rgb-blue-color); --rgb-state-climate-dry-color: var(--rgb-orange-color); - --rgb-state-climate-fan-only-color: var(--rgb-teal-color); + --rgb-state-climate-fan-only-color: var(--rgb-cyan-color); --rgb-state-climate-heat-color: var(--rgb-deep-orange-color); - --rgb-state-climate-heat-cool-color: var(--rgb-green-color); + --rgb-state-climate-heat-cool-color: var(--rgb-state-default-color); --rgb-state-climate-idle-color: var(--rgb-disabled-color); /* input components */