Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose entities for Google/Alexa #680

Merged
merged 7 commits into from
Nov 6, 2018
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Show exposed entities on cloud panel
balloob committed Nov 6, 2018
commit b7fae79ea8a4a87a7a7986265a5f819d6ecd7019
62 changes: 0 additions & 62 deletions src/common/entity/entity_filter.js

This file was deleted.

64 changes: 64 additions & 0 deletions src/common/entity/entity_filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import computeDomain from "./compute_domain.js";

export type FilterFunc = (entityId: string) => boolean;

export const generateFilter = (
includeDomains: string[],
includeEntities: string[],
excludeDomains: string[],
excludeEntities: string[]
): FilterFunc => {
const includeDomainsSet = new Set(includeDomains);
const includeEntitiesSet = new Set(includeEntities);
const excludeDomainsSet = new Set(excludeDomains);
const excludeEntitiesSet = new Set(excludeEntities);

const haveInclude = includeDomainsSet.size > 0 || includeEntitiesSet.size > 0;
const haveExclude = excludeDomainsSet.size > 0 || excludeEntitiesSet.size > 0;

// Case 1 - no includes or excludes - pass all entities
if (!haveInclude && !haveExclude) {
return () => true;
}

// Case 2 - includes, no excludes - only include specified entities
if (haveInclude && !haveExclude) {
return (entityId) =>
includeEntitiesSet.has(entityId) ||
includeDomainsSet.has(computeDomain(entityId));
}

// Case 3 - excludes, no includes - only exclude specified entities
if (!haveInclude && haveExclude) {
return (entityId) =>
!excludeEntitiesSet.has(entityId) &&
!excludeDomainsSet.has(computeDomain(entityId));
}

// Case 4 - both includes and excludes specified
// Case 4a - include domain specified
// - if domain is included, and entity not excluded, pass
// - if domain is not included, and entity not included, fail
// note: if both include and exclude domains specified,
// the exclude domains are ignored
if (includeDomainsSet.size) {
return (entityId) =>
includeDomainsSet.has(computeDomain(entityId))
? !excludeEntitiesSet.has(entityId)
: includeEntitiesSet.has(entityId);
}

// Case 4b - exclude domain specified
// - if domain is excluded, and entity not included, fail
// - if domain is not excluded, and entity not excluded, pass
if (excludeDomainsSet.size) {
return (entityId) =>
excludeDomainsSet.has(computeDomain(entityId))
? includeEntitiesSet.has(entityId)
: !excludeEntitiesSet.has(entityId);
}

// Case 4c - neither include or exclude domain specified
// - Only pass if entity is included. Ignore entity excludes.
return (entityId) => includeEntitiesSet.has(entityId);
};
17 changes: 16 additions & 1 deletion src/panels/config/cloud/cloud-alexa-pref.ts
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { HomeAssistant } from "../../../types";
import { updatePref } from "./data";
import { CloudStatusLoggedIn } from "./types";
import "./cloud-exposed-entities";

