From 4e360ca8cc72001c5f3a24ed9c4d364c18cf049c Mon Sep 17 00:00:00 2001 From: niels Date: Thu, 22 Oct 2020 07:31:32 +0200 Subject: [PATCH 1/3] update support for group entities --- src/custom-elements/condition-entity-row.ts | 2 +- src/custom-elements/scheduler-card-editor.ts | 6 +-- .../scheduler-entities-card.ts | 4 +- src/custom-elements/scheduler-entity-row.ts | 5 +-- .../scheduler-entitypicker-card.ts | 7 ++-- src/custom-elements/scheduler-options-card.ts | 9 ++--- .../scheduler-timepicker-card.ts | 1 - src/entity.ts | 25 ++++++------- src/formatAction.ts | 4 +- src/scheduler-card.ts | 6 +-- src/standard-configuration/climate.ts | 17 +++++---- src/standard-configuration/group.ts | 22 +++++++++-- src/standard-configuration/standardActions.ts | 37 ++++++++++--------- 13 files changed, 80 insertions(+), 65 deletions(-) diff --git a/src/custom-elements/condition-entity-row.ts b/src/custom-elements/condition-entity-row.ts index b8ee7058..c2ba4dbf 100755 --- a/src/custom-elements/condition-entity-row.ts +++ b/src/custom-elements/condition-entity-row.ts @@ -21,7 +21,7 @@ export class ConditionEntityRow extends LitElement { firstUpdated() { if (!this.hass || !this.config || !this.item) return; - this.entity = entityConfig(this.hass.states[this.item.entity], this.config)!; + this.entity = entityConfig(this.item.entity, this.hass, this.config)!; } render() { diff --git a/src/custom-elements/scheduler-card-editor.ts b/src/custom-elements/scheduler-card-editor.ts index de8b2845..93ff793e 100755 --- a/src/custom-elements/scheduler-card-editor.ts +++ b/src/custom-elements/scheduler-card-editor.ts @@ -174,9 +174,9 @@ export class SchedulerCardEditor extends LitElement implements LovelaceCardEdito getDomainSwitches() { if (!this._config || !this.hass) return; let includedDomains = this._config.include ? [...this._config.include] : []; - const entityList = Object.values(this.hass.states) - .filter(e => !IsSchedulerEntity(e.entity_id)) - .map(e => entityConfig(e, { include: ["*"] })) + const entityList = Object.keys(this.hass.states) + .filter(e => !IsSchedulerEntity(e)) + .map(e => entityConfig(e, this.hass!, { include: ["*"] })) .filter(e => e && e.actions.length) as EntityElement[]; const domainList = entityList diff --git a/src/custom-elements/scheduler-entities-card.ts b/src/custom-elements/scheduler-entities-card.ts index 0fd55d4a..b4f70f6e 100755 --- a/src/custom-elements/scheduler-entities-card.ts +++ b/src/custom-elements/scheduler-entities-card.ts @@ -29,7 +29,7 @@ export class SchedulerEntitiesCard extends LitElement { if (this.config.discover_existing !== undefined && !this.config.discover_existing) { schedules = schedules.filter(el => - (el.attributes.actions.map(e => e.entity) as string[]).every(e => this._hass!.states[e] && entityFilter(this._hass!.states[e], this.config!)) + (el.attributes.actions.map(e => e.entity) as string[]).every(e => this._hass!.states[e] && entityFilter(e, this._hass!, this.config!)) ); } @@ -109,7 +109,7 @@ export class SchedulerEntitiesCard extends LitElement { if (!this.schedules.length) return html`${localize('instructions.no_entries_defined')}`; return this.schedules.map(e => e.entity_id).map(entity_id => { const entity = this._hass!.states[entity_id] as ScheduleEntity; - let discovered = !(entity.attributes.actions!.map(importAction).every(e => entityFilter(e.entity, this.config!))); + let discovered = !(entity.attributes.actions!.map(importAction).every(e => entityFilter(e.entity, this._hass!, this.config!))); if (discovered) { return html` diff --git a/src/custom-elements/scheduler-entity-row.ts b/src/custom-elements/scheduler-entity-row.ts index 7b6452a0..1c7124ae 100755 --- a/src/custom-elements/scheduler-entity-row.ts +++ b/src/custom-elements/scheduler-entity-row.ts @@ -41,15 +41,14 @@ export class ScheduleEntityRow extends LitElement { const entries: ImportedEntry[] = stateObj.attributes.entries.map(importEntry); const nextEntry = this.computeNextEntry(entries); const action: HassAction = importAction(nextEntry.actions.map(e => stateObj.attributes.actions[e])[0]); - const entity = this.hass.states[action.entity]; let entityName = DeadEntityName; let icon = DeadEntityIcon; - let entityCfg = entityConfig(entity, this.config); + let entityCfg = entityConfig(action.entity, this.hass, this.config); if (entityCfg) { //entity exists in HASS let actionCfg = findAction(entityCfg, action); - icon = actionCfg.icon || entityCfg.icon || entity.attributes.icon || DefaultActionIcon; + icon = actionCfg.icon || entityCfg.icon || DefaultActionIcon; entityName = entityCfg.name; } diff --git a/src/custom-elements/scheduler-entitypicker-card.ts b/src/custom-elements/scheduler-entitypicker-card.ts index bab9a374..0cbb6ab9 100755 --- a/src/custom-elements/scheduler-entitypicker-card.ts +++ b/src/custom-elements/scheduler-entitypicker-card.ts @@ -24,9 +24,8 @@ export class SchedulerEditorCard extends LitElement { getGroups() { if (!this.hass || !this.config) return []; - const entities = Object.values(this.hass.states) - .filter(e => entityFilter(e, this.config!, { actions: true })) - .map(e => e.entity_id); + const entities = Object.keys(this.hass.states) + .filter(e => entityFilter(e, this.hass!, this.config!, { actions: true })); let groups = entityGroups(entities, this.config!); groups.sort((a, b) => a.name.trim().toLowerCase() < b.name.trim().toLowerCase() ? -1 : 1); @@ -39,7 +38,7 @@ export class SchedulerEditorCard extends LitElement { let entities = groupConfig .find(e => e.id == this.selectedGroup)!.entities - .map(e => entityConfig(this.hass!.states[e], this.config!)) + .map(e => entityConfig(e, this.hass!, this.config!)) .filter(e => e) as EntityElement[]; entities.sort((a, b) => a.name.trim().toLowerCase() < b.name.trim().toLowerCase() ? -1 : 1); diff --git a/src/custom-elements/scheduler-options-card.ts b/src/custom-elements/scheduler-options-card.ts index 95f8222d..ef19bc96 100755 --- a/src/custom-elements/scheduler-options-card.ts +++ b/src/custom-elements/scheduler-options-card.ts @@ -105,16 +105,15 @@ export class SchedulerOptionsCard extends LitElement { renderAddCondition() { if (!this.addCondition || !this.hass || !this.config) return html``; - let hassEntities = Object.values(this.hass.states) - .filter(e => entityFilter(e, this.config!, { states: true })) - .map(e => e.entity_id); + let hassEntities = Object.keys(this.hass.states) + .filter(e => entityFilter(e, this.hass!, this.config!, { states: true })); let groups = entityGroups(hassEntities, this.config!); groups.sort((a, b) => a.name.trim().toLowerCase() < b.name.trim().toLowerCase() ? -1 : 1); let entities: EntityElement[] = []; if (this.selectedGroup) { entities = groups.find(e => e.id == this.selectedGroup)!.entities - .map(e => entityConfig(this.hass!.states[e], this.config!)) + .map(e => entityConfig(e, this.hass!, this.config!)) .filter(e => e) as EntityElement[]; entities.sort((a, b) => a.name.trim().toLowerCase() < b.name.trim().toLowerCase() ? -1 : 1); } @@ -199,7 +198,7 @@ export class SchedulerOptionsCard extends LitElement { confirmConditionClick() { if (!this.selectedEntity || !this.config || !this.hass) return; - const states = entityConfig(this.hass.states[this.selectedEntity], this.config)!.states!; + const states = entityConfig(this.selectedEntity, this.hass, this.config)!.states!; const step = Array.isArray(states) ? 1 : states.step || 1; let default_state = Array.isArray(states) ? states[0] : Number((Math.round((states.min + states.max) / 2 / step) * step).toPrecision(5)); let condition: Condition = { diff --git a/src/custom-elements/scheduler-timepicker-card.ts b/src/custom-elements/scheduler-timepicker-card.ts index 8968c5bb..43e1b2eb 100755 --- a/src/custom-elements/scheduler-timepicker-card.ts +++ b/src/custom-elements/scheduler-timepicker-card.ts @@ -210,7 +210,6 @@ export class SchedulerTimepickerCard extends LitElement { if (!actionConfig.variable) return html``; if (actionConfig.variable.type == EVariableType.Level) { let config = actionConfig.variable as LevelVariableConfig; - if (!config.unit && config.field == FieldTemperature) Object.assign(config, { unit: this.hass.config.unit_system.temperature }); if (!this.entries[this.activeEntry].variable) this.updateActiveEntry({ variable: { type: EVariableType.Level, value: config.min, enabled: !config.optional } }); diff --git a/src/entity.ts b/src/entity.ts index 4e600ed4..19b3f41d 100755 --- a/src/entity.ts +++ b/src/entity.ts @@ -1,4 +1,4 @@ -import { computeEntity, computeDomain } from 'custom-card-helpers'; +import { computeEntity, computeDomain, HomeAssistant } from 'custom-card-helpers'; import { Dictionary, EntityConfig, EntityElement, GroupConfig, CardConfig } from './types'; import { DefaultEntityIcon } from './const'; import { standardActions } from './standard-configuration/standardActions'; @@ -14,21 +14,21 @@ export function IsSchedulerEntity(entity_id: string) { return entity_id.match(/^switch.schedule_[0-9a-f]{6}$/); } -export function entityConfig(entity: HassEntity | undefined, config: Partial) { - if (!entity) return; - const entity_id = typeof entity == "string" ? entity : entity.entity_id; +export function entityConfig(entity_id: string, hass: HomeAssistant, config: Partial) { + const stateObj = hass.states[entity_id]; + if (!stateObj) return; let output: EntityElement = { id: entity_id, - name: entity.attributes.friendly_name || computeEntity(entity_id), + name: stateObj.attributes.friendly_name || computeEntity(entity_id), icon: DefaultEntityIcon, actions: [], }; if (config.standard_configuration === undefined || config.standard_configuration) { - output = { ...output, actions: [...standardActions(entity)], icon: standardIcon(entity), states: standardStates(entity) } + output = { ...output, actions: [...standardActions(entity_id, hass)], icon: standardIcon(stateObj), states: standardStates(stateObj) } } - output = { ...output, icon: entity.attributes.icon || output.icon }; + output = { ...output, icon: stateObj.attributes.icon || output.icon }; if (config.customize) { const customize = Object.entries(config.customize) @@ -57,17 +57,14 @@ export function entityConfig(entity: HassEntity | undefined, config: Partial; groups?: GroupConfig[] }, +export function entityFilter(entity_id: string, hass: HomeAssistant, + config: CardConfig, options?: { states?: boolean; actions?: boolean } ) { - const entity_id = (typeof entity == "object") ? entity.entity_id : entity; - if (IsSchedulerEntity(entity_id)) return false; else if (!applyFilters(entity_id, config) && (!config.groups || !config.groups.some(e => applyFilters(entity_id, e)))) return false; - if (options && typeof entity == "object") { - const entityCfg = entityConfig(entity, config); + if (options) { + const entityCfg = entityConfig(entity_id, hass, config); if (!entityCfg) return false; if (options.states && !entityCfg.states) return false; if (options.actions && !entityCfg.actions.length) return false; diff --git a/src/formatAction.ts b/src/formatAction.ts index 92f84eb9..7b8282c3 100755 --- a/src/formatAction.ts +++ b/src/formatAction.ts @@ -1,7 +1,7 @@ import { Dictionary, HassAction, ActionElement } from "./types"; import { localize } from "./localize/localize"; import { uniqueId } from "./action"; -import { HomeAssistant } from "custom-card-helpers"; +import { HomeAssistant, computeEntity } from "custom-card-helpers"; import { PrettyPrintName } from "./helpers"; export function formatVariable(variable: string, value: string | number, hass: HomeAssistant) { @@ -21,7 +21,7 @@ export function formatVariable(variable: string, value: string | number, hass: H export function formatAction(config: ActionElement | HassAction, hass: HomeAssistant) { if ("name" in config && config.name) return String(config.name); - const service = config.service; + const service = computeEntity(config.service); let service_data: Dictionary = { ...config.service_data }; let translation: string | undefined; diff --git a/src/scheduler-card.ts b/src/scheduler-card.ts index 850e997e..dd588ae5 100644 --- a/src/scheduler-card.ts +++ b/src/scheduler-card.ts @@ -168,7 +168,7 @@ export class SchedulerCard extends LitElement { private _confirmItemClick(ev: CustomEvent): void { if (!this._hass || !this._config) return; const entity = ev.detail.entity; - this.entity = entityConfig(this._hass.states[entity], this._config)!; + this.entity = entityConfig(entity, this._hass, this._config)!; const action = ev.detail.action; if (action != CreateTimeScheme) { @@ -226,7 +226,7 @@ export class SchedulerCard extends LitElement { this.entries.forEach(entry => { if (!entry.action || !entry.entity) return; - const entity = entityConfig(this._hass!.states[entry.entity], this._config!)!; + const entity = entityConfig(entry.entity, this._hass!, this._config!)!; const action = findAction(entity, { service: entry.action }); const variableData = exportActionVariable(action, entry); const output: HassAction = { @@ -322,7 +322,7 @@ export class SchedulerCard extends LitElement { if (!scheduleEntity) return; const entries: ImportedEntry[] = scheduleEntity.attributes.entries.map(importEntry); const entity_id = importAction(scheduleEntity.attributes.actions[0]).entity; - this.entity = entityConfig(this._hass.states[entity_id], this._config); + this.entity = entityConfig(entity_id, this._hass, this._config); if (!this.entity) { const actions = scheduleEntity.attributes.actions diff --git a/src/standard-configuration/climate.ts b/src/standard-configuration/climate.ts index 250aaf20..57f16a0d 100755 --- a/src/standard-configuration/climate.ts +++ b/src/standard-configuration/climate.ts @@ -2,6 +2,7 @@ import { HassEntity } from "home-assistant-js-websocket"; import { levelVariable, listVariable, listVariableOption } from "../actionVariables"; import { ActionConfig } from "../types"; import { TurnOffAction } from "../const"; +import { HomeAssistant } from "custom-card-helpers"; const modeIcons = { heat: "fire", @@ -12,17 +13,19 @@ const modeIcons = { fan_only: "fan" } -export function climateActions(entity: HassEntity) { - const supportedFeatures = entity.attributes.supported_features!; - const presetModes = entity.attributes.preset_modes; - const hvacModes = entity.attributes.hvac_modes; +export function climateActions(entity_id: string, hass: HomeAssistant) { + const stateObj = hass.states[entity_id]; + const supportedFeatures = stateObj.attributes.supported_features!; + const presetModes = stateObj.attributes.preset_modes; + const hvacModes = stateObj.attributes.hvac_modes; const filteredHvacModes = hvacModes.filter(e => !['off', 'heat', 'cool', 'heat_cool'].includes(e)) const tempVariable = levelVariable({ field: "temperature", - min: entity.attributes.min_temp, - max: entity.attributes.max_temp, - step: 0.5, + min: stateObj.attributes.min_temp, + max: stateObj.attributes.max_temp, + unit: hass.config.unit_system.temperature, + step: hass.config.unit_system.temperature.includes('C') ? 0.5 : 1, }); let actions: ActionConfig[] = []; diff --git a/src/standard-configuration/group.ts b/src/standard-configuration/group.ts index 1bf541ae..1b8a95dd 100755 --- a/src/standard-configuration/group.ts +++ b/src/standard-configuration/group.ts @@ -2,12 +2,28 @@ import { listVariable, listVariableOption } from "../actionVariables"; import { ActionConfig } from "../types"; import { HassEntity } from "home-assistant-js-websocket"; import { TurnOnAction, TurnOffAction } from "../const"; +import { HomeAssistant, computeDomain } from "custom-card-helpers"; +import { standardActions } from "./standardActions"; +import { uniqueId } from "../action"; -export function groupActions(entity: HassEntity) { - const entities = entity.attributes.entity_id!; +export function groupActions(entity: HassEntity, entityActions: ActionConfig[][]) { + const entities = entity.attributes.entity_id; - let actions: ActionConfig[] = [TurnOnAction, TurnOffAction]; + //prepend the entity domain to the services + entityActions = entityActions.map((actionList, index) => { + const domain = computeDomain(entities[index]); + return actionList.map(e => { + return computeDomain(e.service) == domain ? e : { ...e, service: `${domain}.${e.service}` }; + }); + }); + //find matches + let actions = entityActions[0].filter(action => { + return entityActions.every(e => { + return e.map(uniqueId) + .includes(uniqueId(action)); + }); + }); return actions; } \ No newline at end of file diff --git a/src/standard-configuration/standardActions.ts b/src/standard-configuration/standardActions.ts index ffe9b544..c878a776 100755 --- a/src/standard-configuration/standardActions.ts +++ b/src/standard-configuration/standardActions.ts @@ -1,5 +1,5 @@ import { HassEntity } from "home-assistant-js-websocket"; -import { computeDomain } from "custom-card-helpers"; +import { computeDomain, HomeAssistant } from "custom-card-helpers"; import { alarmControlPanelActions } from "./alarm_control_panel"; import { ActionConfig } from "../types"; import { climateActions } from "./climate"; @@ -18,51 +18,54 @@ import { TurnOnAction, TurnOffAction } from "../const"; import { groupActions } from "./group"; -export function standardActions(entity: HassEntity): ActionConfig[] { - const domain = computeDomain(entity.entity_id); - const supportedFeatures = entity.attributes.supported_features; +export function standardActions(entity_id: string, hass: HomeAssistant): ActionConfig[] { + const domain = computeDomain(entity_id); + const stateObj = hass.states[entity_id]; + if (!stateObj) return []; //entity does not exist switch (domain) { case "alarm_control_panel": - return alarmControlPanelActions(entity); + return alarmControlPanelActions(stateObj); case "climate": - return climateActions(entity); + return climateActions(entity_id, hass); case "cover": - return coverActions(entity); + return coverActions(stateObj); case "fan": - return fanActions(entity); + return fanActions(stateObj); case "group": - return groupActions(entity); + const entities: string[] = stateObj.attributes.entity_id! || []; + const configs = entities.map(e => standardActions(e, hass)); + return groupActions(stateObj, configs); case "humidifer": - return humidifierActions(entity); + return humidifierActions(stateObj); case "input_boolean": return [ { ...TurnOnAction, icon: "flash" }, { ...TurnOffAction, icon: "flash-off" } ]; case "input_number": - return inputNumberActions(entity); + return inputNumberActions(stateObj); case "input_select": - return inputSelectActions(entity); + return inputSelectActions(stateObj); case "light": - return lightActions(entity); + return lightActions(stateObj); case "lock": return lockActions; case "media_player": - return mediaPlayerActions(entity); + return mediaPlayerActions(stateObj); case "scene": return [{ ...TurnOnAction, icon: "play" }]; case "script": - return scriptActions(entity); + return scriptActions(stateObj); case "switch": return [ { ...TurnOnAction, icon: "flash" }, { ...TurnOffAction, icon: "flash-off" } ]; case "vacuum": - return vacuumActions(entity); + return vacuumActions(stateObj); case "water_heater": - return waterHeaterActions(entity); + return waterHeaterActions(stateObj); default: return [] From 87e11e897bf38a3adcf45a9bdccdfb25ebd53f54 Mon Sep 17 00:00:00 2001 From: niels Date: Thu, 22 Oct 2020 09:03:49 +0200 Subject: [PATCH 2/3] add support for hiding entity name in overview page --- src/custom-elements/condition-entity-row.ts | 7 ++++--- src/custom-elements/scheduler-entities-card.ts | 2 +- src/custom-elements/scheduler-entity-row.ts | 5 +++-- src/custom-elements/scheduler-entitypicker-card.ts | 7 ++++--- src/custom-elements/scheduler-options-card.ts | 7 ++++--- src/custom-elements/scheduler-timepicker-card.ts | 6 +++--- src/entity.ts | 1 - src/types.ts | 2 +- 8 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/custom-elements/condition-entity-row.ts b/src/custom-elements/condition-entity-row.ts index c2ba4dbf..b4ea990d 100755 --- a/src/custom-elements/condition-entity-row.ts +++ b/src/custom-elements/condition-entity-row.ts @@ -1,7 +1,7 @@ import { LitElement, html, customElement, css, property } from 'lit-element'; import { Condition, EConditionMatchType, CardConfig, EntityElement } from '../types'; import { PrettyPrintIcon, PrettyPrintName } from '../helpers'; -import { HomeAssistant } from 'custom-card-helpers'; +import { HomeAssistant, computeEntity } from 'custom-card-helpers'; import { entityConfig } from '../entity'; import { DefaultEntityIcon } from '../const'; @@ -26,8 +26,9 @@ export class ConditionEntityRow extends LitElement { render() { if (!this.item || !this.hass || !this.config) return html``; + const stateObj = this.hass.states[this.item.entity]; - if (!this.entity) { + if (!this.entity || !stateObj) { return html` Entity not found '${this.item.entity}' @@ -39,7 +40,7 @@ export class ConditionEntityRow extends LitElement {
- ${PrettyPrintName(this.entity.name)} + ${PrettyPrintName(this.entity.name || stateObj.attributes.friendly_name || computeEntity(stateObj.entity_id))} ${this.getMatchTypeButton()} ${this.getStateButton()} diff --git a/src/custom-elements/scheduler-entities-card.ts b/src/custom-elements/scheduler-entities-card.ts index b4f70f6e..87bd42a0 100755 --- a/src/custom-elements/scheduler-entities-card.ts +++ b/src/custom-elements/scheduler-entities-card.ts @@ -29,7 +29,7 @@ export class SchedulerEntitiesCard extends LitElement { if (this.config.discover_existing !== undefined && !this.config.discover_existing) { schedules = schedules.filter(el => - (el.attributes.actions.map(e => e.entity) as string[]).every(e => this._hass!.states[e] && entityFilter(e, this._hass!, this.config!)) + (el.attributes.actions.map(importAction).map(e => e.entity) as string[]).every(e => this._hass!.states[e] && entityFilter(e, this._hass!, this.config!)) ); } diff --git a/src/custom-elements/scheduler-entity-row.ts b/src/custom-elements/scheduler-entity-row.ts index 1c7124ae..986326ff 100755 --- a/src/custom-elements/scheduler-entity-row.ts +++ b/src/custom-elements/scheduler-entity-row.ts @@ -49,7 +49,7 @@ export class ScheduleEntityRow extends LitElement { if (entityCfg) { //entity exists in HASS let actionCfg = findAction(entityCfg, action); icon = actionCfg.icon || entityCfg.icon || DefaultActionIcon; - entityName = entityCfg.name; + entityName = entityCfg.name !== undefined ? entityCfg.name : this.hass.states[action.entity].attributes.friendly_name || computeEntity(action.entity); } let friendlyName = stateObj.attributes.friendly_name?.match(/^schedule\ #[0-9a-f]{6}$/i) ? '' : stateObj.attributes.friendly_name; @@ -62,7 +62,8 @@ export class ScheduleEntityRow extends LitElement { >
- ${capitalize(PrettyPrintName(entityName))}: ${capitalize(formatAction(action, this.hass))} + ${entityName.length ? `${capitalize(PrettyPrintName(entityName))}: ` : ''} + ${capitalize(formatAction(action, this.hass))}
${capitalize(relativeTime(this.computeTimestamp(nextEntry)))}
${entries.length > 1 ? entries.length == 2 ? localize('misc.one_additional_task') : localize("misc.x_additional_tasks", "{count}", String(entries.length - 1)) : ''} diff --git a/src/custom-elements/scheduler-entitypicker-card.ts b/src/custom-elements/scheduler-entitypicker-card.ts index 0cbb6ab9..8ffcffb1 100755 --- a/src/custom-elements/scheduler-entitypicker-card.ts +++ b/src/custom-elements/scheduler-entitypicker-card.ts @@ -1,5 +1,5 @@ import { LitElement, html, customElement, property } from 'lit-element'; -import { HomeAssistant } from 'custom-card-helpers'; +import { HomeAssistant, computeEntity } from 'custom-card-helpers'; import { localize } from '../localize/localize'; import { CardConfig, EntityElement, GroupElement, ActionElement } from '../types'; import { entityFilter, entityConfig } from '../entity'; @@ -39,9 +39,10 @@ export class SchedulerEditorCard extends LitElement { let entities = groupConfig .find(e => e.id == this.selectedGroup)!.entities .map(e => entityConfig(e, this.hass!, this.config!)) - .filter(e => e) as EntityElement[]; + .filter(e => e) + .map(e => e!.name ? e : Object.assign(e, { name: this.hass!.states[e!.id].attributes.friendly_name || computeEntity(e!.id) })) as EntityElement[]; - entities.sort((a, b) => a.name.trim().toLowerCase() < b.name.trim().toLowerCase() ? -1 : 1); + entities.sort((a, b) => a.name!.trim().toLowerCase() < b.name!.trim().toLowerCase() ? -1 : 1); return entities; } diff --git a/src/custom-elements/scheduler-options-card.ts b/src/custom-elements/scheduler-options-card.ts index ef19bc96..37a0c2c9 100755 --- a/src/custom-elements/scheduler-options-card.ts +++ b/src/custom-elements/scheduler-options-card.ts @@ -4,7 +4,7 @@ import { localize } from '../localize/localize'; import { EConditionType, CardConfig, Entry, EntityElement, Condition, EConditionMatchType } from '../types'; import './condition-entity-row'; -import { HomeAssistant } from 'custom-card-helpers'; +import { HomeAssistant, computeEntity } from 'custom-card-helpers'; import { entityGroups } from '../group'; import { entityConfig, entityFilter } from '../entity'; import { commonStyle } from '../styles'; @@ -114,8 +114,9 @@ export class SchedulerOptionsCard extends LitElement { if (this.selectedGroup) { entities = groups.find(e => e.id == this.selectedGroup)!.entities .map(e => entityConfig(e, this.hass!, this.config!)) - .filter(e => e) as EntityElement[]; - entities.sort((a, b) => a.name.trim().toLowerCase() < b.name.trim().toLowerCase() ? -1 : 1); + .filter(e => e) + .map(e => e!.name ? e : Object.assign(e, { name: this.hass!.states[e!.id].attributes.friendly_name || computeEntity(e!.id) })) as EntityElement[]; + entities.sort((a, b) => a.name!.trim().toLowerCase() < b.name!.trim().toLowerCase() ? -1 : 1); } return html` diff --git a/src/custom-elements/scheduler-timepicker-card.ts b/src/custom-elements/scheduler-timepicker-card.ts index 43e1b2eb..a11bf14b 100755 --- a/src/custom-elements/scheduler-timepicker-card.ts +++ b/src/custom-elements/scheduler-timepicker-card.ts @@ -1,5 +1,5 @@ import { LitElement, html, customElement, css, property } from 'lit-element'; -import { HomeAssistant } from 'custom-card-helpers'; +import { HomeAssistant, computeEntity } from 'custom-card-helpers'; import { localize } from '../localize/localize'; import { CardConfig, ActionElement, EntityElement, EVariableType, LevelVariableConfig, LevelVariable, ListVariable, Entry, ListVariableConfig } from '../types'; import { PrettyPrintIcon, PrettyPrintName, capitalize } from '../helpers'; @@ -55,7 +55,7 @@ export class SchedulerTimepickerCard extends LitElement {
- ${capitalize(PrettyPrintName(this.entity.name))} + ${capitalize(PrettyPrintName(this.entity.name || this.hass!.states[this.entity.id].attributes.friendly_name || computeEntity(this.entity.id)))}
@@ -114,7 +114,7 @@ export class SchedulerTimepickerCard extends LitElement {
- ${capitalize(PrettyPrintName(this.entity.name))} + ${capitalize(PrettyPrintName(this.entity.name || this.hass!.states[this.entity.id].attributes.friendly_name || computeEntity(this.entity.id)))}
diff --git a/src/entity.ts b/src/entity.ts index 19b3f41d..81ec18db 100755 --- a/src/entity.ts +++ b/src/entity.ts @@ -20,7 +20,6 @@ export function entityConfig(entity_id: string, hass: HomeAssistant, config: Par let output: EntityElement = { id: entity_id, - name: stateObj.attributes.friendly_name || computeEntity(entity_id), icon: DefaultEntityIcon, actions: [], }; diff --git a/src/types.ts b/src/types.ts index 89bd530c..9b17fe93 100644 --- a/src/types.ts +++ b/src/types.ts @@ -60,7 +60,7 @@ export interface GroupConfig { export interface EntityElement extends EntityConfig { id: string; - name: string; + name?: string; icon?: string; actions: ActionConfig[]; exclude_actions?: string[]; From b7a48f6205d9ef3ce843ae29a0180c664989143e Mon Sep 17 00:00:00 2001 From: niels Date: Thu, 22 Oct 2020 09:53:43 +0200 Subject: [PATCH 3/3] improve workday sensor support --- src/const.ts | 1 + src/custom-elements/scheduler-entity-row.ts | 22 ++++++++++++++---- .../scheduler-timepicker-card.ts | 4 ++-- src/date-time.ts | 23 ++++++++++++++----- src/scheduler-card.ts | 2 +- 5 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/const.ts b/src/const.ts index 46c49016..f2b1dfa6 100755 --- a/src/const.ts +++ b/src/const.ts @@ -15,6 +15,7 @@ export const DeadEntityIcon = "help-circle-outline"; export const FieldTemperature = 'temperature'; export const UnitPercent = '%'; export const CreateTimeScheme = 'make_scheme'; +export const WorkdaySensor = 'binary_sensor.workday_sensor'; export const DayOptions = [ { id: 1, name: localize('days_short.mon') }, diff --git a/src/custom-elements/scheduler-entity-row.ts b/src/custom-elements/scheduler-entity-row.ts index 986326ff..f52aeb57 100755 --- a/src/custom-elements/scheduler-entity-row.ts +++ b/src/custom-elements/scheduler-entity-row.ts @@ -1,10 +1,10 @@ import { LitElement, html, customElement, css, property, internalProperty, PropertyValues } from 'lit-element'; import { ImportedEntry, Dictionary, EntityElement, HassAction, ActionElement, CardConfig, ScheduleEntity } from '../types'; -import { parseTimestamp, weekday, MinutesPerHour, daysToArray, ETimeEvent, relativeTime } from '../date-time'; +import { parseTimestamp, weekday, MinutesPerHour, daysToArray, ETimeEvent, relativeTime, EDayType } from '../date-time'; import { PrettyPrintName, capitalize, PrettyPrintIcon } from '../helpers'; import { HomeAssistant, computeEntity, } from 'custom-card-helpers'; import { importEntry } from '../interface'; -import { DefaultEntityIcon, DeadEntityIcon, DeadEntityName, DefaultActionIcon } from '../const'; +import { DefaultEntityIcon, DeadEntityIcon, DeadEntityName, DefaultActionIcon, WorkdaySensor } from '../const'; import { formatAction } from '../formatAction'; import { importAction, findActionIndex, actionConfig, findAction } from '../action'; import { localize } from '../localize/localize'; @@ -96,9 +96,21 @@ export class ScheduleEntityRow extends LitElement { ts.setHours(hours); ts.setMinutes(minutes); - let days = daysToArray(entry.days); - //TBD adjust days for weekday integration! - while (ts.valueOf() <= now.valueOf() || !days.includes(weekday(ts))) { + const workdayEntity = this.hass!.states[WorkdaySensor]; + + function blockByWorkdayEntity(ts: Date) { + if (!workdayEntity) return false; + const start_of_day = new Date(); + start_of_day.setHours(0, 0, 0, 0); + let days_diff = Math.floor((ts.valueOf() - start_of_day.valueOf()) / (24 * 3600 * 1000)); + if (days_diff != 0) return false; + if (entry.days.type == EDayType.Workday) return workdayEntity.state != "on"; + else if (entry.days.type == EDayType.Weekend) return workdayEntity.state == "on"; + return false; + } + + let days = daysToArray(entry.days, workdayEntity); + while (ts.valueOf() <= now.valueOf() || !days.includes(weekday(ts)) || blockByWorkdayEntity(ts)) { ts.setDate(ts.getDate() + 1); } return ts; diff --git a/src/custom-elements/scheduler-timepicker-card.ts b/src/custom-elements/scheduler-timepicker-card.ts index a11bf14b..19fc7dc6 100755 --- a/src/custom-elements/scheduler-timepicker-card.ts +++ b/src/custom-elements/scheduler-timepicker-card.ts @@ -4,7 +4,7 @@ import { localize } from '../localize/localize'; import { CardConfig, ActionElement, EntityElement, EVariableType, LevelVariableConfig, LevelVariable, ListVariable, Entry, ListVariableConfig } from '../types'; import { PrettyPrintIcon, PrettyPrintName, capitalize } from '../helpers'; import { EDayType, daysToArray, ETimeEvent, sortDaylist } from '../date-time'; -import { DayTypeOptions, DayOptions, CreateTimeScheme, DefaultTimeStep, FieldTemperature, DefaultActionIcon } from '../const'; +import { DayTypeOptions, DayOptions, CreateTimeScheme, DefaultTimeStep, FieldTemperature, DefaultActionIcon, WorkdaySensor } from '../const'; import './time-picker'; @@ -279,7 +279,7 @@ export class SchedulerTimepickerCard extends LitElement { const input = (ev.target as HTMLInputElement).value; if (Object.values(EDayType).includes(input as EDayType)) { if (input == EDayType.Custom && !daysCfg.custom_days) - Object.assign(daysCfg, { custom_days: daysToArray(daysCfg) }); + Object.assign(daysCfg, { custom_days: daysToArray(daysCfg, this.hass!.states[WorkdaySensor]) }); Object.assign(daysCfg, { type: input }); } else { Object.assign(daysCfg, { custom_days: [...input] }); diff --git a/src/date-time.ts b/src/date-time.ts index 86d837cc..0ccee459 100755 --- a/src/date-time.ts +++ b/src/date-time.ts @@ -1,4 +1,5 @@ import { localize } from './localize/localize'; +import { HassEntity } from 'home-assistant-js-websocket'; export const MinutesPerHour = 60; export const HoursPerDay = 24; @@ -109,12 +110,22 @@ export function parseTimestamp(input: string | Date): number { return value; } -export function daysToArray(dayCfg: Days) { - if (dayCfg.type == EDayType.Daily) return [1, 2, 3, 4, 5, 6, 7]; - else if (dayCfg.type == EDayType.Workday) return [1, 2, 3, 4, 5]; - else if (dayCfg.type == EDayType.Weekend) return [6, 7]; - else if (dayCfg.type == EDayType.Custom) return dayCfg.custom_days as number[]; - else return []; +export function daysToArray(dayCfg: Days, workday_sensor?: HassEntity) { + if (!workday_sensor) { + if (dayCfg.type == EDayType.Daily) return [1, 2, 3, 4, 5, 6, 7]; + else if (dayCfg.type == EDayType.Workday) return [1, 2, 3, 4, 5]; + else if (dayCfg.type == EDayType.Weekend) return [6, 7]; + else if (dayCfg.type == EDayType.Custom) return dayCfg.custom_days as number[]; + else return []; + } + else { + if (dayCfg.type == EDayType.Daily) return [1, 2, 3, 4, 5, 6, 7]; + else if (dayCfg.type == EDayType.Custom) return dayCfg.custom_days as number[]; + const days_of_the_week = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']; + if (dayCfg.type == EDayType.Workday) return (workday_sensor.attributes.workdays.map(day => days_of_the_week.findIndex(e => e == day) + 1)); + else if (dayCfg.type == EDayType.Weekend) return days_of_the_week.map((e, index) => workday_sensor.attributes.workdays.includes(e) ? null : index + 1).filter(e => e); + else return []; + } } export function stringToTimeEvent(input: string): ETimeEvent { diff --git a/src/scheduler-card.ts b/src/scheduler-card.ts index dd588ae5..d2bb2fa3 100644 --- a/src/scheduler-card.ts +++ b/src/scheduler-card.ts @@ -326,7 +326,7 @@ export class SchedulerCard extends LitElement { if (!this.entity) { const actions = scheduleEntity.attributes.actions - .map(e => importAction(e)) + .map(importAction) .map(e => actionConfig(omit(e, ['entity']) as HassAction)); const entity: EntityElement = {