Direct device binding for ZHA config panel (#2856)

* device binding

* review comments

* Update zha-binding.ts
This commit is contained in:
David F. Mulcahey 2019-03-07 13:53:06 -05:00 committed by Paulus Schoutsen
parent 57be7ac873
commit d4be171df9
4 changed files with 287 additions and 30 deletions

View File

@ -1,12 +1,6 @@
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
export interface ZHADeviceEntity extends HassEntity {
device_info?: {
identifiers: any[];
};
}
export interface ZHAEntityReference extends HassEntity { export interface ZHAEntityReference extends HassEntity {
name: string; name: string;
} }
@ -78,6 +72,37 @@ export const fetchDevices = (hass: HomeAssistant): Promise<ZHADevice[]> =>
type: "zha/devices", type: "zha/devices",
}); });
export const fetchBindableDevices = (
hass: HomeAssistant,
ieeeAddress: string
): Promise<ZHADevice[]> =>
hass.callWS({
type: "zha/devices/bindable",
ieee: ieeeAddress,
});
export const bindDevices = (
hass: HomeAssistant,
sourceIEEE: string,
targetIEEE: string
): Promise<void> =>
hass.callWS({
type: "zha/devices/bind",
source_ieee: sourceIEEE,
target_ieee: targetIEEE,
});
export const unbindDevices = (
hass: HomeAssistant,
sourceIEEE: string,
targetIEEE: string
): Promise<void> =>
hass.callWS({
type: "zha/devices/unbind",
source_ieee: sourceIEEE,
target_ieee: targetIEEE,
});
export const readAttributeValue = ( export const readAttributeValue = (
hass: HomeAssistant, hass: HomeAssistant,
data: ReadAttributeServiceData data: ReadAttributeServiceData

View File

@ -3,37 +3,37 @@ import "@polymer/app-layout/app-toolbar/app-toolbar";
import { import {
html, html,
LitElement, LitElement,
PropertyDeclarations, property,
PropertyValues,
TemplateResult, TemplateResult,
CSSResult, CSSResult,
} from "lit-element"; } from "lit-element";
import "@polymer/paper-icon-button/paper-icon-button"; import "@polymer/paper-icon-button/paper-icon-button";
import { HassEntity } from "home-assistant-js-websocket";
import { HASSDomEvent } from "../../../common/dom/fire_event"; import { HASSDomEvent } from "../../../common/dom/fire_event";
import { Cluster } from "../../../data/zha"; import { Cluster, ZHADevice, fetchBindableDevices } from "../../../data/zha";
import "../../../layouts/ha-app-layout"; import "../../../layouts/ha-app-layout";
import "../../../components/ha-paper-icon-button-arrow-prev"; import "../../../components/ha-paper-icon-button-arrow-prev";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { ZHAClusterSelectedParams, ZHANodeSelectedParams } from "./types"; import { ZHAClusterSelectedParams, ZHADeviceSelectedParams } from "./types";
import "./zha-cluster-attributes"; import "./zha-cluster-attributes";
import "./zha-cluster-commands"; import "./zha-cluster-commands";
import "./zha-network"; import "./zha-network";
import "./zha-node"; import "./zha-node";
import "./zha-binding";
export class HaConfigZha extends LitElement { export class HaConfigZha extends LitElement {
public hass?: HomeAssistant; @property() public hass?: HomeAssistant;
public isWide?: boolean; @property() public isWide?: boolean;
private _selectedNode?: HassEntity; @property() private _selectedDevice?: ZHADevice;
private _selectedCluster?: Cluster; @property() private _selectedCluster?: Cluster;
@property() private _bindableDevices: ZHADevice[] = [];
static get properties(): PropertyDeclarations { protected updated(changedProperties: PropertyValues): void {
return { if (changedProperties.has("_selectedDevice")) {
hass: {}, this._fetchBindableDevices();
isWide: {}, }
_selectedCluster: {}, super.update(changedProperties);
_selectedNode: {},
};
} }
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
@ -57,25 +57,35 @@ export class HaConfigZha extends LitElement {
.isWide="${this.isWide}" .isWide="${this.isWide}"
.hass="${this.hass}" .hass="${this.hass}"
@zha-cluster-selected="${this._onClusterSelected}" @zha-cluster-selected="${this._onClusterSelected}"
@zha-node-selected="${this._onNodeSelected}" @zha-node-selected="${this._onDeviceSelected}"
></zha-node> ></zha-node>
${this._selectedCluster ${this._selectedCluster
? html` ? html`
<zha-cluster-attributes <zha-cluster-attributes
.isWide="${this.isWide}" .isWide="${this.isWide}"
.hass="${this.hass}" .hass="${this.hass}"
.selectedNode="${this._selectedNode}" .selectedNode="${this._selectedDevice}"
.selectedCluster="${this._selectedCluster}" .selectedCluster="${this._selectedCluster}"
></zha-cluster-attributes> ></zha-cluster-attributes>
<zha-cluster-commands <zha-cluster-commands
.isWide="${this.isWide}" .isWide="${this.isWide}"
.hass="${this.hass}" .hass="${this.hass}"
.selectedNode="${this._selectedNode}" .selectedNode="${this._selectedDevice}"
.selectedCluster="${this._selectedCluster}" .selectedCluster="${this._selectedCluster}"
></zha-cluster-commands> ></zha-cluster-commands>
` `
: ""} : ""}
${this._selectedDevice && this._bindableDevices.length > 0
? html`
<zha-binding-control
.isWide="${this.isWide}"
.hass="${this.hass}"
.selectedDevice="${this._selectedDevice}"
.bindableDevices="${this._bindableDevices}"
></zha-binding-control>
`
: ""}
</ha-app-layout> </ha-app-layout>
`; `;
} }
@ -86,13 +96,24 @@ export class HaConfigZha extends LitElement {
this._selectedCluster = selectedClusterEvent.detail.cluster; this._selectedCluster = selectedClusterEvent.detail.cluster;
} }
private _onNodeSelected( private _onDeviceSelected(
selectedNodeEvent: HASSDomEvent<ZHANodeSelectedParams> selectedNodeEvent: HASSDomEvent<ZHADeviceSelectedParams>
): void { ): void {
this._selectedNode = selectedNodeEvent.detail.node; this._selectedDevice = selectedNodeEvent.detail.node;
this._selectedCluster = undefined; this._selectedCluster = undefined;
} }
private async _fetchBindableDevices(): Promise<void> {
if (this._selectedDevice && this.hass) {
this._bindableDevices = (await fetchBindableDevices(
this.hass,
this._selectedDevice!.ieee
)).sort((a, b) => {
return a.name.localeCompare(b.name);
});
}
}
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [haStyle]; return [haStyle];
} }

