Make ZHA config panel device oriented (#2722)

* change to be zha device centric instead of entity centric

* clusters by device

* device centric API

* lit ts and cleanup

* type

* review comments and fix remove

* fix set attribute
This commit is contained in:
David F. Mulcahey 2019-02-12 14:55:42 -05:00 committed by Paulus Schoutsen
parent abbfea0b6a
commit 3a644621fe
10 changed files with 253 additions and 323 deletions

View File

@ -7,8 +7,19 @@ export interface ZHADeviceEntity extends HassEntity {
}; };
} }
export interface ZHAEntities { export interface ZHAEntityReference extends HassEntity {
[key: string]: HassEntity[]; name: string;
}
export interface ZHADevice {
name: string;
ieee: string;
manufacturer: string;
model: string;
quirk_applied: boolean;
quirk_class: string;
entities: ZHAEntityReference[];
manufacturer_code: number;
} }
export interface Attribute { export interface Attribute {
@ -19,6 +30,7 @@ export interface Attribute {
export interface Cluster { export interface Cluster {
name: string; name: string;
id: number; id: number;
endpoint_id: number;
type: string; type: string;
} }
@ -29,7 +41,8 @@ export interface Command {
} }
export interface ReadAttributeServiceData { export interface ReadAttributeServiceData {
entity_id: string; ieee: string;
endpoint_id: number;
cluster_id: number; cluster_id: number;
cluster_type: string; cluster_type: string;
attribute: number; attribute: number;
@ -41,64 +54,60 @@ export const reconfigureNode = (
ieeeAddress: string ieeeAddress: string
): Promise<void> => ): Promise<void> =>
hass.callWS({ hass.callWS({
type: "zha/nodes/reconfigure", type: "zha/devices/reconfigure",
ieee: ieeeAddress, ieee: ieeeAddress,
}); });
export const fetchAttributesForCluster = ( export const fetchAttributesForCluster = (
hass: HomeAssistant, hass: HomeAssistant,
entityId: string,
ieeeAddress: string, ieeeAddress: string,
endpointId: number,
clusterId: number, clusterId: number,
clusterType: string clusterType: string
): Promise<Attribute[]> => ): Promise<Attribute[]> =>
hass.callWS({ hass.callWS({
type: "zha/entities/clusters/attributes", type: "zha/devices/clusters/attributes",
entity_id: entityId,
ieee: ieeeAddress, ieee: ieeeAddress,
endpoint_id: endpointId,
cluster_id: clusterId, cluster_id: clusterId,
cluster_type: clusterType, cluster_type: clusterType,
}); });
export const fetchDevices = (hass: HomeAssistant): Promise<ZHADevice[]> =>
hass.callWS({
type: "zha/devices",
});
export const readAttributeValue = ( export const readAttributeValue = (
hass: HomeAssistant, hass: HomeAssistant,
data: ReadAttributeServiceData data: ReadAttributeServiceData
): Promise<string> => { ): Promise<string> => {
return hass.callWS({ return hass.callWS({
...data, ...data,
type: "zha/entities/clusters/attributes/value", type: "zha/devices/clusters/attributes/value",
}); });
}; };
export const fetchCommandsForCluster = ( export const fetchCommandsForCluster = (
hass: HomeAssistant, hass: HomeAssistant,
entityId: string,
ieeeAddress: string, ieeeAddress: string,
endpointId: number,
clusterId: number, clusterId: number,
clusterType: string clusterType: string
): Promise<Command[]> => ): Promise<Command[]> =>
hass.callWS({ hass.callWS({
type: "zha/entities/clusters/commands", type: "zha/devices/clusters/commands",
entity_id: entityId,
ieee: ieeeAddress, ieee: ieeeAddress,
endpoint_id: endpointId,
cluster_id: clusterId, cluster_id: clusterId,
cluster_type: clusterType, cluster_type: clusterType,
}); });
export const fetchClustersForZhaNode = ( export const fetchClustersForZhaNode = (
hass: HomeAssistant, hass: HomeAssistant,
entityId: string,
ieeeAddress: string ieeeAddress: string
): Promise<Cluster[]> => ): Promise<Cluster[]> =>
hass.callWS({ hass.callWS({
type: "zha/entities/clusters", type: "zha/devices/clusters",
entity_id: entityId,
ieee: ieeeAddress, ieee: ieeeAddress,
}); });
export const fetchEntitiesForZhaNode = (
hass: HomeAssistant
): Promise<ZHAEntities> =>
hass.callWS({
type: "zha/entities",
});

View File

@ -14,11 +14,7 @@ import { Cluster } from "../../../data/zha";
import "../../../layouts/ha-app-layout"; import "../../../layouts/ha-app-layout";
import { haStyle } from "../../../resources/ha-style"; import { haStyle } from "../../../resources/ha-style";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { import { ZHAClusterSelectedParams, ZHANodeSelectedParams } from "./types";
ZHAClusterSelectedParams,
ZHAEntitySelectedParams,
ZHANodeSelectedParams,
} from "./types";
import "./zha-cluster-attributes"; import "./zha-cluster-attributes";
import "./zha-cluster-commands"; import "./zha-cluster-commands";
import "./zha-network"; import "./zha-network";
@ -29,14 +25,12 @@ export class HaConfigZha extends LitElement {
public isWide?: boolean; public isWide?: boolean;
private _selectedNode?: HassEntity; private _selectedNode?: HassEntity;
private _selectedCluster?: Cluster; private _selectedCluster?: Cluster;
private _selectedEntity?: HassEntity;
static get properties(): PropertyDeclarations { static get properties(): PropertyDeclarations {
return { return {
hass: {}, hass: {},
isWide: {}, isWide: {},
_selectedCluster: {}, _selectedCluster: {},
_selectedEntity: {},
_selectedNode: {}, _selectedNode: {},
}; };
} }
@ -64,7 +58,6 @@ export class HaConfigZha extends LitElement {
.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._onNodeSelected}"
@zha-entity-selected="${this._onEntitySelected}"
></zha-node> ></zha-node>
${this._selectedCluster ${this._selectedCluster
? html` ? html`
@ -72,7 +65,6 @@ export class HaConfigZha extends LitElement {
.isWide="${this.isWide}" .isWide="${this.isWide}"
.hass="${this.hass}" .hass="${this.hass}"
.selectedNode="${this._selectedNode}" .selectedNode="${this._selectedNode}"
.selectedEntity="${this._selectedEntity}"
.selectedCluster="${this._selectedCluster}" .selectedCluster="${this._selectedCluster}"
></zha-cluster-attributes> ></zha-cluster-attributes>
@ -80,7 +72,6 @@ export class HaConfigZha extends LitElement {
.isWide="${this.isWide}" .isWide="${this.isWide}"
.hass="${this.hass}" .hass="${this.hass}"
.selectedNode="${this._selectedNode}" .selectedNode="${this._selectedNode}"
.selectedEntity="${this._selectedEntity}"
.selectedCluster="${this._selectedCluster}" .selectedCluster="${this._selectedCluster}"
></zha-cluster-commands> ></zha-cluster-commands>
` `
@ -100,13 +91,6 @@ export class HaConfigZha extends LitElement {
): void { ): void {
this._selectedNode = selectedNodeEvent.detail.node; this._selectedNode = selectedNodeEvent.detail.node;
this._selectedCluster = undefined; this._selectedCluster = undefined;
this._selectedEntity = undefined;
}
private _onEntitySelected(
selectedEntityEvent: HASSDomEvent<ZHAEntitySelectedParams>
): void {
this._selectedEntity = selectedEntityEvent.detail.entity;
} }
static get styles(): CSSResult[] { static get styles(): CSSResult[] {

View File

@ -1,4 +1,3 @@
import { HassEntity } from "home-assistant-js-websocket";
import { ZHADeviceEntity, Cluster } from "../../../data/zha"; import { ZHADeviceEntity, Cluster } from "../../../data/zha";
export interface PickerTarget extends EventTarget { export interface PickerTarget extends EventTarget {
@ -17,7 +16,8 @@ export interface ChangeEvent {
} }
export interface SetAttributeServiceData { export interface SetAttributeServiceData {
entity_id: string; ieee: string;
endpoint_id: number;
cluster_id: number; cluster_id: number;
cluster_type: string; cluster_type: string;
attribute: number; attribute: number;
@ -26,17 +26,14 @@ export interface SetAttributeServiceData {
} }
export interface IssueCommandServiceData { export interface IssueCommandServiceData {
entity_id: string; ieee: string;
endpoint_id: number;
cluster_id: number; cluster_id: number;
cluster_type: string; cluster_type: string;
command: number; command: number;
command_type: string; command_type: string;
} }
export interface ZHAEntitySelectedParams {
entity: HassEntity;
}
export interface ZHANodeSelectedParams { export interface ZHANodeSelectedParams {
node: ZHADeviceEntity; node: ZHADeviceEntity;
} }

View File

@ -10,7 +10,6 @@ import {
import "@polymer/paper-button/paper-button"; import "@polymer/paper-button/paper-button";
import "@polymer/paper-card/paper-card"; import "@polymer/paper-card/paper-card";
import "@polymer/paper-icon-button/paper-icon-button"; import "@polymer/paper-icon-button/paper-icon-button";
import { HassEntity } from "home-assistant-js-websocket";
import "../../../components/buttons/ha-call-service-button"; import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description"; import "../../../components/ha-service-description";
import { import {
@ -19,7 +18,7 @@ import {
fetchAttributesForCluster, fetchAttributesForCluster,
ReadAttributeServiceData, ReadAttributeServiceData,
readAttributeValue, readAttributeValue,
ZHADeviceEntity, ZHADevice,
} from "../../../data/zha"; } from "../../../data/zha";
import { haStyle } from "../../../resources/ha-style"; import { haStyle } from "../../../resources/ha-style";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
@ -34,8 +33,7 @@ export class ZHAClusterAttributes extends LitElement {
public hass?: HomeAssistant; public hass?: HomeAssistant;
public isWide?: boolean; public isWide?: boolean;
public showHelp: boolean; public showHelp: boolean;
public selectedNode?: HassEntity; public selectedNode?: ZHADevice;
public selectedEntity?: ZHADeviceEntity;
public selectedCluster?: Cluster; public selectedCluster?: Cluster;
private _attributes: Attribute[]; private _attributes: Attribute[];
private _selectedAttributeIndex: number; private _selectedAttributeIndex: number;
@ -57,7 +55,6 @@ export class ZHAClusterAttributes extends LitElement {
isWide: {}, isWide: {},
showHelp: {}, showHelp: {},
selectedNode: {}, selectedNode: {},
selectedEntity: {},
selectedCluster: {}, selectedCluster: {},
_attributes: {}, _attributes: {},
_selectedAttributeIndex: {}, _selectedAttributeIndex: {},
@ -172,49 +169,54 @@ export class ZHAClusterAttributes extends LitElement {
} }
private async _fetchAttributesForCluster(): Promise<void> { private async _fetchAttributesForCluster(): Promise<void> {
if (this.selectedEntity && this.selectedCluster && this.hass) { if (this.selectedNode && this.selectedCluster && this.hass) {
this._attributes = await fetchAttributesForCluster( this._attributes = await fetchAttributesForCluster(
this.hass, this.hass,
this.selectedEntity!.entity_id, this.selectedNode!.ieee,
this.selectedEntity!.device_info!.identifiers[0][1], this.selectedCluster!.endpoint_id,
this.selectedCluster!.id, this.selectedCluster!.id,
this.selectedCluster!.type this.selectedCluster!.type
); );
this._attributes.sort((a, b) => {
return a.name.localeCompare(b.name);
});
} }
} }
private _computeReadAttributeServiceData(): private _computeReadAttributeServiceData():
| ReadAttributeServiceData | ReadAttributeServiceData
| undefined { | undefined {
if (!this.selectedEntity || !this.selectedCluster || !this.selectedNode) { if (!this.selectedCluster || !this.selectedNode) {
return; return;
} }
return { return {
entity_id: this.selectedEntity!.entity_id, ieee: this.selectedNode!.ieee,
endpoint_id: this.selectedCluster!.endpoint_id,
cluster_id: this.selectedCluster!.id, cluster_id: this.selectedCluster!.id,
cluster_type: this.selectedCluster!.type, cluster_type: this.selectedCluster!.type,
attribute: this._attributes[this._selectedAttributeIndex].id, attribute: this._attributes[this._selectedAttributeIndex].id,
manufacturer: this._manufacturerCodeOverride manufacturer: this._manufacturerCodeOverride
? parseInt(this._manufacturerCodeOverride as string, 10) ? parseInt(this._manufacturerCodeOverride as string, 10)
: this.selectedNode!.attributes.manufacturer_code, : this.selectedNode!.manufacturer_code,
}; };
} }
private _computeSetAttributeServiceData(): private _computeSetAttributeServiceData():
| SetAttributeServiceData | SetAttributeServiceData
| undefined { | undefined {
if (!this.selectedEntity || !this.selectedCluster || !this.selectedNode) { if (!this.selectedCluster || !this.selectedNode) {
return; return;
} }
return { return {
entity_id: this.selectedEntity!.entity_id, ieee: this.selectedNode!.ieee,
endpoint_id: this.selectedCluster!.endpoint_id,
cluster_id: this.selectedCluster!.id, cluster_id: this.selectedCluster!.id,
cluster_type: this.selectedCluster!.type, cluster_type: this.selectedCluster!.type,
attribute: this._attributes[this._selectedAttributeIndex].id, attribute: this._attributes[this._selectedAttributeIndex].id,
value: this._attributeValue, value: this._attributeValue,
manufacturer: this._manufacturerCodeOverride manufacturer: this._manufacturerCodeOverride
? parseInt(this._manufacturerCodeOverride as string, 10) ? parseInt(this._manufacturerCodeOverride as string, 10)
: this.selectedNode!.attributes.manufacturer_code, : this.selectedNode!.manufacturer_code,
}; };
} }
@ -306,8 +308,7 @@ export class ZHAClusterAttributes extends LitElement {
[hidden] { [hidden] {
display: none; display: none;
} }
</style> `,
`,
]; ];
} }
} }

View File

@ -8,14 +8,13 @@ import {
css, css,
} from "lit-element"; } from "lit-element";
import "@polymer/paper-card/paper-card"; import "@polymer/paper-card/paper-card";
import { HassEntity } from "home-assistant-js-websocket";
import "../../../components/buttons/ha-call-service-button"; import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description"; import "../../../components/ha-service-description";
import { import {
Cluster, Cluster,
Command, Command,
fetchCommandsForCluster, fetchCommandsForCluster,
ZHADeviceEntity, ZHADevice,
} from "../../../data/zha"; } from "../../../data/zha";
import { haStyle } from "../../../resources/ha-style"; import { haStyle } from "../../../resources/ha-style";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
@ -29,8 +28,7 @@ import {
export class ZHAClusterCommands extends LitElement { export class ZHAClusterCommands extends LitElement {
public hass?: HomeAssistant; public hass?: HomeAssistant;
public isWide?: boolean; public isWide?: boolean;
public selectedNode?: HassEntity; public selectedNode?: ZHADevice;
public selectedEntity?: ZHADeviceEntity;
public selectedCluster?: Cluster; public selectedCluster?: Cluster;
private _showHelp: boolean; private _showHelp: boolean;
private _commands: Command[]; private _commands: Command[];
@ -50,7 +48,6 @@ export class ZHAClusterCommands extends LitElement {
hass: {}, hass: {},
isWide: {}, isWide: {},
selectedNode: {}, selectedNode: {},
selectedEntity: {},
selectedCluster: {}, selectedCluster: {},
_showHelp: {}, _showHelp: {},
_commands: {}, _commands: {},
@ -146,25 +143,29 @@ export class ZHAClusterCommands extends LitElement {
} }
private async _fetchCommandsForCluster(): Promise<void> { private async _fetchCommandsForCluster(): Promise<void> {
if (this.selectedEntity && this.selectedCluster && this.hass) { if (this.selectedNode && this.selectedCluster && this.hass) {
this._commands = await fetchCommandsForCluster( this._commands = await fetchCommandsForCluster(
this.hass, this.hass,
this.selectedEntity!.entity_id, this.selectedNode!.ieee,
this.selectedEntity!.device_info!.identifiers[0][1], this.selectedCluster!.endpoint_id,
this.selectedCluster!.id, this.selectedCluster!.id,
this.selectedCluster!.type this.selectedCluster!.type
); );
this._commands.sort((a, b) => {
return a.name.localeCompare(b.name);
});
} }
} }
private _computeIssueClusterCommandServiceData(): private _computeIssueClusterCommandServiceData():
| IssueCommandServiceData | IssueCommandServiceData
| undefined { | undefined {
if (!this.selectedEntity || !this.selectedCluster) { if (!this.selectedNode || !this.selectedCluster) {
return; return;
} }
return { return {
entity_id: this.selectedEntity!.entity_id, ieee: this.selectedNode!.ieee,
endpoint_id: this.selectedCluster!.endpoint_id,
cluster_id: this.selectedCluster!.id, cluster_id: this.selectedCluster!.id,
cluster_type: this.selectedCluster!.type, cluster_type: this.selectedCluster!.type,
command: this._commands[this._selectedCommandIndex].id, command: this._commands[this._selectedCommandIndex].id,
@ -257,8 +258,7 @@ export class ZHAClusterCommands extends LitElement {
[hidden] { [hidden] {
display: none; display: none;
} }
</style> `,
`,
]; ];
} }
} }

View File

@ -11,11 +11,7 @@ import "@polymer/paper-card/paper-card";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/buttons/ha-call-service-button"; import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description"; import "../../../components/ha-service-description";
import { import { Cluster, fetchClustersForZhaNode, ZHADevice } from "../../../data/zha";
Cluster,
fetchClustersForZhaNode,
ZHADeviceEntity,
} from "../../../data/zha";
import { haStyle } from "../../../resources/ha-style"; import { haStyle } from "../../../resources/ha-style";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import "../ha-config-section"; import "../ha-config-section";
@ -31,14 +27,16 @@ declare global {
} }
const computeClusterKey = (cluster: Cluster): string => { const computeClusterKey = (cluster: Cluster): string => {
return `${cluster.name} (id: ${cluster.id}, type: ${cluster.type})`; return `${cluster.name} (Endpoint id: ${cluster.endpoint_id}, Id: ${
cluster.id
}, Type: ${cluster.type})`;
}; };
export class ZHAClusters extends LitElement { export class ZHAClusters extends LitElement {
public hass?: HomeAssistant; public hass?: HomeAssistant;
public isWide?: boolean; public isWide?: boolean;
public showHelp: boolean; public showHelp: boolean;
public selectedEntity?: ZHADeviceEntity; public selectedDevice?: ZHADevice;
private _selectedClusterIndex: number; private _selectedClusterIndex: number;
private _clusters: Cluster[]; private _clusters: Cluster[];
@ -54,14 +52,14 @@ export class ZHAClusters extends LitElement {
hass: {}, hass: {},
isWide: {}, isWide: {},
showHelp: {}, showHelp: {},
selectedEntity: {}, selectedDevice: {},
_selectedClusterIndex: {}, _selectedClusterIndex: {},
_clusters: {}, _clusters: {},
}; };
} }
protected updated(changedProperties: PropertyValues): void { protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("selectedEntity")) { if (changedProperties.has("selectedDevice")) {
this._clusters = []; this._clusters = [];
this._selectedClusterIndex = -1; this._selectedClusterIndex = -1;
fireEvent(this, "zha-cluster-selected", { fireEvent(this, "zha-cluster-selected", {
@ -103,9 +101,11 @@ export class ZHAClusters extends LitElement {
if (this.hass) { if (this.hass) {
this._clusters = await fetchClustersForZhaNode( this._clusters = await fetchClustersForZhaNode(
this.hass, this.hass,
this.selectedEntity!.entity_id, this.selectedDevice!.ieee
this.selectedEntity!.device_info!.identifiers[0][1]
); );
this._clusters.sort((a, b) => {
return a.name.localeCompare(b.name);
});
} }
} }

View File

@ -0,0 +1,118 @@
import {
html,
LitElement,
property,
TemplateResult,
CSSResult,
css,
} from "lit-element";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { fireEvent } from "../../../common/dom/fire_event";
import { haStyle } from "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
import "../../../components/entity/state-badge";
import { ZHADevice } from "../../../data/zha";
class ZHADeviceCard extends LitElement {
@property() public hass?: HomeAssistant;
@property() public narrow?: boolean;
@property() public device?: ZHADevice;
protected render(): TemplateResult | void {
return html`
<paper-card>
<div class="card-content">
<dl>
<dt class="label">IEEE:</dt>
<dd class="info">${this.device!.ieee}</dd>
<dt class="label">Quirk applied:</dt>
<dd class="info">${this.device!.quirk_applied}</dd>
<dt class="label">Quirk:</dt>
<dd class="info">${this.device!.quirk_class}</dd>
</dl>
</div>
<div class="device-entities">
${this.device!.entities.map(
(entity) => html`
<paper-icon-item
@click="${this._openMoreInfo}"
.entity="${entity}"
>
<state-badge
.stateObj="${this.hass!.states[entity.entity_id]}"
slot="item-icon"
></state-badge>
<paper-item-body>
<div class="name">${entity.name}</div>
<div class="secondary entity-id">${entity.entity_id}</div>
</paper-item-body>
</paper-icon-item>
`
)}
</div>
</paper-card>
`;
}
private _openMoreInfo(ev: MouseEvent): void {
fireEvent(this, "hass-more-info", {
entityId: (ev.currentTarget as any).entity.entity_id,
});
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
:host(:not([narrow])) .device-entities {
max-height: 225px;
overflow: auto;
}
paper-card {
flex: 1 0 100%;
padding-bottom: 10px;
min-width: 0;
}
.device {
width: 30%;
}
.label {
font-weight: bold;
}
.info {
color: var(--secondary-text-color);
font-weight: bold;
}
dl dt {
float: left;
width: 100px;
text-align: left;
}
dt dd {
margin-left: 10px;
text-align: left;
}
paper-icon-item {
cursor: pointer;
padding-top: 4px;
padding-bottom: 4px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-device-card": ZHADeviceCard;
}
}
customElements.define("zha-device-card", ZHADeviceCard);

View File

@ -1,170 +0,0 @@
import {
html,
LitElement,
PropertyDeclarations,
PropertyValues,
TemplateResult,
CSSResult,
css,
} from "lit-element";
import "@polymer/paper-button/paper-button";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { HassEntity } from "home-assistant-js-websocket";
import { fireEvent } from "../../../common/dom/fire_event";
import { fetchEntitiesForZhaNode } from "../../../data/zha";
import { haStyle } from "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
import { ItemSelectedEvent } from "./types";
declare global {
// for fire event
interface HASSDomEvents {
"zha-entity-selected": {
entity?: HassEntity;
};
}
}
export class ZHAEntities extends LitElement {
public hass?: HomeAssistant;
public showHelp?: boolean;
public selectedNode?: HassEntity;
private _selectedEntityIndex: number;
private _entities: HassEntity[];
constructor() {
super();
this._entities = [];
this._selectedEntityIndex = -1;
}
static get properties(): PropertyDeclarations {
return {
hass: {},
showHelp: {},
selectedNode: {},
_selectedEntityIndex: {},
_entities: {},
};
}
protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("selectedNode")) {
this._entities = [];
this._selectedEntityIndex = -1;
fireEvent(this, "zha-entity-selected", {
entity: undefined,
});
this._fetchEntitiesForZhaNode();
}
super.update(changedProperties);
}
protected render(): TemplateResult | void {
return html`
<div class="node-picker">
<paper-dropdown-menu label="Entities" class="flex">
<paper-listbox
slot="dropdown-content"
.selected="${this._selectedEntityIndex}"
@iron-select="${this._selectedEntityChanged}"
>
${this._entities.map(
(entry) => html`
<paper-item>${entry.entity_id}</paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu>
</div>
${this.showHelp
? html`
<div class="helpText">
Select entity to view per-entity options
</div>
`
: ""}
${this._selectedEntityIndex !== -1
? html`
<div class="actions">
<paper-button @click="${this._showEntityInformation}"
>Entity Information</paper-button
>
</div>
`
: ""}
`;
}
private async _fetchEntitiesForZhaNode(): Promise<void> {
if (this.hass) {
const fetchedEntities = await fetchEntitiesForZhaNode(this.hass);
this._entities = fetchedEntities[this.selectedNode!.attributes.ieee];
}
}
private _selectedEntityChanged(event: ItemSelectedEvent): void {
this._selectedEntityIndex = event.target!.selected;
fireEvent(this, "zha-entity-selected", {
entity: this._entities[this._selectedEntityIndex],
});
}
private _showEntityInformation(): void {
fireEvent(this, "hass-more-info", {
entityId: this._entities[this._selectedEntityIndex].entity_id,
});
}
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;
}
.node-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;
}
.actions {
border-top: 1px solid #e8e8e8;
padding: 5px 16px;
position: relative;
}
.actions paper-button:not([disabled]) {
color: var(--primary-color);
font-weight: 500;
}
.helpText {
color: grey;
padding: 16px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-entities": ZHAEntities;
}
}
customElements.define("zha-entities", ZHAEntities);

View File

@ -102,8 +102,7 @@ export class ZHANetwork extends LitElement {
[hidden] { [hidden] {
display: none; display: none;
} }
</style> `,
`,
]; ];
} }
} }

View File

@ -4,6 +4,7 @@ import {
PropertyDeclarations, PropertyDeclarations,
TemplateResult, TemplateResult,
CSSResult, CSSResult,
PropertyValues,
css, css,
} from "lit-element"; } from "lit-element";
import "@polymer/paper-button/paper-button"; import "@polymer/paper-button/paper-button";
@ -11,29 +12,22 @@ import "@polymer/paper-card/paper-card";
import "@polymer/paper-icon-button/paper-icon-button"; import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { HassEntity } from "home-assistant-js-websocket"; import { fireEvent } from "../../../common/dom/fire_event";
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
import computeStateName from "../../../common/entity/compute_state_name";
import sortByName from "../../../common/entity/states_sort_by_name";
import "../../../components/buttons/ha-call-service-button"; import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description"; import "../../../components/ha-service-description";
import { haStyle } from "../../../resources/ha-style"; import { haStyle } from "../../../resources/ha-style";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import "../ha-config-section"; import "../ha-config-section";
import { import { ItemSelectedEvent, NodeServiceData } from "./types";
ItemSelectedEvent,
NodeServiceData,
ZHAEntitySelectedParams,
} from "./types";
import "./zha-clusters"; import "./zha-clusters";
import "./zha-entities"; import "./zha-device-card";
import { reconfigureNode } from "../../../data/zha"; import { reconfigureNode, fetchDevices, ZHADevice } from "../../../data/zha";
declare global { declare global {
// for fire event // for fire event
interface HASSDomEvents { interface HASSDomEvents {
"zha-node-selected": { "zha-node-selected": {
node?: HassEntity; node?: ZHADevice;
}; };
} }
} }
@ -43,10 +37,9 @@ export class ZHANode extends LitElement {
public isWide?: boolean; public isWide?: boolean;
private _showHelp: boolean; private _showHelp: boolean;
private _selectedNodeIndex: number; private _selectedNodeIndex: number;
private _selectedNode?: HassEntity; private _selectedNode?: ZHADevice;
private _selectedEntity?: HassEntity;
private _serviceData?: {}; private _serviceData?: {};
private _nodes: HassEntity[]; private _nodes: ZHADevice[];
constructor() { constructor() {
super(); super();
@ -62,13 +55,31 @@ export class ZHANode extends LitElement {
_showHelp: {}, _showHelp: {},
_selectedNodeIndex: {}, _selectedNodeIndex: {},
_selectedNode: {}, _selectedNode: {},
_entities: {},
_serviceData: {}, _serviceData: {},
_selectedEntity: {}, _nodes: {},
}; };
} }
public firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
if (this._nodes.length === 0) {
this._fetchDevices();
}
this.addEventListener("hass-service-called", (ev) =>
this.serviceCalled(ev)
);
}
protected serviceCalled(ev): void {
// Check if this is for us
if (ev.detail.success && ev.detail.service === "remove") {
this._selectedNodeIndex = -1;
this._fetchDevices();
}
}
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
this._nodes = this._computeNodes(this.hass);
return html` return html`
<ha-config-section .isWide="${this.isWide}"> <ha-config-section .isWide="${this.isWide}">
<div class="sectionHeader" slot="header"> <div class="sectionHeader" slot="header">
@ -94,12 +105,11 @@ export class ZHANode extends LitElement {
<paper-listbox <paper-listbox
slot="dropdown-content" slot="dropdown-content"
@iron-select="${this._selectedNodeChanged}" @iron-select="${this._selectedNodeChanged}"
.selected="${this._selectedNodeIndex}"
> >
${this._nodes.map( ${this._nodes.map(
(entry) => html` (entry) => html`
<paper-item <paper-item>${entry.name}</paper-item>
>${this._computeSelectCaption(entry)}</paper-item
>
` `
)} )}
</paper-listbox> </paper-listbox>
@ -112,9 +122,18 @@ export class ZHANode extends LitElement {
</div> </div>
` `
: ""} : ""}
${this._selectedNodeIndex !== -1
? html`
<zha-device-card
class="card"
.hass="${this.hass}"
.device="${this._selectedNode}"
.narrow="${!this.isWide}"
></zha-device-card>
`
: ""}
${this._selectedNodeIndex !== -1 ? this._renderNodeActions() : ""} ${this._selectedNodeIndex !== -1 ? this._renderNodeActions() : ""}
${this._selectedNodeIndex !== -1 ? this._renderEntities() : ""} ${this._selectedNode ? this._renderClusters() : ""}
${this._selectedEntity ? this._renderClusters() : ""}
</paper-card> </paper-card>
</ha-config-section> </ha-config-section>
`; `;
@ -123,9 +142,6 @@ export class ZHANode extends LitElement {
private _renderNodeActions(): TemplateResult { private _renderNodeActions(): TemplateResult {
return html` return html`
<div class="card-actions"> <div class="card-actions">
<paper-button @click="${this._showNodeInformation}"
>Node Information</paper-button
>
<paper-button @click="${this._onReconfigureNodeClick}" <paper-button @click="${this._onReconfigureNodeClick}"
>Reconfigure Node</paper-button >Reconfigure Node</paper-button
> >
@ -158,22 +174,11 @@ export class ZHANode extends LitElement {
`; `;
} }
private _renderEntities(): TemplateResult {
return html`
<zha-entities
.hass="${this.hass}"
.selectedNode="${this._selectedNode}"
.showHelp="${this._showHelp}"
@zha-entity-selected="${this._onEntitySelected}"
></zha-entities>
`;
}
private _renderClusters(): TemplateResult { private _renderClusters(): TemplateResult {
return html` return html`
<zha-clusters <zha-clusters
.hass="${this.hass}" .hass="${this.hass}"
.selectedEntity="${this._selectedEntity}" .selectedDevice="${this._selectedNode}"
.showHelp="${this._showHelp}" .showHelp="${this._showHelp}"
></zha-clusters> ></zha-clusters>
`; `;
@ -186,50 +191,26 @@ export class ZHANode extends LitElement {
private _selectedNodeChanged(event: ItemSelectedEvent): void { private _selectedNodeChanged(event: ItemSelectedEvent): void {
this._selectedNodeIndex = event!.target!.selected; this._selectedNodeIndex = event!.target!.selected;
this._selectedNode = this._nodes[this._selectedNodeIndex]; this._selectedNode = this._nodes[this._selectedNodeIndex];
this._selectedEntity = undefined;
fireEvent(this, "zha-node-selected", { node: this._selectedNode }); fireEvent(this, "zha-node-selected", { node: this._selectedNode });
this._serviceData = this._computeNodeServiceData(); this._serviceData = this._computeNodeServiceData();
} }
private async _onReconfigureNodeClick(): Promise<void> { private async _onReconfigureNodeClick(): Promise<void> {
if (this.hass) { if (this.hass) {
await reconfigureNode(this.hass, this._selectedNode!.attributes.ieee); await reconfigureNode(this.hass, this._selectedNode!.ieee);
} }
} }
private _showNodeInformation(): void {
fireEvent(this, "hass-more-info", {
entityId: this._selectedNode!.entity_id,
});
}
private _computeNodeServiceData(): NodeServiceData { private _computeNodeServiceData(): NodeServiceData {
return { return {
ieee_address: this._selectedNode!.attributes.ieee, ieee_address: this._selectedNode!.ieee,
}; };
} }
private _computeSelectCaption(stateObj: HassEntity): string { private async _fetchDevices() {
return ( this._nodes = (await fetchDevices(this.hass!)).sort((a, b) => {
computeStateName(stateObj) + " (Node:" + stateObj.attributes.ieee + ")" return a.name.localeCompare(b.name);
); });
}
private _computeNodes(hass?: HomeAssistant): HassEntity[] {
if (hass) {
return Object.keys(hass.states)
.map((key) => hass.states[key])
.filter((ent) => ent.entity_id.match("zha[.]"))
.sort(sortByName);
} else {
return [];
}
}
private _onEntitySelected(
entitySelectedEvent: HASSDomEvent<ZHAEntitySelectedParams>
): void {
this._selectedEntity = entitySelectedEvent.detail.entity;
} }
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
@ -287,6 +268,17 @@ export class ZHANode extends LitElement {
padding-bottom: 10px; padding-bottom: 10px;
} }
.card {
box-sizing: border-box;
display: flex;
flex: 1 0 300px;
min-width: 0;
max-width: 600px;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
ha-service-description { ha-service-description {
display: block; display: block;
color: grey; color: grey;