diff --git a/src/data/zha.ts b/src/data/zha.ts index 5f4cb3aa18..96c8166a24 100644 --- a/src/data/zha.ts +++ b/src/data/zha.ts @@ -27,6 +27,7 @@ export interface ZHADevice { device_type: string; signature: any; neighbors: Neighbor[]; + pairing_status?: string; } export interface Neighbor { @@ -270,3 +271,23 @@ export const addGroup = ( group_name: groupName, members: membersToAdd, }); + +export const INITIALIZED = "INITIALIZED"; +export const INTERVIEW_COMPLETE = "INTERVIEW_COMPLETE"; +export const CONFIGURED = "CONFIGURED"; +export const PAIRED = "PAIRED"; +export const INCOMPLETE_PAIRING_STATUSES = [ + PAIRED, + CONFIGURED, + INTERVIEW_COMPLETE, +]; + +export const DEVICE_JOINED = "device_joined"; +export const RAW_DEVICE_INITIALIZED = "raw_device_initialized"; +export const DEVICE_FULLY_INITIALIZED = "device_fully_initialized"; +export const DEVICE_MESSAGE_TYPES = [ + DEVICE_JOINED, + RAW_DEVICE_INITIALIZED, + DEVICE_FULLY_INITIALIZED, +]; +export const LOG_OUTPUT = "log_output"; diff --git a/src/panels/config/integrations/integration-panels/zha/zha-add-devices-page.ts b/src/panels/config/integrations/integration-panels/zha/zha-add-devices-page.ts index 6c2fdcc897..7f56d95d9e 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-add-devices-page.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-add-devices-page.ts @@ -14,13 +14,17 @@ import { } from "lit-element"; import "../../../../../components/ha-service-description"; import "@polymer/paper-input/paper-textarea"; -import { ZHADevice } from "../../../../../data/zha"; import "../../../../../layouts/hass-tabs-subpage"; import { haStyle } from "../../../../../resources/styles"; import { HomeAssistant, Route } from "../../../../../types"; -import "./zha-device-card"; +import "./zha-device-pairing-status-card"; import { zhaTabs } from "./zha-config-dashboard"; import { IronAutogrowTextareaElement } from "@polymer/iron-autogrow-textarea"; +import { + DEVICE_MESSAGE_TYPES, + LOG_OUTPUT, + ZHADevice, +} from "../../../../../data/zha"; @customElement("zha-add-devices-page") class ZHAAddDevicesPage extends LitElement { @@ -34,7 +38,10 @@ class ZHAAddDevicesPage extends LitElement { @internalProperty() private _error?: string; - @internalProperty() private _discoveredDevices: ZHADevice[] = []; + @internalProperty() private _discoveredDevices: Record< + string, + ZHADevice + > = {}; @internalProperty() private _formattedEvents = ""; @@ -64,7 +71,7 @@ class ZHAAddDevicesPage extends LitElement { super.disconnectedCallback(); this._unsubscribe(); this._error = undefined; - this._discoveredDevices = []; + this._discoveredDevices = {}; this._formattedEvents = ""; } @@ -115,7 +122,7 @@ class ZHAAddDevicesPage extends LitElement { ${this._error ? html`
${this._error}
` : ""}
- ${this._discoveredDevices.length < 1 + ${Object.keys(this._discoveredDevices).length < 1 ? html`

@@ -133,15 +140,15 @@ class ZHAAddDevicesPage extends LitElement {

` : html` - ${this._discoveredDevices.map( + ${Object.values(this._discoveredDevices).map( (device) => html` - + > ` )} `} @@ -164,7 +171,7 @@ class ZHAAddDevicesPage extends LitElement { } private _handleMessage(message: any): void { - if (message.type === "log_output") { + if (message.type === LOG_OUTPUT) { this._formattedEvents += message.log_entry.message + "\n"; if (this.shadowRoot) { const paperTextArea = this.shadowRoot.querySelector("paper-textarea"); @@ -175,8 +182,8 @@ class ZHAAddDevicesPage extends LitElement { } } } - if (message.type && message.type === "device_fully_initialized") { - this._discoveredDevices.push(message.device_info); + if (message.type && DEVICE_MESSAGE_TYPES.includes(message.type)) { + this._discoveredDevices[message.device_info.ieee] = message.device_info; } } diff --git a/src/panels/config/integrations/integration-panels/zha/zha-device-pairing-status-card.ts b/src/panels/config/integrations/integration-panels/zha/zha-device-pairing-status-card.ts new file mode 100644 index 0000000000..c6b4181e85 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zha/zha-device-pairing-status-card.ts @@ -0,0 +1,147 @@ +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-listbox/paper-listbox"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + internalProperty, + TemplateResult, +} from "lit-element"; +import "../../../../../components/buttons/ha-call-service-button"; +import "../../../../../components/entity/state-badge"; +import "../../../../../components/ha-card"; +import "../../../../../components/ha-service-description"; +import { + CONFIGURED, + INCOMPLETE_PAIRING_STATUSES, + INITIALIZED, + INTERVIEW_COMPLETE, + ZHADevice, +} from "../../../../../data/zha"; +import { haStyle } from "../../../../../resources/styles"; +import { HomeAssistant } from "../../../../../types"; +import "../../../../../components/ha-area-picker"; +import { formatAsPaddedHex } from "./functions"; +import "./zha-device-card"; +import { classMap } from "lit-html/directives/class-map"; + +@customElement("zha-device-pairing-status-card") +class ZHADevicePairingStatusCard extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public device?: ZHADevice; + + @property({ type: Boolean }) public narrow?: boolean; + + @internalProperty() private _showHelp = false; + + protected render(): TemplateResult { + if (!this.hass || !this.device) { + return html``; + } + + return html` +
+

+ ${this.hass!.localize( + `ui.panel.config.zha.device_pairing_card.${this.device.pairing_status}` + )} +

+

+ ${this.hass!.localize( + `ui.panel.config.zha.device_pairing_card.${this.device.pairing_status}_status_text` + )} +

+
+
+ ${[INTERVIEW_COMPLETE, CONFIGURED].includes( + this.device.pairing_status! + ) + ? html` +
${this.device.model}
+
+ ${this.hass.localize( + "ui.dialogs.zha_device_info.manuf", + "manufacturer", + this.device.manufacturer + )} +
+ ` + : html``} +
+ ${INCOMPLETE_PAIRING_STATUSES.includes(this.device.pairing_status!) + ? html` +
IEEE: ${this.device.ieee}
+
+ NWK: ${formatAsPaddedHex(this.device.nwk)} +
+ ` + : html``} +
+ ${this.device.pairing_status === INITIALIZED + ? html` + + ` + : html``} +
+
+ `; + } + + static get styles(): CSSResult[] { + return [ + haStyle, + css` + .discovered { + --ha-card-border-color: var(--primary-color); + } + .discovered.initialized { + --ha-card-border-color: var(--success-color); + } + .discovered .header { + background: var(--primary-color); + color: var(--text-primary-color); + padding: 8px; + text-align: center; + margin-bottom: 20px; + } + .discovered.initialized .header { + background: var(--success-color); + } + h1 { + margin: 0; + } + h4 { + margin: 0; + } + .text, + .manuf, + .model { + color: var(--secondary-text-color); + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "zha-device-pairing-status-card": ZHADevicePairingStatusCard; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index eceb15c8e3..e1902f4fc3 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2198,6 +2198,16 @@ "issue_zigbee_command": "Issue Zigbee Command", "help_command_dropdown": "Select a command to interact with." }, + "device_pairing_card": { + "PAIRED": "Device Found", + "PAIRED_status_text": "Starting Interview", + "INTERVIEW_COMPLETE": "Interview Complete", + "INTERVIEW_COMPLETE_status_text": "Configuring", + "CONFIGURED": "Configuration Complete", + "CONFIGURED_status_text": "Initializing", + "INITIALIZED": "Initialization Complete", + "INITIALIZED_status_text": "The device is ready to use" + }, "network": { "caption": "Network" },