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": {