Skip to content

Commit

Permalink
Merge pull request #164 from nielsfaber/improvements
Browse files Browse the repository at this point in the history
Improvements
  • Loading branch information
nielsfaber authored Oct 22, 2020
2 parents e606662 + b7a48f6 commit 3dbf891
Show file tree
Hide file tree
Showing 16 changed files with 136 additions and 94 deletions.
1 change: 1 addition & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') },
Expand Down
9 changes: 5 additions & 4 deletions src/custom-elements/condition-entity-row.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -21,13 +21,14 @@ 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() {
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`
<hui-warning>
Entity not found '${this.item.entity}'
Expand All @@ -39,7 +40,7 @@ export class ConditionEntityRow extends LitElement {
<div class="list-item">
<mwc-button @click="${this.entityButtonClick}" class="${this.selected ? 'active' : ''}">
<ha-icon icon="${PrettyPrintIcon(this.entity.icon || DefaultEntityIcon)}"></ha-icon>
${PrettyPrintName(this.entity.name)}
${PrettyPrintName(this.entity.name || stateObj.attributes.friendly_name || computeEntity(stateObj.entity_id))}
</mwc-button>
${this.getMatchTypeButton()}
${this.getStateButton()}
Expand Down
6 changes: 3 additions & 3 deletions src/custom-elements/scheduler-card-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/custom-elements/scheduler-entities-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(importAction).map(e => e.entity) as string[]).every(e => this._hass!.states[e] && entityFilter(e, this._hass!, this.config!))
);
}

Expand Down Expand Up @@ -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`
Expand Down
32 changes: 22 additions & 10 deletions src/custom-elements/scheduler-entity-row.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -41,16 +41,15 @@ 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;
entityName = entityCfg.name;
icon = actionCfg.icon || entityCfg.icon || DefaultActionIcon;
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;
Expand All @@ -63,7 +62,8 @@ export class ScheduleEntityRow extends LitElement {
>
</state-badge>
<div class="info">
${capitalize(PrettyPrintName(entityName))}: ${capitalize(formatAction(action, this.hass))}
${entityName.length ? `${capitalize(PrettyPrintName(entityName))}: ` : ''}
${capitalize(formatAction(action, this.hass))}
<div class="secondary">
${capitalize(relativeTime(this.computeTimestamp(nextEntry)))}<br>
${entries.length > 1 ? entries.length == 2 ? localize('misc.one_additional_task') : localize("misc.x_additional_tasks", "{count}", String(entries.length - 1)) : ''}
Expand Down Expand Up @@ -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;
Expand Down
14 changes: 7 additions & 7 deletions src/custom-elements/scheduler-entitypicker-card.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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);
Expand All @@ -39,10 +38,11 @@ export class SchedulerEditorCard extends LitElement {

let entities = groupConfig
.find(e => e.id == this.selectedGroup)!.entities
.map(e => entityConfig(this.hass!.states[e], this.config!))
.filter(e => e) as EntityElement[];
.map(e => entityConfig(e, this.hass!, this.config!))
.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;
}

Expand Down
16 changes: 8 additions & 8 deletions src/custom-elements/scheduler-options-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -105,18 +105,18 @@ 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!))
.filter(e => e) as EntityElement[];
entities.sort((a, b) => a.name.trim().toLowerCase() < b.name.trim().toLowerCase() ? -1 : 1);
.map(e => entityConfig(e, this.hass!, this.config!))
.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`
Expand Down Expand Up @@ -199,7 +199,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 = {
Expand Down
11 changes: 5 additions & 6 deletions src/custom-elements/scheduler-timepicker-card.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
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';
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';
Expand Down Expand Up @@ -55,7 +55,7 @@ export class SchedulerTimepickerCard extends LitElement {
<div class="summary-entity">
<ha-icon icon="${PrettyPrintIcon(this.entity.icon)}">
</ha-icon>
${capitalize(PrettyPrintName(this.entity.name))}
${capitalize(PrettyPrintName(this.entity.name || this.hass!.states[this.entity.id].attributes.friendly_name || computeEntity(this.entity.id)))}
</div>
<div class="summary-arrow">
<ha-icon icon="hass:arrow-right">
Expand Down Expand Up @@ -114,7 +114,7 @@ export class SchedulerTimepickerCard extends LitElement {
<div class="summary-entity">
<ha-icon icon="${PrettyPrintIcon(this.entity.icon)}">
</ha-icon>
${capitalize(PrettyPrintName(this.entity.name))}
${capitalize(PrettyPrintName(this.entity.name || this.hass!.states[this.entity.id].attributes.friendly_name || computeEntity(this.entity.id)))}
</div>
<div class="summary-arrow">
<ha-icon icon="hass:arrow-right">
Expand Down Expand Up @@ -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 } });
Expand Down Expand Up @@ -280,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] });
Expand Down
23 changes: 17 additions & 6 deletions src/date-time.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { localize } from './localize/localize';
import { HassEntity } from 'home-assistant-js-websocket';

export const MinutesPerHour = 60;
export const HoursPerDay = 24;
Expand Down Expand Up @@ -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 {
Expand Down
24 changes: 10 additions & 14 deletions src/entity.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -14,21 +14,20 @@ export function IsSchedulerEntity(entity_id: string) {
return entity_id.match(/^switch.schedule_[0-9a-f]{6}$/);
}

export function entityConfig(entity: HassEntity | undefined, config: Partial<CardConfig>) {
if (!entity) return;
const entity_id = typeof entity == "string" ? entity : entity.entity_id;
export function entityConfig(entity_id: string, hass: HomeAssistant, config: Partial<CardConfig>) {
const stateObj = hass.states[entity_id];
if (!stateObj) return;

let output: EntityElement = {
id: entity_id,
name: entity.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)
Expand Down Expand Up @@ -57,17 +56,14 @@ export function entityConfig(entity: HassEntity | undefined, config: Partial<Car
return output;
}

export function entityFilter(
entity: HassEntity | string,
config: { include?: string[]; exclude?: string[]; customize?: Dictionary<EntityConfig>; 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;
Expand Down
Loading

0 comments on commit 3dbf891

Please sign in to comment.