View File

@ -1,4 +1,4 @@
import { ZHADeviceEntity, Cluster } from "../../../data/zha"; import { ZHADevice, Cluster } from "../../../data/zha";
export interface PickerTarget extends EventTarget { export interface PickerTarget extends EventTarget {
selected: number; selected: number;
@ -34,8 +34,8 @@ export interface IssueCommandServiceData {
command_type: string; command_type: string;
} }
export interface ZHANodeSelectedParams { export interface ZHADeviceSelectedParams {
node: ZHADeviceEntity; node: ZHADevice;
} }
export interface ZHAClusterSelectedParams { export interface ZHAClusterSelectedParams {

View File

@ -0,0 +1,211 @@
import {
html,
LitElement,
property,
PropertyValues,
TemplateResult,
CSSResult,
css,
customElement,
} from "lit-element";
import "@polymer/paper-card/paper-card";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import { ZHADevice, bindDevices, unbindDevices } from "../../../data/zha";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
import { ItemSelectedEvent } from "./types";
@customElement("zha-binding-control")
export class ZHABindingControl extends LitElement {
@property() public hass?: HomeAssistant;
@property() public isWide?: boolean;
@property() public selectedDevice?: ZHADevice;
@property() private _showHelp: boolean = false;
@property() private _bindTargetIndex: number = -1;
@property() private bindableDevices: ZHADevice[] = [];
private _deviceToBind?: ZHADevice;
protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("selectedDevice")) {
this._bindTargetIndex = -1;
}
super.update(changedProperties);
}
protected render(): TemplateResult | void {
return html`
<ha-config-section .isWide="${this.isWide}">
<div class="sectionHeader" slot="header">
<span>Device Binding</span>
<paper-icon-button
class="toggle-help-icon"
@click="${this._onHelpTap}"
icon="hass:help-circle"
>
</paper-icon-button>
</div>
<span slot="introduction">Bind and unbind devices.</span>
<paper-card class="content">
<div class="command-picker">
<paper-dropdown-menu label="Bindable Devices" class="flex">
<paper-listbox
slot="dropdown-content"
.selected="${this._bindTargetIndex}"
@iron-select="${this._bindTargetIndexChanged}"
>
${this.bindableDevices.map(
(device) => html`
<paper-item>${device.name}</paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu>
</div>
${this._showHelp
? html`
<div class="helpText">
Select a device to issue a bind command.
</div>
`
: ""}
<div class="card-actions">
<mwc-button @click="${this._onBindDevicesClick}">Bind</mwc-button>
${this._showHelp
? html`
<div class="helpText">
Bind devices.
</div>
`
: ""}
<mwc-button @click="${this._onUnbindDevicesClick}"
>Unbind</mwc-button
>
${this._showHelp
? html`
<div class="helpText">
Unbind devices.
</div>
`
: ""}
</div>
</paper-card>
</ha-config-section>
`;
}
private _bindTargetIndexChanged(event: ItemSelectedEvent): void {
this._bindTargetIndex = event.target!.selected;
this._deviceToBind =
this._bindTargetIndex === -1
? undefined
: this.bindableDevices[this._bindTargetIndex];
}
private _onHelpTap(): void {
this._showHelp = !this._showHelp;
}
private async _onBindDevicesClick(): Promise<void> {
if (this.hass && this._deviceToBind && this.selectedDevice) {
await bindDevices(
this.hass,
this.selectedDevice.ieee,
this._deviceToBind.ieee
);
}
}
private async _onUnbindDevicesClick(): Promise<void> {
if (this.hass && this._deviceToBind && this.selectedDevice) {
await unbindDevices(
this.hass,
this.selectedDevice.ieee,
this._deviceToBind.ieee
);
}
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
.flex {
-ms-flex: 1 1 0.000000001px;
-webkit-flex: 1;
flex: 1;
-webkit-flex-basis: 0.000000001px;
flex-basis: 0.000000001px;
}
.content {
margin-top: 24px;
}
paper-card {
display: block;
margin: 0 auto;
max-width: 600px;
}
.card-actions.warning ha-call-service-button {
color: var(--google-red-500);
}
.command-picker {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-ms-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.input-text {
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.sectionHeader {
position: relative;
}
.helpText {
color: grey;
padding: 16px;
}
.toggle-help-icon {
position: absolute;
top: -6px;
right: 0;
color: var(--primary-color);
}
ha-service-description {
display: block;
color: grey;
}
[hidden] {
display: none;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-binding-control": ZHABindingControl;
}
}