From 669358bf1ad5085eb224012778fb9b015b3bde64 Mon Sep 17 00:00:00 2001 From: "David F. Mulcahey" Date: Mon, 25 Mar 2019 23:26:32 -0400 Subject: [PATCH] ZHA add devices page (#2969) * zha add device page add device join dialog stub update dialog stub fix spinner add messages and devices to dialog dialog updates update dialog update dialog add debug info fix reference add header update dialog test zha gateway message subscription add device join dialog stub add messages and devices to dialog dialog updates update dialog add debug info update dialog start transitioning to a page instead of a dialog fix import subpage update router remove old dialog handle remove dialog parts make add button call navigate change extract page add devices page cleanup * update device join page * auto scroll log * update css and add device page layout * fix padding * fix missing imports * fix imports * add -> permit * left justify device cards to prevent jumping * conditionally display entity ids * cleanup * fix vertical alignment * review comments * fix manufacturer overrides --- src/data/zha.ts | 3 +- src/panels/config/ha-panel-config.ts | 4 +- src/panels/config/zha/ha-config-zha.ts | 41 +-- src/panels/config/zha/types.ts | 8 +- src/panels/config/zha/zha-add-devices-page.ts | 246 ++++++++++++++ src/panels/config/zha/zha-binding.ts | 22 +- .../config/zha/zha-cluster-attributes.ts | 48 ++- src/panels/config/zha/zha-cluster-commands.ts | 33 +- src/panels/config/zha/zha-clusters.ts | 27 +- src/panels/config/zha/zha-config-panel.ts | 70 ++++ src/panels/config/zha/zha-device-card.ts | 304 ++++++++++++++++-- src/panels/config/zha/zha-network.ts | 69 ++-- src/panels/config/zha/zha-node.ts | 253 ++++----------- src/translations/en.json | 13 +- 14 files changed, 822 insertions(+), 319 deletions(-) create mode 100644 src/panels/config/zha/zha-add-devices-page.ts create mode 100644 src/panels/config/zha/zha-config-panel.ts diff --git a/src/data/zha.ts b/src/data/zha.ts index bdb965e5bd..87ac9a3a88 100644 --- a/src/data/zha.ts +++ b/src/data/zha.ts @@ -16,6 +16,7 @@ export interface ZHADevice { manufacturer_code: number; device_reg_id: string; user_given_name: string; + area_id: string; } export interface Attribute { @@ -42,7 +43,7 @@ export interface ReadAttributeServiceData { cluster_id: number; cluster_type: string; attribute: number; - manufacturer: number; + manufacturer?: number; } export const reconfigureNode = ( diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index 0d78f87168..052c12aea3 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -73,9 +73,9 @@ class HaPanelConfig extends HassRouterPage { import(/* webpackChunkName: "panel-config-users" */ "./users/ha-config-users"), }, zha: { - tag: "ha-config-zha", + tag: "zha-config-panel", load: () => - import(/* webpackChunkName: "panel-config-zha" */ "./zha/ha-config-zha"), + import(/* webpackChunkName: "panel-config-zha" */ "./zha/zha-config-panel"), }, zwave: { tag: "ha-config-zwave", diff --git a/src/panels/config/zha/ha-config-zha.ts b/src/panels/config/zha/ha-config-zha.ts index 1708b9f5a8..0c96d858a5 100755 --- a/src/panels/config/zha/ha-config-zha.ts +++ b/src/panels/config/zha/ha-config-zha.ts @@ -1,26 +1,26 @@ -import "@polymer/app-layout/app-header/app-header"; -import "@polymer/app-layout/app-toolbar/app-toolbar"; +import "../../../components/ha-paper-icon-button-arrow-prev"; +import "../../../layouts/hass-subpage"; +import "./zha-binding"; +import "./zha-cluster-attributes"; +import "./zha-cluster-commands"; +import "./zha-network"; +import "./zha-node"; +import "@polymer/paper-icon-button/paper-icon-button"; + import { + CSSResult, html, LitElement, property, PropertyValues, TemplateResult, - CSSResult, } from "lit-element"; -import "@polymer/paper-icon-button/paper-icon-button"; + import { HASSDomEvent } from "../../../common/dom/fire_event"; -import { Cluster, ZHADevice, fetchBindableDevices } from "../../../data/zha"; -import "../../../layouts/ha-app-layout"; -import "../../../components/ha-paper-icon-button-arrow-prev"; +import { Cluster, fetchBindableDevices, ZHADevice } from "../../../data/zha"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { ZHAClusterSelectedParams, ZHADeviceSelectedParams } from "./types"; -import "./zha-cluster-attributes"; -import "./zha-cluster-commands"; -import "./zha-network"; -import "./zha-node"; -import "./zha-binding"; export class HaConfigZha extends LitElement { @property() public hass?: HomeAssistant; @@ -38,16 +38,7 @@ export class HaConfigZha extends LitElement { protected render(): TemplateResult | void { return html` - - - - -
Zigbee Home Automation
-
-
- + ` : ""} -
+ `; } @@ -117,10 +108,6 @@ export class HaConfigZha extends LitElement { static get styles(): CSSResult[] { return [haStyle]; } - - private _onBackTapped(): void { - history.back(); - } } declare global { diff --git a/src/panels/config/zha/types.ts b/src/panels/config/zha/types.ts index 5e531c98aa..c785614efb 100644 --- a/src/panels/config/zha/types.ts +++ b/src/panels/config/zha/types.ts @@ -8,6 +8,12 @@ export interface ItemSelectedEvent { target?: PickerTarget; } +export interface ZHADeviceRemovedEvent { + detail?: { + device?: ZHADevice; + }; +} + export interface ChangeEvent { detail?: { value?: any; @@ -22,7 +28,7 @@ export interface SetAttributeServiceData { cluster_type: string; attribute: number; value: any; - manufacturer: number; + manufacturer?: number; } export interface IssueCommandServiceData { diff --git a/src/panels/config/zha/zha-add-devices-page.ts b/src/panels/config/zha/zha-add-devices-page.ts new file mode 100644 index 0000000000..9ebcce185a --- /dev/null +++ b/src/panels/config/zha/zha-add-devices-page.ts @@ -0,0 +1,246 @@ +import "../../../components/ha-service-description"; +import "../../../components/ha-textarea"; +import "../../../layouts/hass-subpage"; +import "./zha-device-card"; +import "@material/mwc-button"; +import "@polymer/paper-icon-button/paper-icon-button"; +import "@polymer/paper-spinner/paper-spinner"; + +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; + +import { ZHADevice } from "../../../data/zha"; +import { haStyle } from "../../../resources/styles"; +import { HomeAssistant } from "../../../types"; + +@customElement("zha-add-devices-page") +class ZHAAddDevicesPage extends LitElement { + @property() public hass!: HomeAssistant; + @property() public isWide?: boolean; + @property() private _error?: string; + @property() private _discoveredDevices: ZHADevice[] = []; + @property() private _formattedEvents: string = ""; + @property() private _active: boolean = false; + @property() private _showHelp: boolean = false; + private _addDevicesTimeoutHandle: any = undefined; + private _subscribed?: Promise<() => Promise>; + + public connectedCallback(): void { + super.connectedCallback(); + this._subscribe(); + } + + public disconnectedCallback(): void { + super.disconnectedCallback(); + this._unsubscribe(); + this._error = undefined; + this._discoveredDevices = []; + this._formattedEvents = ""; + } + + protected render(): TemplateResult | void { + return html` + + ${this._active + ? html` +

+ + ${this.hass!.localize( + "ui.panel.config.zha.add_device_page.spinner" + )} +

+ ` + : html` +
+ + Search again + + + ${this._showHelp + ? html` + + ` + : ""} +
+ `} + ${this._error + ? html` +
${this._error}
+ ` + : ""} +
+
+ ${this._discoveredDevices.length < 1 + ? html` +
+

+ ${this.hass!.localize( + "ui.panel.config.zha.add_device_page.discovery_text" + )} +

+
+ ` + : html` + ${this._discoveredDevices.map( + (device) => html` + + ` + )} + `} +
+ + +
+ `; + } + + private _handleMessage(message: any): void { + if (message.type === "log_output") { + this._formattedEvents += message.log_entry.message + "\n"; + if (this.shadowRoot) { + const textArea = this.shadowRoot.querySelector("ha-textarea"); + if (textArea) { + textArea.scrollTop = textArea.scrollHeight; + } + } + } + if (message.type && message.type === "device_fully_initialized") { + this._discoveredDevices.push(message.device_info); + } + } + + private _unsubscribe(): void { + this._active = false; + if (this._addDevicesTimeoutHandle) { + clearTimeout(this._addDevicesTimeoutHandle); + } + if (this._subscribed) { + this._subscribed.then((unsub) => unsub()); + this._subscribed = undefined; + } + } + + private _subscribe(): void { + this._subscribed = this.hass!.connection.subscribeMessage( + (message) => this._handleMessage(message), + { type: "zha/devices/permit" } + ); + this._active = true; + this._addDevicesTimeoutHandle = setTimeout( + () => this._unsubscribe(), + 60000 + ); + } + + private _onHelpTap(): void { + this._showHelp = !this._showHelp; + } + + static get styles(): CSSResult[] { + return [ + haStyle, + css` + .discovery-text, + .content-header { + margin: 16px; + } + .content { + border-top: 1px solid var(--light-primary-color); + min-height: 500px; + display: flex; + flex-wrap: wrap; + padding: 4px; + justify-content: left; + overflow: scroll; + } + .error { + color: var(--google-red-500); + } + paper-spinner { + display: none; + margin-right: 20px; + margin-left: 16px; + } + paper-spinner[active] { + display: block; + float: left; + margin-right: 20px; + margin-left: 16px; + } + .card { + margin-left: 16px; + margin-right: 16px; + margin-bottom: 0px; + margin-top: 10px; + } + .events { + margin: 16px; + border-top: 1px solid var(--light-primary-color); + padding-top: 16px; + min-height: 200px; + max-height: 200px; + overflow: scroll; + } + .toggle-help-icon { + position: absolute; + margin-top: 16px; + margin-right: 16px; + top: -6px; + right: 0; + color: var(--primary-color); + } + ha-service-description { + margin-top: 16px; + margin-left: 16px; + display: block; + color: grey; + } + .search-button { + margin-top: 16px; + margin-left: 16px; + } + .help-text { + color: grey; + padding-left: 16px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zha-add-devices-page": ZHAAddDevicesPage; + } +} diff --git a/src/panels/config/zha/zha-binding.ts b/src/panels/config/zha/zha-binding.ts index 3394262a83..a2bc43c247 100644 --- a/src/panels/config/zha/zha-binding.ts +++ b/src/panels/config/zha/zha-binding.ts @@ -1,20 +1,26 @@ +import "../../../components/buttons/ha-call-service-button"; +import "../../../components/ha-service-description"; +import "../ha-config-section"; +import "@material/mwc-button/mwc-button"; +import "@polymer/paper-card/paper-card"; +import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; +import "@polymer/paper-icon-button/paper-icon-button"; +import "@polymer/paper-listbox/paper-listbox"; + import { + css, + CSSResult, + customElement, 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 { bindDevices, unbindDevices, ZHADevice } from "../../../data/zha"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; -import "../ha-config-section"; import { ItemSelectedEvent } from "./types"; @customElement("zha-binding-control") diff --git a/src/panels/config/zha/zha-cluster-attributes.ts b/src/panels/config/zha/zha-cluster-attributes.ts index c59126cbf2..10f4a3677e 100644 --- a/src/panels/config/zha/zha-cluster-attributes.ts +++ b/src/panels/config/zha/zha-cluster-attributes.ts @@ -1,17 +1,24 @@ +import "../../../components/buttons/ha-call-service-button"; +import "../../../components/ha-service-description"; +import "../ha-config-section"; +import "@material/mwc-button"; +import "@polymer/paper-card/paper-card"; +import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; +import "@polymer/paper-icon-button/paper-icon-button"; +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-item/paper-item"; +import "@polymer/paper-listbox/paper-listbox"; + import { + css, + CSSResult, html, LitElement, PropertyDeclarations, PropertyValues, TemplateResult, - CSSResult, - css, } from "lit-element"; -import "@material/mwc-button"; -import "@polymer/paper-card/paper-card"; -import "@polymer/paper-icon-button/paper-icon-button"; -import "../../../components/buttons/ha-call-service-button"; -import "../../../components/ha-service-description"; + import { Attribute, Cluster, @@ -22,13 +29,12 @@ import { } from "../../../data/zha"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; -import "../ha-config-section"; +import { formatAsPaddedHex } from "./functions"; import { ChangeEvent, ItemSelectedEvent, SetAttributeServiceData, } from "./types"; -import { formatAsPaddedHex } from "./functions"; export class ZHAClusterAttributes extends LitElement { public hass?: HomeAssistant; @@ -115,7 +121,7 @@ export class ZHAClusterAttributes extends LitElement { ${this.showHelp ? html` -
+
Select an attribute to view or set its value
` @@ -152,6 +158,13 @@ export class ZHAClusterAttributes extends LitElement { Get Zigbee Attribute + ${this.showHelp + ? html` +
+ Get the value for the selected attribute +
+ ` + : ""} ` : ""} @@ -201,7 +215,7 @@ export class ZHAClusterAttributes extends LitElement { attribute: this._attributes[this._selectedAttributeIndex].id, manufacturer: this._manufacturerCodeOverride ? parseInt(this._manufacturerCodeOverride as string, 10) - : this.selectedNode!.manufacturer_code, + : undefined, }; } @@ -220,7 +234,7 @@ export class ZHAClusterAttributes extends LitElement { value: this._attributeValue, manufacturer: this._manufacturerCodeOverride ? parseInt(this._manufacturerCodeOverride as string, 10) - : this.selectedNode!.manufacturer_code, + : undefined, }; } @@ -312,6 +326,16 @@ export class ZHAClusterAttributes extends LitElement { [hidden] { display: none; } + .help-text { + color: grey; + padding-left: 28px; + padding-right: 28px; + padding-bottom: 16px; + } + .help-text2 { + color: grey; + padding: 16px; + } `, ]; } diff --git a/src/panels/config/zha/zha-cluster-commands.ts b/src/panels/config/zha/zha-cluster-commands.ts index 28a68af9ab..d8bbcac2e4 100644 --- a/src/panels/config/zha/zha-cluster-commands.ts +++ b/src/panels/config/zha/zha-cluster-commands.ts @@ -1,15 +1,23 @@ +import "../../../components/buttons/ha-call-service-button"; +import "../../../components/ha-service-description"; +import "../ha-config-section"; +import "@polymer/paper-card/paper-card"; +import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; +import "@polymer/paper-icon-button/paper-icon-button"; +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-item/paper-item"; +import "@polymer/paper-listbox/paper-listbox"; + import { + css, + CSSResult, html, LitElement, PropertyDeclarations, PropertyValues, TemplateResult, - CSSResult, - css, } from "lit-element"; -import "@polymer/paper-card/paper-card"; -import "../../../components/buttons/ha-call-service-button"; -import "../../../components/ha-service-description"; + import { Cluster, Command, @@ -18,13 +26,12 @@ import { } from "../../../data/zha"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; -import "../ha-config-section"; +import { formatAsPaddedHex } from "./functions"; import { ChangeEvent, IssueCommandServiceData, ItemSelectedEvent, } from "./types"; -import { formatAsPaddedHex } from "./functions"; export class ZHAClusterCommands extends LitElement { public hass?: HomeAssistant; @@ -107,7 +114,7 @@ export class ZHAClusterCommands extends LitElement {
${this._showHelp ? html` -
Select a command to interact with
+
Select a command to interact with
` : ""} ${this._selectedCommandIndex !== -1 @@ -135,6 +142,7 @@ export class ZHAClusterCommands extends LitElement { .hass="${this.hass}" domain="zha" service="issue_zigbee_cluster_command" + class="help-text2" > ` : ""} @@ -242,7 +250,14 @@ export class ZHAClusterCommands extends LitElement { position: relative; } - .helpText { + .help-text { + color: grey; + padding-left: 28px; + padding-right: 28px; + padding-bottom: 16px; + } + + .help-text2 { color: grey; padding: 16px; } diff --git a/src/panels/config/zha/zha-clusters.ts b/src/panels/config/zha/zha-clusters.ts index bbabf3e2fa..512cc16d7d 100644 --- a/src/panels/config/zha/zha-clusters.ts +++ b/src/panels/config/zha/zha-clusters.ts @@ -1,22 +1,27 @@ +import "../../../components/buttons/ha-call-service-button"; +import "../../../components/ha-service-description"; +import "../ha-config-section"; +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 { + css, + CSSResult, html, LitElement, PropertyDeclarations, PropertyValues, TemplateResult, - CSSResult, - css, } from "lit-element"; -import "@polymer/paper-card/paper-card"; + import { fireEvent } from "../../../common/dom/fire_event"; -import "../../../components/buttons/ha-call-service-button"; -import "../../../components/ha-service-description"; import { Cluster, fetchClustersForZhaNode, ZHADevice } from "../../../data/zha"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; -import "../ha-config-section"; -import { ItemSelectedEvent } from "./types"; import { formatAsPaddedHex } from "./functions"; +import { ItemSelectedEvent } from "./types"; declare global { // for fire event @@ -90,7 +95,7 @@ export class ZHAClusters extends LitElement { ${this.showHelp ? html` -
+
Select cluster to view attributes and commands
` @@ -143,9 +148,11 @@ export class ZHAClusters extends LitElement { padding-right: 28px; padding-bottom: 10px; } - .helpText { + .help-text { color: grey; - padding: 16px; + padding-left: 28px; + padding-right: 28px; + padding-bottom: 16px; } `, ]; diff --git a/src/panels/config/zha/zha-config-panel.ts b/src/panels/config/zha/zha-config-panel.ts new file mode 100644 index 0000000000..114c0f20ff --- /dev/null +++ b/src/panels/config/zha/zha-config-panel.ts @@ -0,0 +1,70 @@ +import "../../../layouts/hass-loading-screen"; + +import { customElement, property } from "lit-element"; + +import { listenMediaQuery } from "../../../common/dom/media_query"; +import { + HassRouterPage, + RouterOptions, +} from "../../../layouts/hass-router-page"; +import { HomeAssistant } from "../../../types"; + +@customElement("zha-config-panel") +class ZHAConfigPanel extends HassRouterPage { + @property() public hass!: HomeAssistant; + @property() public _wideSidebar: boolean = false; + @property() public _wide: boolean = false; + + protected routerOptions: RouterOptions = { + defaultPage: "configuration", + cacheAll: true, + preloadAll: true, + routes: { + configuration: { + tag: "ha-config-zha", + load: () => + import(/* webpackChunkName: "zha-configuration-page" */ "./ha-config-zha"), + }, + add: { + tag: "zha-add-devices-page", + load: () => + import(/* webpackChunkName: "zha-add-devices-page" */ "./zha-add-devices-page"), + }, + }, + }; + + private _listeners: Array<() => void> = []; + + public connectedCallback(): void { + super.connectedCallback(); + this._listeners.push( + listenMediaQuery("(min-width: 1040px)", (matches) => { + this._wide = matches; + }) + ); + this._listeners.push( + listenMediaQuery("(min-width: 1296px)", (matches) => { + this._wideSidebar = matches; + }) + ); + } + + public disconnectedCallback(): void { + super.disconnectedCallback(); + while (this._listeners.length) { + this._listeners.pop()!(); + } + } + + protected updatePageEl(el): void { + el.route = this.routeTail; + el.hass = this.hass; + el.isWide = this.hass.dockedSidebar ? this._wideSidebar : this._wide; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zha-config-panel": ZHAConfigPanel; + } +} diff --git a/src/panels/config/zha/zha-device-card.ts b/src/panels/config/zha/zha-device-card.ts index d68d724ecb..c37d3902d9 100644 --- a/src/panels/config/zha/zha-device-card.ts +++ b/src/panels/config/zha/zha-device-card.ts @@ -1,40 +1,123 @@ +import "../../../components/buttons/ha-call-service-button"; +import "../../../components/entity/state-badge"; +import "@material/mwc-button"; +import "@polymer/paper-card/paper-card"; +import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-item/paper-icon-item"; +import "@polymer/paper-item/paper-item"; +import "@polymer/paper-item/paper-item-body"; +import "@polymer/paper-listbox/paper-listbox"; + import { + css, + CSSResult, + customElement, html, LitElement, property, + PropertyValues, 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 compare from "../../../common/string/compare"; +import { + AreaRegistryEntry, + fetchAreaRegistry, +} from "../../../data/area_registry"; +import { + DeviceRegistryEntryMutableParams, + updateDeviceRegistryEntry, +} from "../../../data/device_registry"; +import { reconfigureNode, ZHADevice } from "../../../data/zha"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; +import { ItemSelectedEvent, NodeServiceData } from "./types"; -import "../../../components/entity/state-badge"; -import { ZHADevice } from "../../../data/zha"; +declare global { + // for fire event + interface HASSDomEvents { + "zha-device-removed": { + device?: ZHADevice; + }; + } +} +@customElement("zha-device-card") class ZHADeviceCard extends LitElement { @property() public hass?: HomeAssistant; @property() public narrow?: boolean; @property() public device?: ZHADevice; + @property() public showHelp: boolean = false; + @property() public showActions?: boolean; + @property() public isJoinPage?: boolean; + @property() private _serviceData?: NodeServiceData; + @property() private _areas: AreaRegistryEntry[] = []; + @property() private _selectedAreaIndex: number = -1; + + public firstUpdated(changedProperties: PropertyValues): void { + super.firstUpdated(changedProperties); + this.addEventListener("hass-service-called", (ev) => + this.serviceCalled(ev) + ); + this._serviceData = { + ieee_address: this.device!.ieee, + }; + fetchAreaRegistry(this.hass!).then((areas) => { + this._areas = areas.sort((a, b) => compare(a.name, b.name)); + }); + } + + protected updated(changedProperties: PropertyValues): void { + if (changedProperties.has("device")) { + this._selectedAreaIndex = + this._areas.findIndex((area) => area.area_id === this.device!.area_id) + + 1; + } + super.update(changedProperties); + } + + protected serviceCalled(ev): void { + // Check if this is for us + if (ev.detail.success && ev.detail.service === "remove") { + fireEvent(this, "zha-device-removed", { + device: this.device, + }); + } + } protected render(): TemplateResult | void { return html` - + + ${ + this.isJoinPage + ? html` +
+
${this.device!.model}
+
+ ${this.hass!.localize( + "ui.panel.config.integrations.config_entry.manuf", + "manufacturer", + this.device!.manufacturer + )} +
+
+ ` + : "" + }
-
IEEE:
-
${this.device!.ieee}
-
Quirk applied:
-
${this.device!.quirk_applied}
-
Quirk:
-
${this.device!.quirk_class}
+
IEEE:
+
${this.device!.ieee}
+ ${ + this.device!.quirk_applied + ? html` +
Quirk:
+
${this.device!.quirk_class}
+ ` + : "" + }
@@ -49,24 +132,145 @@ class ZHADeviceCard extends LitElement { .stateObj="${this.hass!.states[entity.entity_id]}" slot="item-icon" > - -
${entity.name}
-
${entity.entity_id}
-
+ ${!this.isJoinPage + ? html` + +
${entity.name}
+
+ ${entity.entity_id} +
+
+ ` + : ""} ` )}
+
+ +
+
+ + + + ${this.hass!.localize( + "ui.panel.config.integrations.config_entry.no_area" + )} + + + ${this._areas.map( + (entry) => html` + ${entry.name} + ` + )} + + +
+ ${ + this.showActions + ? html` +
+ Reconfigure Device + ${this.showHelp + ? html` +
+ ${this.hass!.localize( + "ui.panel.config.zha.services.reconfigure" + )} +
+ ` + : ""} + + Remove Device + ${this.showHelp + ? html` +
+ ${this.hass!.localize( + "ui.panel.config.zha.services.remove" + )} +
+ ` + : ""} +
+ ` + : "" + } + `; } + private async _onReconfigureNodeClick(): Promise { + if (this.hass) { + await reconfigureNode(this.hass, this.device!.ieee); + } + } + + private async _saveCustomName(event): Promise { + if (this.hass) { + const values: DeviceRegistryEntryMutableParams = { + name_by_user: event.target.value, + area_id: this.device!.area_id ? this.device!.area_id : undefined, + }; + + await updateDeviceRegistryEntry( + this.hass, + this.device!.device_reg_id, + values + ); + + this.device!.user_given_name = event.target.value; + } + } + private _openMoreInfo(ev: MouseEvent): void { fireEvent(this, "hass-more-info", { entityId: (ev.currentTarget as any).entity.entity_id, }); } + private async _selectedAreaChanged(event: ItemSelectedEvent) { + if (!this.device || !this._areas) { + return; + } + this._selectedAreaIndex = event!.target!.selected; + const area = this._areas[this._selectedAreaIndex - 1]; // account for No Area + if ( + (!area && !this.device.area_id) || + (area && area.area_id === this.device.area_id) + ) { + return; + } + + await updateDeviceRegistryEntry(this.hass!, this.device.device_reg_id, { + area_id: area ? area.area_id : undefined, + name_by_user: this.device!.user_given_name, + }); + } + static get styles(): CSSResult[] { return [ haStyle, @@ -74,29 +278,43 @@ class ZHADeviceCard extends LitElement { :host(:not([narrow])) .device-entities { max-height: 225px; overflow: auto; + display: flex; + flex-wrap: wrap; + padding: 4px; + justify-content: left; } paper-card { flex: 1 0 100%; padding-bottom: 10px; - min-width: 0; + min-width: 425px; } .device { width: 30%; } - .label { + .device .name { font-weight: bold; } + .device .manuf { + color: var(--secondary-text-color); + } + .extra-info { + margin-top: 8px; + } + .manuf, + .zha-info, + .entity-id { + color: var(--secondary-text-color); + } .info { - color: var(--secondary-text-color); - font-weight: bold; + margin-left: 16px; } dl dt { + padding-left: 12px; float: left; - width: 100px; + width: 50px; text-align: left; } dt dd { - margin-left: 10px; text-align: left; } paper-icon-item { @@ -104,6 +322,36 @@ class ZHADeviceCard extends LitElement { padding-top: 4px; padding-bottom: 4px; } + .editable { + padding-left: 28px; + padding-right: 28px; + padding-bottom: 10px; + } + .help-text { + color: grey; + padding: 16px; + } + .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; + } `, ]; } @@ -114,5 +362,3 @@ declare global { "zha-device-card": ZHADeviceCard; } } - -customElements.define("zha-device-card", ZHADeviceCard); diff --git a/src/panels/config/zha/zha-network.ts b/src/panels/config/zha/zha-network.ts index 87cf043838..b6c92d694e 100644 --- a/src/panels/config/zha/zha-network.ts +++ b/src/panels/config/zha/zha-network.ts @@ -1,19 +1,22 @@ +import "../../../components/buttons/ha-call-service-button"; +import "../../../components/ha-service-description"; +import "../ha-config-section"; +import "@material/mwc-button"; +import "@polymer/paper-card/paper-card"; +import "@polymer/paper-icon-button/paper-icon-button"; + import { + css, + CSSResult, html, LitElement, PropertyDeclarations, TemplateResult, - CSSResult, - css, } from "lit-element"; -import "@material/mwc-button"; -import "@polymer/paper-card/paper-card"; -import "@polymer/paper-icon-button/paper-icon-button"; -import "../../../components/buttons/ha-call-service-button"; -import "../../../components/ha-service-description"; + +import { navigate } from "../../../common/navigate"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; -import "../ha-config-section"; export class ZHANetwork extends LitElement { public hass?: HomeAssistant; @@ -30,6 +33,7 @@ export class ZHANetwork extends LitElement { hass: {}, isWide: {}, _showHelp: {}, + _joinParams: {}, }; } @@ -37,29 +41,31 @@ export class ZHANetwork extends LitElement { return html`
- Network Management - + Network Management +
Commands that affect entire network -
- Permit - ${ - this._showHelp - ? html` - - ` - : "" - } +
+ + Add Devices + + ${this._showHelp + ? html` + + ` + : ""} +
`; @@ -69,6 +75,10 @@ export class ZHANetwork extends LitElement { this._showHelp = !this._showHelp; } + private _onAddDevicesClick() { + navigate(this, "add"); + } + static get styles(): CSSResult[] { return [ haStyle, @@ -102,6 +112,11 @@ export class ZHANetwork extends LitElement { [hidden] { display: none; } + + .help-text2 { + color: grey; + padding: 16px; + } `, ]; } diff --git a/src/panels/config/zha/zha-node.ts b/src/panels/config/zha/zha-node.ts index 959c7bc594..39ab5a1181 100644 --- a/src/panels/config/zha/zha-node.ts +++ b/src/panels/config/zha/zha-node.ts @@ -1,32 +1,30 @@ -import { - html, - LitElement, - PropertyDeclarations, - TemplateResult, - CSSResult, - PropertyValues, - css, -} from "lit-element"; +import "../../../components/buttons/ha-call-service-button"; +import "../../../components/ha-service-description"; +import "../ha-config-section"; +import "./zha-clusters"; +import "./zha-device-card"; import "@material/mwc-button"; import "@polymer/paper-card/paper-card"; import "@polymer/paper-icon-button/paper-icon-button"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; + +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; + import { fireEvent } from "../../../common/dom/fire_event"; -import "../../../components/buttons/ha-call-service-button"; -import "../../../components/ha-service-description"; +import { fetchDevices, ZHADevice } from "../../../data/zha"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; -import "../ha-config-section"; -import { ItemSelectedEvent, NodeServiceData, ChangeEvent } from "./types"; -import "./zha-clusters"; -import "./zha-device-card"; -import { - updateDeviceRegistryEntry, - DeviceRegistryEntryMutableParams, -} from "../../../data/device_registry"; -import { reconfigureNode, fetchDevices, ZHADevice } from "../../../data/zha"; +import { ItemSelectedEvent, ZHADeviceRemovedEvent } from "./types"; declare global { // for fire event @@ -37,60 +35,25 @@ declare global { } } +@customElement("zha-node") export class ZHANode extends LitElement { - public hass?: HomeAssistant; - public isWide?: boolean; - private _showHelp: boolean; - private _selectedNodeIndex: number; - private _selectedNode?: ZHADevice; - private _serviceData?: {}; - private _nodes: ZHADevice[]; - private _userSelectedName?: string; + @property() public hass?: HomeAssistant; + @property() public isWide?: boolean; + @property() private _showHelp: boolean = false; + @property() private _selectedDeviceIndex: number = -1; + @property() private _selectedDevice?: ZHADevice; + @property() private _nodes: ZHADevice[] = []; - constructor() { - super(); - this._showHelp = false; - this._selectedNodeIndex = -1; - this._nodes = []; - } - - static get properties(): PropertyDeclarations { - return { - hass: {}, - isWide: {}, - _showHelp: {}, - _selectedNodeIndex: {}, - _selectedNode: {}, - _entities: {}, - _serviceData: {}, - _nodes: {}, - _userSelectedName: {}, - }; - } - - 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(); - } + public connectedCallback(): void { + super.connectedCallback(); + this._fetchDevices(); } protected render(): TemplateResult | void { return html`
- Node Management + Device Management
- Run ZHA commands that affect a single node. Pick a node to see a list - of available commands.

Note: Sleepy (battery powered) + Run ZHA commands that affect a single device. Pick a device to see a + list of available commands.

Note: Sleepy (battery powered) devices need to be awake when executing commands against them. You can generally wake a sleepy device by triggering it.

Some devices such as Xiaomi sensors have a wake up button that you can @@ -108,11 +71,15 @@ export class ZHANode extends LitElement {
- + ${this._nodes.map( (entry) => html` @@ -128,95 +95,36 @@ export class ZHANode extends LitElement {
${this._showHelp ? html` -
- Select node to view per-node options +
+ Select device to view per-device options
` : ""} - ${this._selectedNodeIndex !== -1 + ${this._selectedDeviceIndex !== -1 ? html` ` : ""} - ${this._selectedNodeIndex !== -1 - ? html` -
- -
- ` - : ""} - ${this._selectedNodeIndex !== -1 ? this._renderNodeActions() : ""} - ${this._selectedNode ? this._renderClusters() : ""} + ${this._selectedDevice ? this._renderClusters() : ""} `; } - private _renderNodeActions(): TemplateResult { - return html` -
- Reconfigure Node - ${this._showHelp - ? html` -
- ${this.hass!.localize( - "ui.panel.config.zha.services.reconfigure" - )} -
- ` - : ""} - Remove Node - ${this._showHelp - ? html` - - ` - : ""} - Update Name - ${this._showHelp - ? html` -
- ${this.hass!.localize( - "ui.panel.config.zha.services.updateDeviceName" - )} -
- ` - : ""} -
- `; - } - private _renderClusters(): TemplateResult { return html` `; @@ -226,45 +134,10 @@ export class ZHANode extends LitElement { this._showHelp = !this._showHelp; } - private _selectedNodeChanged(event: ItemSelectedEvent): void { - this._selectedNodeIndex = event!.target!.selected; - this._selectedNode = this._nodes[this._selectedNodeIndex]; - this._userSelectedName = ""; - fireEvent(this, "zha-node-selected", { node: this._selectedNode }); - this._serviceData = this._computeNodeServiceData(); - } - - private async _onReconfigureNodeClick(): Promise { - if (this.hass) { - await reconfigureNode(this.hass, this._selectedNode!.ieee); - } - } - - private _onUserSelectedNameChanged(value: ChangeEvent): void { - this._userSelectedName = value.detail!.value; - } - - private async _onUpdateDeviceNameClick(): Promise { - if (this.hass) { - const values: DeviceRegistryEntryMutableParams = { - name_by_user: this._userSelectedName, - }; - - await updateDeviceRegistryEntry( - this.hass, - this._selectedNode!.device_reg_id, - values - ); - - this._selectedNode!.user_given_name = this._userSelectedName!; - this._userSelectedName = ""; - } - } - - private _computeNodeServiceData(): NodeServiceData { - return { - ieee_address: this._selectedNode!.ieee, - }; + private _selectedDeviceChanged(event: ItemSelectedEvent): void { + this._selectedDeviceIndex = event!.target!.selected; + this._selectedDevice = this._nodes[this._selectedDeviceIndex]; + fireEvent(this, "zha-node-selected", { node: this._selectedDevice }); } private async _fetchDevices() { @@ -273,6 +146,13 @@ export class ZHANode extends LitElement { }); } + private _onDeviceRemoved(event: ZHADeviceRemovedEvent): void { + this._selectedDeviceIndex = -1; + this._nodes.splice(this._nodes.indexOf(event.detail!.device!), 1); + this._selectedDevice = undefined; + fireEvent(this, "zha-node-selected", { node: this._selectedDevice }); + } + static get styles(): CSSResult[] { return [ haStyle, @@ -298,13 +178,10 @@ export class ZHANode extends LitElement { } .help-text { + color: grey; padding-left: 28px; padding-right: 28px; - } - - .helpText { - color: grey; - padding: 16px; + padding-bottom: 16px; } paper-card { @@ -355,12 +232,6 @@ export class ZHANode extends LitElement { right: 0; color: var(--primary-color); } - - .input-text { - padding-left: 28px; - padding-right: 28px; - padding-bottom: 10px; - } `, ]; } @@ -371,5 +242,3 @@ declare global { "zha-node": ZHANode; } } - -customElements.define("zha-node", ZHANode); diff --git a/src/translations/en.json b/src/translations/en.json index 1ec7543e42..afd77b714c 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -877,7 +877,18 @@ "description": "Zigbee Home Automation network management", "services": { "reconfigure": "Reconfigure ZHA device (heal device). Use this if you are having issues with the device. If the device in question is a battery powered device please ensure it is awake and accepting commands when you use this service.", - "updateDeviceName": "Set a custom name for this device in the device registry." + "updateDeviceName": "Set a custom name for this device in the device registry.", + "remove": "Remove a device from the ZigBee network." + }, + "device_card": { + "device_name_placeholder": "User given name", + "area_picker_label": "Area", + "update_name_button": "Update Name" + }, + "add_device_page": { + "header": "Zigbee Home Automation - Add Devices", + "spinner": "Searching for ZHA Zigbee devices...", + "discovery_text": "Discovered devices will show up here. Follow the instructions for your device(s) and place the device(s) in pairing mode." } }, "zwave": {