export class CloudAlexaPref extends LitElement {
public hass?: HomeAssistant;
@@ -23,11 +24,13 @@ export class CloudAlexaPref extends LitElement {
}

protected render(): TemplateResult {
const enabled = this.cloudStatus!.alexa_enabled;

return html`
${this.renderStyle()}
<paper-card heading="Alexa">
<paper-toggle-button
.checked="${this.cloudStatus!.alexa_enabled}"
.checked="${enabled}"
@change="${this._toggleChanged}"
></paper-toggle-button>
<div class="card-content">
@@ -43,6 +46,18 @@ export class CloudAlexaPref extends LitElement {
</li>
</ul>
<em>This integration requires an Alexa-enabled device like the Amazon Echo.</em>
${
enabled
? html`
<p>Exposed entities:</p>
<cloud-exposed-entities
.hass="${this.hass}"
.filter="${this.cloudStatus!.alexa_entities}"
.supportedDomains="${this.cloudStatus!.alexa_domains}"
></cloud-exposed-entities>
`
: ""
}
</div>
</paper-card>
`;
115 changes: 115 additions & 0 deletions src/panels/config/cloud/cloud-exposed-entities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {
html,
LitElement,
PropertyDeclarations,
PropertyValues,
} from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import { repeat } from "lit-html/directives/repeat";
import "@polymer/paper-tooltip/paper-tooltip";
import { HassEntityBase } from "home-assistant-js-websocket";
import "../../../components/entity/ha-state-icon";

import { fireEvent } from "../../../common/dom/fire_event";
import { HomeAssistant } from "../../../types";
import { EntityFilter } from "./types";
import computeStateName from "../../../common/entity/compute_state_name";
import {
FilterFunc,
generateFilter,
} from "../../../common/entity/entity_filter";

export class CloudExposedEntities extends LitElement {
public hass?: HomeAssistant;
public filter?: EntityFilter;
public supportedDomains?: string[];
private _filterFunc?: FilterFunc;

static get properties(): PropertyDeclarations {
return {
hass: {},
filter: {},
supportedDomains: {},
_filterFunc: {},
};
}

protected render(): TemplateResult {
if (!this._filterFunc) {
return html``;
}

const states: Array<[string, HassEntityBase]> = [];

Object.keys(this.hass!.states).forEach((entityId) => {
if (this._filterFunc!(entityId)) {
const stateObj = this.hass!.states[entityId];
states.push([computeStateName(stateObj), stateObj]);
}
});
states.sort();

return html`
${this.renderStyle()}
${repeat(
states!,
(stateInfo) => stateInfo[1].entity_id,
(stateInfo) => html`
<span>
<ha-state-icon
.stateObj='${stateInfo[1]}'
@click='${this._handleMoreInfo}'
></ha-state-icon>
<paper-tooltip
position="bottom"
>${stateInfo[0]}</paper-tooltip>
</span>
`
)}
`;
}

protected updated(changedProperties: PropertyValues) {
if (
changedProperties.has("filter") &&
changedProperties.get("filter") !== this.filter
) {
const filter = this.filter!;
const filterFunc = generateFilter(
filter.include_domains,
filter.include_entities,
filter.exclude_domains,
filter.exclude_entities
);
const domains = new Set(this.supportedDomains);
this._filterFunc = (entityId: string) => {
const domain = entityId.split(".")[0];
return domains.has(domain) && filterFunc(entityId);
};
}
}

private _handleMoreInfo(ev: MouseEvent) {
fireEvent(this, "hass-more-info", {
entityId: (ev.currentTarget as any).stateObj.entity_id,
});
}

private renderStyle(): TemplateResult {
return html`
<style>
ha-state-icon {
color: var(--primary-text-color);
}
</style>
`;
}
}

declare global {
interface HTMLElementTagNameMap {
"cloud-exposed-entities": CloudExposedEntities;
}
}

customElements.define("cloud-exposed-entities", CloudExposedEntities);
19 changes: 17 additions & 2 deletions src/panels/config/cloud/cloud-google-pref.ts
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { HomeAssistant } from "../../../types";
import { updatePref } from "./data";
import { CloudStatusLoggedIn } from "./types";
import "./cloud-exposed-entities";

export class CloudGooglePref extends LitElement {
public hass?: HomeAssistant;
@@ -24,11 +25,13 @@ export class CloudGooglePref extends LitElement {
}

protected render(): TemplateResult {
const enabled = this.cloudStatus!.google_enabled;

return html`
${this.renderStyle()}
<paper-card heading="Google Assistant">
<paper-toggle-button
.checked="${this.cloudStatus!.google_enabled}"
.checked="${enabled}"
@change="${this._toggleChanged}"
></paper-toggle-button>
<div class="card-content">
@@ -46,11 +49,23 @@ export class CloudGooglePref extends LitElement {
</li>
</ul>
<em>This integration requires a Google Assistant-enabled device like the Google Home or Android phone.</em>
${
enabled
? html`
<p>Exposed entities:</p>
<cloud-exposed-entities
.hass="${this.hass}"
.filter="${this.cloudStatus!.google_entities}"
.supportedDomains="${this.cloudStatus!.google_domains}"
></cloud-exposed-entities>
`
: ""
}
</div>
<div class="card-actions">
<ha-call-api-button
.hass="${this.hass}"
.disabled="${!this.cloudStatus!.google_enabled}"
.disabled="${!enabled}"
path="cloud/google_actions/sync"
>Sync devices</ha-call-api-button>
</div>
4 changes: 3 additions & 1 deletion src/panels/config/cloud/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
interface EntityFilter {
export interface EntityFilter {
include_domains: string[];
include_entities: string[];
exclude_domains: string[];
@@ -13,8 +13,10 @@ export type CloudStatusLoggedIn = CloudStatusBase & {
email: string;
google_enabled: boolean;
google_entities: EntityFilter;
google_domains: string[];
alexa_enabled: boolean;
alexa_entities: EntityFilter;
alexa_domains: string[];
};

export type CloudStatus = CloudStatusBase | CloudStatusLoggedIn;