mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
Direct device binding for ZHA config panel (#2856)
* device binding * review comments * Update zha-binding.ts
This commit is contained in:
parent
57be7ac873
commit
d4be171df9
@ -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
|
||||||
|
@ -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];
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
211
src/panels/config/zha/zha-binding.ts
Normal file
211
src/panels/config/zha/zha-binding.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user