From f5b44656cf94bba165c58771fc9d8dc9306e55a1 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 23 Aug 2022 09:12:55 -0500 Subject: [PATCH] Update a few Condition Descriptions (#13404) * Update a few Describe-conditions * Update to multiple states --- .../pages/automation/describe-condition.ts | 50 +++++- src/data/automation_i18n.ts | 169 +++++++++++++++++- src/data/script_i18n.ts | 12 +- .../condition/ha-automation-condition-row.ts | 16 +- 4 files changed, 225 insertions(+), 22 deletions(-) diff --git a/gallery/src/pages/automation/describe-condition.ts b/gallery/src/pages/automation/describe-condition.ts index e77a7a2af6..2f6cef5f47 100644 --- a/gallery/src/pages/automation/describe-condition.ts +++ b/gallery/src/pages/automation/describe-condition.ts @@ -1,20 +1,41 @@ import { dump } from "js-yaml"; -import { html, css, LitElement, TemplateResult } from "lit"; -import { customElement, state } from "lit/decorators"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; import "../../../../src/components/ha-card"; import "../../../../src/components/ha-yaml-editor"; import { Condition } from "../../../../src/data/automation"; import { describeCondition } from "../../../../src/data/automation_i18n"; +import { getEntity } from "../../../../src/fake_data/entity"; +import { provideHass } from "../../../../src/fake_data/provide_hass"; +import { HomeAssistant } from "../../../../src/types"; + +const ENTITIES = [ + getEntity("light", "kitchen", "on", { + friendly_name: "Kitchen Light", + }), + getEntity("device_tracker", "person", "home", { + friendly_name: "Person", + }), + getEntity("zone", "home", "", { + friendly_name: "Home", + }), +]; const conditions = [ { condition: "and" }, { condition: "not" }, { condition: "or" }, - { condition: "state" }, - { condition: "numeric_state" }, + { condition: "state", entity_id: "light.kitchen", state: "on" }, + { + condition: "numeric_state", + entity_id: "light.kitchen", + attribute: "brightness", + below: 80, + above: 20, + }, { condition: "sun", after: "sunset" }, - { condition: "sun", after: "sunrise" }, - { condition: "zone" }, + { condition: "sun", after: "sunrise", offset: "-01:00" }, + { condition: "zone", entity_id: "device_tracker.person", zone: "zone.home" }, { condition: "time" }, { condition: "template" }, ]; @@ -27,15 +48,21 @@ const initialCondition: Condition = { @customElement("demo-automation-describe-condition") export class DemoAutomationDescribeCondition extends LitElement { + @property({ attribute: false }) hass!: HomeAssistant; + @state() _condition = initialCondition; protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + return html`
${this._condition - ? describeCondition(this._condition) + ? describeCondition(this._condition, this.hass) : ""} html`
- ${describeCondition(conf as any)} + ${describeCondition(conf as any, this.hass)}
${dump(conf)}
` @@ -57,6 +84,13 @@ export class DemoAutomationDescribeCondition extends LitElement { `; } + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + const hass = provideHass(this); + hass.updateTranslations(null, "en"); + hass.addEntities(ENTITIES); + } + private _dataChanged(ev: CustomEvent): void { ev.stopPropagation(); this._condition = ev.detail.isValid ? ev.detail.value : undefined; diff --git a/src/data/automation_i18n.ts b/src/data/automation_i18n.ts index a22e803c49..5b72330818 100644 --- a/src/data/automation_i18n.ts +++ b/src/data/automation_i18n.ts @@ -1,3 +1,6 @@ +import secondsToDuration from "../common/datetime/seconds_to_duration"; +import { computeStateName } from "../common/entity/compute_state_name"; +import type { HomeAssistant } from "../types"; import { Condition, Trigger } from "./automation"; export const describeTrigger = (trigger: Trigger) => { @@ -7,12 +10,176 @@ export const describeTrigger = (trigger: Trigger) => { return `${trigger.platform || "Unknown"} trigger`; }; -export const describeCondition = (condition: Condition) => { +export const describeCondition = ( + condition: Condition, + hass: HomeAssistant +) => { if (condition.alias) { return condition.alias; } + if (["or", "and", "not"].includes(condition.condition)) { return `multiple conditions using "${condition.condition}"`; } + + // State Condition + if (condition.condition === "state" && condition.entity_id) { + let base = "Confirm"; + const stateObj = hass.states[condition.entity_id]; + const entity = stateObj ? computeStateName(stateObj) : condition.entity_id; + + if ("attribute" in condition) { + base += ` ${condition.attribute} from`; + } + + let states = ""; + + if (Array.isArray(condition.state)) { + for (const [index, state] of condition.state.entries()) { + states += `${index > 0 ? "," : ""} ${ + condition.state.length > 1 && index === condition.state.length - 1 + ? "or" + : "" + } ${state}`; + } + } else { + states = condition.state.toString(); + } + + base += ` ${entity} is ${states}`; + + if ("for" in condition) { + let duration: string; + if (typeof condition.for === "number") { + duration = `for ${secondsToDuration(condition.for)!}`; + } else if (typeof condition.for === "string") { + duration = `for ${condition.for}`; + } else { + duration = `for ${JSON.stringify(condition.for)}`; + } + base += ` for ${duration}`; + } + + return base; + } + + // Numeric State Condition + if (condition.condition === "numeric_state" && condition.entity_id) { + let base = "Confirm"; + const stateObj = hass.states[condition.entity_id]; + const entity = stateObj ? computeStateName(stateObj) : condition.entity_id; + + if ("attribute" in condition) { + base += ` ${condition.attribute} from`; + } + + base += ` ${entity} is`; + + if ("above" in condition) { + base += ` above ${condition.above}`; + } + + if ("below" in condition && "above" in condition) { + base += " and"; + } + + if ("below" in condition) { + base += ` below ${condition.below}`; + } + + return base; + } + + // Sun condition + if ( + condition.condition === "sun" && + ("before" in condition || "after" in condition) + ) { + let base = "Confirm"; + + if (!condition.after && !condition.before) { + base += " sun"; + return base; + } + + base += " sun"; + + if (condition.after) { + let duration = ""; + + if (condition.after_offset) { + if (typeof condition.after_offset === "number") { + duration = ` offset by ${secondsToDuration(condition.after_offset)!}`; + } else if (typeof condition.after_offset === "string") { + duration = ` offset by ${condition.after_offset}`; + } else { + duration = ` offset by ${JSON.stringify(condition.after_offset)}`; + } + } + + base += ` after ${condition.after}${duration}`; + } + + if (condition.before) { + base += ` before ${condition.before}`; + } + + return base; + } + + // Zone condition + if (condition.condition === "zone" && condition.entity_id && condition.zone) { + let entities = ""; + let entitiesPlural = false; + let zones = ""; + let zonesPlural = false; + + const states = hass.states; + + if (Array.isArray(condition.entity_id)) { + if (condition.entity_id.length > 1) { + entitiesPlural = true; + } + for (const [index, entity] of condition.entity_id.entries()) { + if (states[entity]) { + entities += `${index > 0 ? "," : ""} ${ + condition.entity_id.length > 1 && + index === condition.entity_id.length - 1 + ? "or" + : "" + } ${computeStateName(states[entity]) || entity}`; + } + } + } else { + entities = states[condition.entity_id] + ? computeStateName(states[condition.entity_id]) + : condition.entity_id; + } + + if (Array.isArray(condition.zone)) { + if (condition.zone.length > 1) { + zonesPlural = true; + } + + for (const [index, zone] of condition.zone.entries()) { + if (states[zone]) { + zones += `${index > 0 ? "," : ""} ${ + condition.zone.length > 1 && index === condition.zone.length - 1 + ? "or" + : "" + } ${computeStateName(states[zone]) || zone}`; + } + } + } else { + zones = states[condition.zone] + ? computeStateName(states[condition.zone]) + : condition.zone; + } + + return `Confirm ${entities} ${entitiesPlural ? "are" : "is"} in ${zones} ${ + zonesPlural ? "zones" : "zone" + }`; + } + return `${condition.condition} condition`; }; diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts index 6e3fcdc36b..899a5242f9 100644 --- a/src/data/script_i18n.ts +++ b/src/data/script_i18n.ts @@ -163,7 +163,7 @@ export const describeAction = ( } if (actionType === "check_condition") { - return `Test ${describeCondition(action as Condition)}`; + return `Test ${describeCondition(action as Condition, hass)}`; } if (actionType === "stop") { @@ -177,7 +177,7 @@ export const describeAction = ( typeof config.if === "string" ? config.if : ensureArray(config.if) - .map((condition) => describeCondition(condition)) + .map((condition) => describeCondition(condition, hass)) .join(", ") } then ${ensureArray(config.then).map((thenAction) => describeAction(hass, thenAction) @@ -200,7 +200,7 @@ export const describeAction = ( typeof chooseAction.conditions === "string" ? chooseAction.conditions : ensureArray(chooseAction.conditions) - .map((condition) => describeCondition(condition)) + .map((condition) => describeCondition(condition, hass)) .join(", ") } then ${ensureArray(chooseAction.sequence) .map((chooseSeq) => describeAction(hass, chooseSeq)) @@ -223,11 +223,11 @@ export const describeAction = ( )} ${"count" in config.repeat ? `${config.repeat.count} times` : ""}${ "while" in config.repeat ? `while ${ensureArray(config.repeat.while) - .map((condition) => describeCondition(condition)) + .map((condition) => describeCondition(condition, hass)) .join(", ")} is true` : "until" in config.repeat ? `until ${ensureArray(config.repeat.until) - .map((condition) => describeCondition(condition)) + .map((condition) => describeCondition(condition, hass)) .join(", ")} is true` : "for_each" in config.repeat ? `for every item: ${ensureArray(config.repeat.for_each) @@ -238,7 +238,7 @@ export const describeAction = ( } if (actionType === "check_condition") { - return `Test ${describeCondition(action as Condition)}`; + return `Test ${describeCondition(action as Condition, hass)}`; } if (actionType === "device_action") { diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index df38ecc5a5..cd9b7e6d4b 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -5,14 +5,17 @@ import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; import { handleStructError } from "../../../../common/structs/handle-errors"; -import "../../../../components/ha-button-menu"; -import "../../../../components/ha-card"; import "../../../../components/buttons/ha-progress-button"; import type { HaProgressButton } from "../../../../components/buttons/ha-progress-button"; -import "../../../../components/ha-icon-button"; +import "../../../../components/ha-button-menu"; +import "../../../../components/ha-card"; import "../../../../components/ha-expansion-panel"; +import "../../../../components/ha-icon-button"; import { Condition, testCondition } from "../../../../data/automation"; +import { describeCondition } from "../../../../data/automation_i18n"; +import { validateConfig } from "../../../../data/config"; import { showAlertDialog, showConfirmationDialog, @@ -20,9 +23,6 @@ import { import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import "./ha-automation-condition-editor"; -import { validateConfig } from "../../../../data/config"; -import { describeCondition } from "../../../../data/automation_i18n"; -import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; export interface ConditionElement extends LitElement { condition: Condition; @@ -81,7 +81,9 @@ export default class HaAutomationConditionRow extends LitElement { ${this.hass.localize(