From 98111971baac4502c0c826bb2288b57bad7e5935 Mon Sep 17 00:00:00 2001 From: Paulo Ferreira de Castro Date: Sun, 24 Mar 2024 17:04:01 +0000 Subject: [PATCH] Add the time domain (time entities) to the list of standard configuration actions --- src/components/timeslot-editor.ts | 6 +++- src/components/variable-picker.ts | 31 ++++++++++++++++++- src/data/actions/assign_action.ts | 19 +++++++++++- src/data/actions/compare_actions.ts | 1 + src/data/actions/compute_action_display.ts | 5 ++- src/data/actions/migrate_action_config.ts | 1 + src/data/variables/compute_merged_variable.ts | 7 +++-- src/data/variables/compute_variables.ts | 5 ++- src/data/variables/time_variable.ts | 19 ++++++++++++ src/editor/scheduler-editor-options.ts | 1 + src/editor/scheduler-editor-time.ts | 28 +++++++++++------ src/localize/languages/en.json | 4 +++ src/standard-configuration/action_icons.ts | 3 ++ src/standard-configuration/action_name.ts | 3 ++ src/standard-configuration/actions.ts | 9 ++++++ src/standard-configuration/standardActions.ts | 3 ++ src/standard-configuration/standardIcon.ts | 1 + src/standard-configuration/standardStates.ts | 3 ++ src/standard-configuration/variables.ts | 14 ++++++--- src/types.ts | 7 ++++- 20 files changed, 149 insertions(+), 21 deletions(-) create mode 100644 src/data/variables/time_variable.ts mode change 100644 => 100755 src/types.ts diff --git a/src/components/timeslot-editor.ts b/src/components/timeslot-editor.ts index 8e7a8742..f3aed765 100755 --- a/src/components/timeslot-editor.ts +++ b/src/components/timeslot-editor.ts @@ -3,10 +3,11 @@ import { customElement, property, eventOptions } from 'lit/decorators'; import { mdiUnfoldMoreVertical } from '@mdi/js'; import { HomeAssistant } from 'custom-card-helpers'; -import { Timeslot, Action, EVariableType, LevelVariable, ListVariable } from '../types'; +import { Timeslot, Action, EVariableType, LevelVariable, ListVariable, TimeVariable } from '../types'; import { stringToTime, timeToString, roundTime, parseRelativeTime } from '../data/date-time/time'; import { compareActions } from '../data/actions/compare_actions'; import { levelVariableDisplay } from '../data/variables/level_variable'; +import { timeVariableDisplay } from '../data/variables/time_variable'; import { unique, PrettyPrintName, getLocale } from '../helpers'; import { localize } from '../localize/localize'; import { stringToDate } from '../data/date-time/string_to_date'; @@ -246,6 +247,9 @@ export class TimeslotEditor extends LitElement { variable = variable as ListVariable; const listItem = variable.options.find(e => e.value == value); return PrettyPrintName(listItem && listItem.name ? listItem.name : String(value)); + } else if (variable.type == EVariableType.Time) { + variable = variable as TimeVariable; + return timeVariableDisplay(value, variable); } else return ''; }) .join(', '); diff --git a/src/components/variable-picker.ts b/src/components/variable-picker.ts index a7534dc7..acbb68a9 100755 --- a/src/components/variable-picker.ts +++ b/src/components/variable-picker.ts @@ -1,6 +1,7 @@ import { LitElement, html, css } from 'lit'; import { property, customElement } from 'lit/decorators.js'; -import { Variable, LevelVariable, EVariableType, ListVariable, TextVariable } from '../types'; +import { HomeAssistant } from 'custom-card-helpers'; +import { Variable, LevelVariable, EVariableType, ListVariable, TextVariable, TimeVariable } from '../types'; import './variable-slider'; import './button-group'; @@ -8,6 +9,9 @@ import { fireEvent } from 'custom-card-helpers'; @customElement('scheduler-variable-picker') export class SchedulerVariablePicker extends LitElement { + @property() + hass?: HomeAssistant; + @property() variable?: Variable | null; @@ -28,6 +32,7 @@ export class SchedulerVariablePicker extends LitElement { else if (this.variable.type == EVariableType.Level) return this.renderLevelVariable(); else if (this.variable.type == EVariableType.List) return this.renderListVariable(); else if (this.variable.type == EVariableType.Text) return this.renderTextVariable(); + else if (this.variable.type == EVariableType.Time) return this.renderTimeVariable(); else return html``; } @@ -83,9 +88,33 @@ export class SchedulerVariablePicker extends LitElement { `; } + renderTimeVariable() { + if (!this.hass || !this.variable) { + console.warn(`${this.renderTimeVariable.name}() not rendering: undefined references`); + return html``; + } + const variable = this.variable as TimeVariable; + const value = this.value; + + return html` + +
${variable.enable_seconds ? 'Hours:Minutes:Seconds' : 'Hours:Minutes'}
+ `; + } + static styles = css` ha-textfield { width: 100%; } + div.key { + color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6)); + font-style: italic; + font-size: 0.75rem; + } `; } diff --git a/src/data/actions/assign_action.ts b/src/data/actions/assign_action.ts index e1bddeae..075b2846 100755 --- a/src/data/actions/assign_action.ts +++ b/src/data/actions/assign_action.ts @@ -1,4 +1,12 @@ -import { Action, EVariableType, LevelVariable, ListVariable, ServiceCall, TextVariable } from '../../types'; +import { + Action, + EVariableType, + LevelVariable, + ListVariable, + ServiceCall, + TextVariable, + TimeVariable, +} from '../../types'; import { omit } from '../../helpers'; export const assignAction = (entity_id: string, action: Action) => { @@ -32,6 +40,15 @@ export const assignAction = (entity_id: string, action: Action) => { [key]: config.options.length ? config.options[0].value : undefined, }, }; + } else if (config.type == EVariableType.Time) { + config = config as TimeVariable; + output = { + ...output, + service_data: { + ...output.service_data, + [key]: config.enable_seconds ? '00:00:00' : '00:00', + }, + }; } else if (config.type == EVariableType.Text) { config = config as TextVariable; output = { diff --git a/src/data/actions/compare_actions.ts b/src/data/actions/compare_actions.ts index 72fd815d..38f1513a 100755 --- a/src/data/actions/compare_actions.ts +++ b/src/data/actions/compare_actions.ts @@ -48,6 +48,7 @@ export function compareActions(actionA: Action, actionB: Action, allowVars = fal if (variable.type === EVariableType.List) { return (variable as ListVariable).options.some(e => e.value === value); } else if (variable.type === EVariableType.Level) return !isNaN(value); + else if (variable.type == EVariableType.Time) return true; else if (variable.type == EVariableType.Text) return true; return false; diff --git a/src/data/actions/compute_action_display.ts b/src/data/actions/compute_action_display.ts index 3babd70f..7728aacf 100755 --- a/src/data/actions/compute_action_display.ts +++ b/src/data/actions/compute_action_display.ts @@ -1,9 +1,10 @@ import { computeEntity } from 'custom-card-helpers'; -import { Action, EVariableType, LevelVariable, ListVariable, TextVariable } from '../../types'; +import { Action, EVariableType, LevelVariable, ListVariable, TextVariable, TimeVariable } from '../../types'; import { levelVariableDisplay } from '../variables/level_variable'; import { PrettyPrintName } from '../../helpers'; import { listVariableDisplay } from '../variables/list_variable'; import { textVariableDisplay } from '../variables/text_variable'; +import { timeVariableDisplay } from '../variables/time_variable'; const wildcardPattern = /\{([^\}]+)\}/; const parameterPattern = /\[([^\]]+)\]/; @@ -26,6 +27,8 @@ export function computeActionDisplay(action: Action) { replacement = levelVariableDisplay(action.service_data![field], action.variables![field] as LevelVariable); else if (action.variables![field].type == EVariableType.List) replacement = listVariableDisplay(action.service_data![field], action.variables![field] as ListVariable); + else if (action.variables![field].type == EVariableType.Time) + replacement = timeVariableDisplay(action.service_data![field], action.variables![field] as TimeVariable); else replacement = textVariableDisplay(action.service_data![field], action.variables![field] as TextVariable); } else { replacement = action.service_data![field]; diff --git a/src/data/actions/migrate_action_config.ts b/src/data/actions/migrate_action_config.ts index 310ce330..0ad67348 100755 --- a/src/data/actions/migrate_action_config.ts +++ b/src/data/actions/migrate_action_config.ts @@ -46,6 +46,7 @@ export const migrateActionConfig = ( ); else return null; + case EVariableType.Time: case EVariableType.Text: //keep the selected text variable return output.map(e => diff --git a/src/data/variables/compute_merged_variable.ts b/src/data/variables/compute_merged_variable.ts index 950653d5..82d65533 100755 --- a/src/data/variables/compute_merged_variable.ts +++ b/src/data/variables/compute_merged_variable.ts @@ -1,13 +1,14 @@ -import { LevelVariable, ListVariable, TextVariable, EVariableType } from '../../types'; +import { LevelVariable, ListVariable, TextVariable, TimeVariable, EVariableType } from '../../types'; import { levelVariable } from './level_variable'; import { listVariable } from './list_variable'; import { textVariable } from './text_variable'; +import { timeVariable } from './time_variable'; import { isDefined, unique } from '../../helpers'; import { computeVariables } from './compute_variables'; export function computeMergedVariable( ...variables: Partial[] -): LevelVariable | ListVariable | TextVariable | undefined { +): LevelVariable | ListVariable | TextVariable | TimeVariable | undefined { const types = unique(variables.map(e => e.type).filter(isDefined)); if (!types.length) { variables = Object.values(computeVariables(Object.assign({}, ...variables))!); @@ -18,6 +19,8 @@ export function computeMergedVariable( if (types[0] == EVariableType.Level) { return levelVariable(...(variables as LevelVariable[])); + } else if (types[0] == EVariableType.Time) { + return timeVariable(...(variables as TimeVariable[])); } else if (types[0] == EVariableType.List) { return listVariable(...(variables as ListVariable[])); } else return textVariable(...(variables as TextVariable[])); diff --git a/src/data/variables/compute_variables.ts b/src/data/variables/compute_variables.ts index eec6d62f..d7267816 100755 --- a/src/data/variables/compute_variables.ts +++ b/src/data/variables/compute_variables.ts @@ -1,7 +1,8 @@ -import { LevelVariable, ListVariable, TextVariable, Dictionary, VariableDictionary } from '../../types'; +import { LevelVariable, ListVariable, TextVariable, TimeVariable, Dictionary, VariableDictionary } from '../../types'; import { listVariable } from './list_variable'; import { levelVariable } from './level_variable'; import { textVariable } from './text_variable'; +import { timeVariable } from './time_variable'; export function computeVariables( variables?: Dictionary> @@ -14,6 +15,8 @@ export function computeVariables( return [field, listVariable(variable as ListVariable)]; } else if ('min' in variable || 'max' in variable) { return [field, levelVariable(variable as LevelVariable)]; + } else if ('enable_seconds' in variable) { + return [field, timeVariable(variable as TimeVariable)]; } else { return [field, textVariable(variable as TextVariable)]; } diff --git a/src/data/variables/time_variable.ts b/src/data/variables/time_variable.ts new file mode 100644 index 00000000..b4c87aa0 --- /dev/null +++ b/src/data/variables/time_variable.ts @@ -0,0 +1,19 @@ +import { isDefined } from '../../helpers'; +import { EVariableType, TimeVariable } from '../../types'; + +export function timeVariable(...config: Partial[]) { + //factory function to create TimeVariable from configuration + + const name = config.map(e => e.name).filter(isDefined); + + const variable: TimeVariable = { + type: EVariableType.Time, + name: name.length ? name.reduce((_acc, val) => val) : undefined, + enable_seconds: config.some(e => e.enable_seconds), + }; + return variable; +} + +export function timeVariableDisplay(value: any, _variable: TimeVariable): string { + return String(value); +} diff --git a/src/editor/scheduler-editor-options.ts b/src/editor/scheduler-editor-options.ts index 436fc3c6..f1eb94d3 100755 --- a/src/editor/scheduler-editor-options.ts +++ b/src/editor/scheduler-editor-options.ts @@ -465,6 +465,7 @@ export class SchedulerEditorOptions extends LitElement {
${this.hass.localize('ui.panel.config.automation.editor.conditions.type.state.label')}
(this.conditionValue = ev.detail.value)} diff --git a/src/editor/scheduler-editor-time.ts b/src/editor/scheduler-editor-time.ts index be935f2b..48b3fb81 100755 --- a/src/editor/scheduler-editor-time.ts +++ b/src/editor/scheduler-editor-time.ts @@ -93,6 +93,12 @@ export class SchedulerEditorTime extends LitElement { render() { if (!this.hass || !this.config || !this.entities || !this.actions) return html``; + let timePickerHeader = ''; + if (!this.timeslots) { + timePickerHeader = this.hass.localize('ui.dialogs.helper_settings.input_datetime.time'); + if (this.entities?.[0].id.startsWith('time')) + timePickerHeader += ` (${localize('ui.panel.common.title', getLocale(this.hass))})`; + } return html`
@@ -102,7 +108,7 @@ export class SchedulerEditorTime extends LitElement { ${!this.timeslots ? html` ${this.getVariableEditor()} ${this.renderDays()} -
${this.hass.localize('ui.dialogs.helper_settings.input_datetime.time')}
+
${timePickerHeader}
html`
- ${capitalize( - PrettyPrintName( - entity.name || this.hass!.states[entity.id].attributes.friendly_name || computeEntity(entity.id) - ) - )} + ${capitalize(PrettyPrintName(this.getEntityName(entity)))}
` )} @@ -395,11 +401,15 @@ export class SchedulerEditorTime extends LitElement { return actions.map(action => { return Object.entries(this.actions!.find(e => compareActions(e, action, true))!.variables!).map( ([field, variable]) => { + let header: string = variable.name || PrettyPrintName(field); + if (header.toLowerCase() === 'time') { + const entity = this.entities?.[0]; + if (entity) header += ` (${PrettyPrintName(this.getEntityName(entity))})`; + } return html` -
- ${variable.name || PrettyPrintName(field)} -
+
${header}
diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 0aa4df87..6327f25c 100755 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -51,6 +51,9 @@ "script": { "script": "execute" }, + "time": { + "set_value": "set value[ to {time}]" + }, "vacuum": { "start_pause": "start / pause" }, @@ -76,6 +79,7 @@ "media_player": "media players", "notify": "notification", "switch": "switches", + "time": "time", "vacuum": "vacuums", "water_heater": "water heaters" }, diff --git a/src/standard-configuration/action_icons.ts b/src/standard-configuration/action_icons.ts index b07533c2..7eed24f6 100755 --- a/src/standard-configuration/action_icons.ts +++ b/src/standard-configuration/action_icons.ts @@ -123,6 +123,9 @@ const actionIcons: IconList = { turn_on: 'mdi:flash', turn_off: 'mdi:flash-off', }, + time: { + set_value: 'mdi:clock-outline', + }, vacuum: { turn_on: 'mdi:power', start: 'mdi:play-circle-outline', diff --git a/src/standard-configuration/action_name.ts b/src/standard-configuration/action_name.ts index 84e6a585..563fdb65 100755 --- a/src/standard-configuration/action_name.ts +++ b/src/standard-configuration/action_name.ts @@ -105,6 +105,9 @@ const actionNamesList: Record> = { turn_on: {}, turn_off: {}, }, + time: { + set_value: { + variables: { + time: { + enable_seconds: true, + }, + }, + }, + }, vacuum: { turn_on: { supported_feature: 1 }, start: { diff --git a/src/standard-configuration/standardActions.ts b/src/standard-configuration/standardActions.ts index c81e38f7..d25c8cf2 100755 --- a/src/standard-configuration/standardActions.ts +++ b/src/standard-configuration/standardActions.ts @@ -8,6 +8,7 @@ import { HassEntity } from 'home-assistant-js-websocket'; import { levelVariable } from '../data/variables/level_variable'; import { listVariable } from '../data/variables/list_variable'; import { textVariable } from '../data/variables/text_variable'; +import { timeVariable } from '../data/variables/time_variable'; import { actionName } from './action_name'; import { actionIcon } from './action_icons'; import { getVariableName } from './variable_name'; @@ -124,6 +125,8 @@ const parseActionVariable = ( return listVariable(config); } else if ('min' in config && isDefined(config.min) && 'max' in config && isDefined(config.max)) { return levelVariable(config); + } else if ('enable_seconds' in config && isDefined(config.enable_seconds)) { + return timeVariable(config); } else { return textVariable(config); } diff --git a/src/standard-configuration/standardIcon.ts b/src/standard-configuration/standardIcon.ts index c546172b..657ca06c 100755 --- a/src/standard-configuration/standardIcon.ts +++ b/src/standard-configuration/standardIcon.ts @@ -75,6 +75,7 @@ export const domainIcons: Record = { sensor: 'mdi:eye', sun: 'mdi:white-balance-sunny', switch: 'mdi:flash', + time: 'mdi:clock', timer: 'mdi:timer', vacuum: 'mdi:robot-vacuum', water_heater: 'mdi:water-boiler', diff --git a/src/standard-configuration/standardStates.ts b/src/standard-configuration/standardStates.ts index 18a8fed7..404577ca 100755 --- a/src/standard-configuration/standardStates.ts +++ b/src/standard-configuration/standardStates.ts @@ -4,6 +4,7 @@ import { DefaultActionIcon } from '../const'; import { levelVariable } from '../data/variables/level_variable'; import { listVariable } from '../data/variables/list_variable'; import { textVariable } from '../data/variables/text_variable'; +import { timeVariable } from '../data/variables/time_variable'; import { getLocale, isDefined } from '../helpers'; import { localize } from '../localize/localize'; import { Variable } from '../types'; @@ -43,6 +44,8 @@ export function standardStates(entity_id: string, hass: HomeAssistant): Variable return listVariable(stateConfig); } else if ('min' in stateConfig && isDefined(stateConfig.min) && 'max' in stateConfig && isDefined(stateConfig.max)) { return levelVariable(stateConfig); + } else if ('enable_seconds' in stateConfig && isDefined(stateConfig.enable_seconds)) { + return timeVariable(stateConfig); } else { return textVariable(stateConfig); } diff --git a/src/standard-configuration/variables.ts b/src/standard-configuration/variables.ts index edfb0fdf..e6fa544e 100755 --- a/src/standard-configuration/variables.ts +++ b/src/standard-configuration/variables.ts @@ -1,7 +1,7 @@ import { HomeAssistant } from 'custom-card-helpers'; import { HassEntity } from 'home-assistant-js-websocket'; import { isDefined, omit, pick } from '../helpers'; -import { AtLeast, LevelVariable, ListVariable, TextVariable } from '../types'; +import { AtLeast, LevelVariable, ListVariable, TextVariable, TimeVariable } from '../types'; import { listAttribute, numericAttribute, stringAttribute } from './attribute'; type ListOption = { name: string; icon: string }; @@ -31,22 +31,28 @@ type TextVariableConfig = { multiline?: boolean; }; -export type VariableConfig = ListVariableType | LevelVariableType | TextVariableConfig; +type TimeVariableConfig = { + enable_seconds?: boolean; +}; + +export type VariableConfig = ListVariableType | LevelVariableType | TextVariableConfig | TimeVariableConfig; export const parseVariable = ( config: VariableConfig, stateObj: HassEntity | undefined, hass: HomeAssistant -): Partial | AtLeast | Partial => { +): Partial | AtLeast | Partial | TimeVariable => { const res = 'template' in config && isDefined(config.template) ? { ...omit(config, 'template'), ...config.template(stateObj, hass) } - : ({ ...config } as ListVariableConfig | LevelVariableConfig | TextVariableConfig); + : ({ ...config } as ListVariableConfig | LevelVariableConfig | TextVariableConfig | TimeVariableConfig); if ('options' in res) { return parseListVariable(res, stateObj); } else if ('min' in res && 'max' in res) { return parseLevelVariable(res, stateObj); + } else if ('enable_seconds' in res) { + return { enable_seconds: res.enable_seconds ?? true } as TimeVariable; } else { return res as TextVariableConfig; } diff --git a/src/types.ts b/src/types.ts old mode 100644 new mode 100755 index 66088553..387ea6b5 --- a/src/types.ts +++ b/src/types.ts @@ -132,6 +132,7 @@ export enum EVariableType { Level = 'LEVEL', List = 'LIST', Text = 'TEXT', + Time = 'TIME', } export interface Variable { @@ -163,7 +164,11 @@ export interface TextVariable extends Variable { multiline: boolean; } -export type VariableDictionary = Dictionary; +export interface TimeVariable extends Variable { + enable_seconds: boolean; +} + +export type VariableDictionary = Dictionary; /* entries */