diff --git a/gallery/src/pages/automation/describe-trigger.ts b/gallery/src/pages/automation/describe-trigger.ts index 41b589489b..081f6162a7 100644 --- a/gallery/src/pages/automation/describe-trigger.ts +++ b/gallery/src/pages/automation/describe-trigger.ts @@ -1,25 +1,56 @@ 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 { Trigger } from "../../../../src/data/automation"; import { describeTrigger } 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("person", "person", "", { + friendly_name: "Person", + }), + getEntity("zone", "home", "", { + friendly_name: "Home", + }), +]; const triggers = [ - { platform: "state" }, + { platform: "state", entity_id: "light.kitchen", from: "off", to: "on" }, { platform: "mqtt" }, - { platform: "geo_location" }, - { platform: "homeassistant" }, - { platform: "numeric_state" }, - { platform: "sun" }, + { + platform: "geo_location", + source: "test_source", + zone: "zone.home", + event: "enter", + }, + { platform: "homeassistant", event: "start" }, + { + platform: "numeric_state", + entity_id: "light.kitchen", + attribute: "brightness", + below: 80, + above: 20, + }, + { platform: "sun", event: "sunset" }, { platform: "time_pattern" }, { platform: "webhook" }, - { platform: "zone" }, + { + platform: "zone", + entity_id: "person.person", + zone: "zone.home", + event: "enter", + }, { platform: "tag" }, - { platform: "time" }, + { platform: "time", at: "15:32" }, { platform: "template" }, - { platform: "event" }, + { platform: "event", event_type: "homeassistant_started" }, ]; const initialTrigger: Trigger = { @@ -29,14 +60,22 @@ const initialTrigger: Trigger = { @customElement("demo-automation-describe-trigger") export class DemoAutomationDescribeTrigger extends LitElement { + @property({ attribute: false }) hass!: HomeAssistant; + @state() _trigger = initialTrigger; protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + return html`
- ${this._trigger ? describeTrigger(this._trigger) : ""} + ${this._trigger + ? describeTrigger(this._trigger, this.hass) + : ""} html`
- ${describeTrigger(conf as any)} + ${describeTrigger(conf as any, this.hass)}
${dump(conf)}
` @@ -56,6 +95,13 @@ export class DemoAutomationDescribeTrigger 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._trigger = ev.detail.isValid ? ev.detail.value : undefined; diff --git a/src/data/automation_i18n.ts b/src/data/automation_i18n.ts index 909061103f..33bf8fa9d1 100644 --- a/src/data/automation_i18n.ts +++ b/src/data/automation_i18n.ts @@ -2,11 +2,296 @@ 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"; +import { formatAttributeName } from "./entity_attributes"; -export const describeTrigger = (trigger: Trigger, ignoreAlias = false) => { +export const describeTrigger = ( + trigger: Trigger, + hass: HomeAssistant, + ignoreAlias = false +) => { if (trigger.alias && !ignoreAlias) { return trigger.alias; } + + // Event Trigger + if (trigger.platform === "event" && trigger.event_type) { + let eventTypes = ""; + + if (Array.isArray(trigger.event_type)) { + for (const [index, state] of trigger.event_type.entries()) { + eventTypes += `${index > 0 ? "," : ""} ${ + trigger.event_type.length > 1 && + index === trigger.event_type.length - 1 + ? "or" + : "" + } ${state}`; + } + } else { + eventTypes = trigger.event_type.toString(); + } + + return `When ${eventTypes} event is fired`; + } + + // Home Assistant Trigger + if (trigger.platform === "homeassistant" && trigger.event) { + return `When Home Assistant is ${ + trigger.event === "start" ? "started" : "shutdown" + }`; + } + + // Numeric State Trigger + if (trigger.platform === "numeric_state" && trigger.entity_id) { + let base = "When"; + const stateObj = hass.states[trigger.entity_id]; + const entity = stateObj ? computeStateName(stateObj) : trigger.entity_id; + + if (trigger.attribute) { + base += ` ${formatAttributeName(trigger.attribute)} from`; + } + + base += ` ${entity} is`; + + if ("above" in trigger) { + base += ` above ${trigger.above}`; + } + + if ("below" in trigger && "above" in trigger) { + base += " and"; + } + + if ("below" in trigger) { + base += ` below ${trigger.below}`; + } + + return base; + } + + // State Trigger + if (trigger.platform === "state" && trigger.entity_id) { + let base = "When"; + let entities = ""; + + const states = hass.states; + + if (trigger.attribute) { + base += ` ${formatAttributeName(trigger.attribute)} from`; + } + + if (Array.isArray(trigger.entity_id)) { + for (const [index, entity] of trigger.entity_id.entries()) { + if (states[entity]) { + entities += `${index > 0 ? "," : ""} ${ + trigger.entity_id.length > 1 && + index === trigger.entity_id.length - 1 + ? "or" + : "" + } ${computeStateName(states[entity]) || entity}`; + } + } + } else { + entities = states[trigger.entity_id] + ? computeStateName(states[trigger.entity_id]) + : trigger.entity_id; + } + + base += ` ${entities} changes`; + + if (trigger.from) { + let from = ""; + if (Array.isArray(trigger.from)) { + for (const [index, state] of trigger.from.entries()) { + from += `${index > 0 ? "," : ""} ${ + trigger.from.length > 1 && index === trigger.from.length - 1 + ? "or" + : "" + } ${state}`; + } + } else { + from = trigger.from.toString(); + } + base += ` from ${from}`; + } + + if (trigger.to) { + let to = ""; + if (Array.isArray(trigger.to)) { + for (const [index, state] of trigger.to.entries()) { + to += `${index > 0 ? "," : ""} ${ + trigger.to.length > 1 && index === trigger.to.length - 1 ? "or" : "" + } ${state}`; + } + } else if (trigger.to) { + to = trigger.to.toString(); + } + + base += ` to ${to}`; + } + + if ("for" in trigger) { + let duration: string; + if (typeof trigger.for === "number") { + duration = `for ${secondsToDuration(trigger.for)!}`; + } else if (typeof trigger.for === "string") { + duration = `for ${trigger.for}`; + } else { + duration = `for ${JSON.stringify(trigger.for)}`; + } + + base += ` for ${duration}`; + } + + return base; + } + + // Sun Trigger + if (trigger.platform === "sun" && trigger.event) { + let base = `When the sun ${trigger.event === "sunset" ? "sets" : "rises"}`; + + if (trigger.offset) { + let duration = ""; + + if (trigger.offset) { + if (typeof trigger.offset === "number") { + duration = ` offset by ${secondsToDuration(trigger.offset)!}`; + } else if (typeof trigger.offset === "string") { + duration = ` offset by ${trigger.offset}`; + } else { + duration = ` offset by ${JSON.stringify(trigger.offset)}`; + } + } + base += duration; + } + + return base; + } + + // Tag Trigger + if (trigger.platform === "tag") { + return "When a tag is scanned"; + } + + // Time Trigger + if (trigger.platform === "time" && trigger.at) { + const at = trigger.at.includes(".") + ? hass.states[trigger.at] || trigger.at + : trigger.at; + + return `When the time is equal to ${at}`; + } + + // Time Patter Trigger + if (trigger.platform === "time_pattern") { + return "Time pattern trigger"; + } + + // Zone Trigger + if (trigger.platform === "zone" && trigger.entity_id && trigger.zone) { + let entities = ""; + let zones = ""; + let zonesPlural = false; + + const states = hass.states; + + if (Array.isArray(trigger.entity_id)) { + for (const [index, entity] of trigger.entity_id.entries()) { + if (states[entity]) { + entities += `${index > 0 ? "," : ""} ${ + trigger.entity_id.length > 1 && + index === trigger.entity_id.length - 1 + ? "or" + : "" + } ${computeStateName(states[entity]) || entity}`; + } + } + } else { + entities = states[trigger.entity_id] + ? computeStateName(states[trigger.entity_id]) + : trigger.entity_id; + } + + if (Array.isArray(trigger.zone)) { + if (trigger.zone.length > 1) { + zonesPlural = true; + } + + for (const [index, zone] of trigger.zone.entries()) { + if (states[zone]) { + zones += `${index > 0 ? "," : ""} ${ + trigger.zone.length > 1 && index === trigger.zone.length - 1 + ? "or" + : "" + } ${computeStateName(states[zone]) || zone}`; + } + } + } else { + zones = states[trigger.zone] + ? computeStateName(states[trigger.zone]) + : trigger.zone; + } + + return `When ${entities} ${trigger.event}s ${zones} ${ + zonesPlural ? "zones" : "zone" + }`; + } + + // Geo Location Trigger + if (trigger.platform === "geo_location" && trigger.source && trigger.zone) { + let sources = ""; + let zones = ""; + let zonesPlural = false; + const states = hass.states; + + if (Array.isArray(trigger.source)) { + for (const [index, source] of trigger.source.entries()) { + sources += `${index > 0 ? "," : ""} ${ + trigger.source.length > 1 && index === trigger.source.length - 1 + ? "or" + : "" + } ${source}`; + } + } else { + sources = trigger.source; + } + + if (Array.isArray(trigger.zone)) { + if (trigger.zone.length > 1) { + zonesPlural = true; + } + + for (const [index, zone] of trigger.zone.entries()) { + if (states[zone]) { + zones += `${index > 0 ? "," : ""} ${ + trigger.zone.length > 1 && index === trigger.zone.length - 1 + ? "or" + : "" + } ${computeStateName(states[zone]) || zone}`; + } + } + } else { + zones = states[trigger.zone] + ? computeStateName(states[trigger.zone]) + : trigger.zone; + } + + return `When ${sources} ${trigger.event}s ${zones} ${ + zonesPlural ? "zones" : "zone" + }`; + } + // MQTT Trigger + if (trigger.platform === "mqtt") { + return "When a MQTT payload has been received"; + } + + // Template Trigger + if (trigger.platform === "template") { + return "When a template triggers"; + } + + // Webhook Trigger + if (trigger.platform === "webhook") { + return "When a Webhook payload has been received"; + } return `${trigger.platform || "Unknown"} trigger`; }; diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts index bef0eaf2dc..79993d9913 100644 --- a/src/data/script_i18n.ts +++ b/src/data/script_i18n.ts @@ -142,7 +142,7 @@ export const describeAction = ( if (actionType === "wait_for_trigger") { const config = action as WaitForTriggerAction; return `Wait for ${ensureArray(config.wait_for_trigger) - .map((trigger) => describeTrigger(trigger)) + .map((trigger) => describeTrigger(trigger, hass)) .join(", ")}`; } diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 5dc2d0c1e1..2113207c2f 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -16,6 +16,7 @@ import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; import { handleStructError } from "../../../../common/structs/handle-errors"; import { debounce } from "../../../../common/util/debounce"; import "../../../../components/ha-alert"; @@ -23,9 +24,10 @@ import "../../../../components/ha-button-menu"; import "../../../../components/ha-card"; import "../../../../components/ha-expansion-panel"; import "../../../../components/ha-icon-button"; -import { HaYamlEditor } from "../../../../components/ha-yaml-editor"; import "../../../../components/ha-textfield"; +import { HaYamlEditor } from "../../../../components/ha-yaml-editor"; import { subscribeTrigger, Trigger } from "../../../../data/automation"; +import { describeTrigger } from "../../../../data/automation_i18n"; import { validateConfig } from "../../../../data/config"; import { showAlertDialog, @@ -49,8 +51,6 @@ import "./types/ha-automation-trigger-time"; import "./types/ha-automation-trigger-time_pattern"; import "./types/ha-automation-trigger-webhook"; import "./types/ha-automation-trigger-zone"; -import { describeTrigger } from "../../../../data/automation_i18n"; -import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; export interface TriggerElement extends LitElement { trigger: Trigger; @@ -121,7 +121,9 @@ export default class HaAutomationTriggerRow extends LitElement {