Skip to content

Commit

Permalink
Add the time domain (time entities) to the list of standard configura…
Browse files Browse the repository at this point in the history
…tion actions
  • Loading branch information
pdcastro committed Apr 26, 2024
1 parent 8cab754 commit 9811197
Show file tree
Hide file tree
Showing 20 changed files with 149 additions and 21 deletions.
6 changes: 5 additions & 1 deletion src/components/timeslot-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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(', ');
Expand Down
31 changes: 30 additions & 1 deletion src/components/variable-picker.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
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';
import { fireEvent } from 'custom-card-helpers';

@customElement('scheduler-variable-picker')
export class SchedulerVariablePicker extends LitElement {
@property()
hass?: HomeAssistant;

@property()
variable?: Variable | null;

Expand All @@ -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``;
}

Expand Down Expand Up @@ -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`
<ha-time-input
.enableSecond=${variable.enable_seconds}
.value=${value}
.locale=${this.hass.locale}
@value-changed=${this.listVariableUpdated}
></ha-time-input>
<div class="key">${variable.enable_seconds ? 'Hours:Minutes:Seconds' : 'Hours:Minutes'}</div>
`;
}

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;
}
`;
}
19 changes: 18 additions & 1 deletion src/data/actions/assign_action.ts
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down Expand Up @@ -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 = {
Expand Down
1 change: 1 addition & 0 deletions src/data/actions/compare_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 4 additions & 1 deletion src/data/actions/compute_action_display.ts
Original file line number Diff line number Diff line change
@@ -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 = /\[([^\]]+)\]/;
Expand All @@ -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];
Expand Down
1 change: 1 addition & 0 deletions src/data/actions/migrate_action_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down
7 changes: 5 additions & 2 deletions src/data/variables/compute_merged_variable.ts
Original file line number Diff line number Diff line change
@@ -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>[]
): 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))!);
Expand All @@ -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[]));
Expand Down
5 changes: 4 additions & 1 deletion src/data/variables/compute_variables.ts
Original file line number Diff line number Diff line change
@@ -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<Partial<LevelVariable | ListVariable | TextVariable>>
Expand All @@ -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)];
}
Expand Down
19 changes: 19 additions & 0 deletions src/data/variables/time_variable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { isDefined } from '../../helpers';
import { EVariableType, TimeVariable } from '../../types';

export function timeVariable(...config: Partial<TimeVariable>[]) {
//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);
}
1 change: 1 addition & 0 deletions src/editor/scheduler-editor-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ export class SchedulerEditorOptions extends LitElement {
<div class="header">${this.hass.localize('ui.panel.config.automation.editor.conditions.type.state.label')}</div>
<scheduler-variable-picker
.hass=${this.hass}
.variable=${states}
.value=${this.conditionValue}
@value-changed=${(ev: CustomEvent) => (this.conditionValue = ev.detail.value)}
Expand Down
28 changes: 19 additions & 9 deletions src/editor/scheduler-editor-time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
<div class="content">
<div class="header">
Expand All @@ -102,7 +108,7 @@ export class SchedulerEditorTime extends LitElement {
${!this.timeslots
? html`
${this.getVariableEditor()} ${this.renderDays()}
<div class="header">${this.hass.localize('ui.dialogs.helper_settings.input_datetime.time')}</div>
<div class="header">${timePickerHeader}</div>
<time-picker
.hass=${this.hass}
.value=${this.schedule.timeslots[0].start}
Expand Down Expand Up @@ -150,6 +156,10 @@ export class SchedulerEditorTime extends LitElement {
`;
}

getEntityName(entity: EntityElement) {
return entity.name || this.hass!.states[entity.id].attributes.friendly_name || computeEntity(entity.id);
}

renderSummary() {
if (!this.entities || !this.actions) return html``;
return html`
Expand All @@ -159,11 +169,7 @@ export class SchedulerEditorTime extends LitElement {
entity => html`
<div>
<ha-icon icon="${PrettyPrintIcon(entity.icon)}"> </ha-icon>
${capitalize(
PrettyPrintName(
entity.name || this.hass!.states[entity.id].attributes.friendly_name || computeEntity(entity.id)
)
)}
${capitalize(PrettyPrintName(this.getEntityName(entity)))}
</div>
`
)}
Expand Down Expand Up @@ -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`
<div class="header">
${variable.name || PrettyPrintName(field)}
</div>
<div class="header">${header}</div>
<scheduler-variable-picker
.hass=${this.hass}
.variable=${variable}
.value=${action.service_data ? action.service_data[field] : null}
@value-changed=${(ev: CustomEvent) =>
Expand Down
4 changes: 4 additions & 0 deletions src/localize/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
"script": {
"script": "execute"
},
"time": {
"set_value": "set value[ to {time}]"
},
"vacuum": {
"start_pause": "start / pause"
},
Expand All @@ -76,6 +79,7 @@
"media_player": "media players",
"notify": "notification",
"switch": "switches",
"time": "time",
"vacuum": "vacuums",
"water_heater": "water heaters"
},
Expand Down
3 changes: 3 additions & 0 deletions src/standard-configuration/action_icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
3 changes: 3 additions & 0 deletions src/standard-configuration/action_name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ const actionNamesList: Record<string, Record<string, string | actionNameTemplate
turn_on: 'ui.card.vacuum.actions.turn_on',
turn_off: 'ui.card.vacuum.actions.turn_off',
},
time: {
set_value: 'services.time.set_value',
},
vacuum: {
turn_on: 'ui.card.vacuum.actions.turn_on',
start: 'ui.card.vacuum.start_cleaning',
Expand Down
9 changes: 9 additions & 0 deletions src/standard-configuration/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,15 @@ export const actionList: Record<string, Record<string, ActionItem>> = {
turn_on: {},
turn_off: {},
},
time: {
set_value: {
variables: {
time: {
enable_seconds: true,
},
},
},
},
vacuum: {
turn_on: { supported_feature: 1 },
start: {
Expand Down
3 changes: 3 additions & 0 deletions src/standard-configuration/standardActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions src/standard-configuration/standardIcon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const domainIcons: Record<string, string> = {
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',
Expand Down
3 changes: 3 additions & 0 deletions src/standard-configuration/standardStates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
}
Expand Down
Loading

0 comments on commit 9811197

Please sign in to comment.