From 8cd9f891fb054aaaca622a82a63c2982ce16dfd2 Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Wed, 28 Jul 2021 09:38:50 -0400 Subject: [PATCH] Add remove failed node support to Z-Wave JS devices (#9560) Co-authored-by: Bram Kragten --- src/data/zwave_js.ts | 21 ++ .../zwave_js/ha-device-actions-zwave_js.ts | 16 ++ .../dialog-zwave_js-remove-failed-node.ts | 237 ++++++++++++++++++ ...show-dialog-zwave_js-remove-failed-node.ts | 20 ++ src/translations/en.json | 11 +- 5 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-remove-failed-node.ts create mode 100644 src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-failed-node.ts diff --git a/src/data/zwave_js.ts b/src/data/zwave_js.ts index 3219480313..b443d62fbc 100644 --- a/src/data/zwave_js.ts +++ b/src/data/zwave_js.ts @@ -83,6 +83,12 @@ export interface ZWaveJSHealNetworkStatusMessage { heal_node_status: { [key: number]: string }; } +export interface ZWaveJSRemovedNode { + node_id: number; + manufacturer: string; + label: string; +} + export enum NodeStatus { Unknown, Asleep, @@ -189,6 +195,21 @@ export const healNode = ( node_id: node_id, }); +export const removeFailedNode = ( + hass: HomeAssistant, + entry_id: string, + node_id: number, + callbackFunction: (message: any) => void +): Promise => + hass.connection.subscribeMessage( + (message: any) => callbackFunction(message), + { + type: "zwave_js/remove_failed_node", + entry_id: entry_id, + node_id: node_id, + } + ); + export const healNetwork = ( hass: HomeAssistant, entry_id: string diff --git a/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts b/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts index 461a6ab387..cd6b6e5f7d 100644 --- a/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts +++ b/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts @@ -17,6 +17,7 @@ import { haStyle } from "../../../../../../resources/styles"; import { HomeAssistant } from "../../../../../../types"; import { showZWaveJSReinterviewNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-reinterview-node"; import { showZWaveJSHealNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-heal-node"; +import { showZWaveJSRemoveFailedNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-failed-node"; @customElement("ha-device-actions-zwave_js") export class HaDeviceActionsZWaveJS extends LitElement { @@ -60,6 +61,11 @@ export class HaDeviceActionsZWaveJS extends LitElement { ${this.hass.localize("ui.panel.config.zwave_js.device_info.heal_node")} + + ${this.hass.localize( + "ui.panel.config.zwave_js.device_info.remove_failed" + )} + `; } @@ -84,6 +90,16 @@ export class HaDeviceActionsZWaveJS extends LitElement { }); } + private async _removeFailedNode() { + if (!this._nodeId || !this._entryId) { + return; + } + showZWaveJSRemoveFailedNodeDialog(this, { + entry_id: this._entryId, + node_id: this._nodeId, + }); + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-remove-failed-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-remove-failed-node.ts new file mode 100644 index 0000000000..ca9cce32f6 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-remove-failed-node.ts @@ -0,0 +1,237 @@ +import "@material/mwc-button/mwc-button"; +import { mdiCheckCircle, mdiCloseCircle, mdiRobotDead } from "@mdi/js"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import "../../../../../components/ha-circular-progress"; +import { createCloseHeading } from "../../../../../components/ha-dialog"; +import { + removeFailedNode, + ZWaveJSRemovedNode, +} from "../../../../../data/zwave_js"; +import { haStyleDialog } from "../../../../../resources/styles"; +import { HomeAssistant } from "../../../../../types"; +import { ZWaveJSRemoveFailedNodeDialogParams } from "./show-dialog-zwave_js-remove-failed-node"; + +@customElement("dialog-zwave_js-remove-failed-node") +class DialogZWaveJSRemoveFailedNode extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private entry_id?: string; + + @state() private node_id?: number; + + @state() private _status = ""; + + @state() private _error?: any; + + @state() private _node?: ZWaveJSRemovedNode; + + private _subscribed?: Promise; + + public disconnectedCallback(): void { + super.disconnectedCallback(); + this._unsubscribe(); + } + + public async showDialog( + params: ZWaveJSRemoveFailedNodeDialogParams + ): Promise { + this.entry_id = params.entry_id; + this.node_id = params.node_id; + } + + public closeDialog(): void { + this._unsubscribe(); + this.entry_id = undefined; + this._status = ""; + + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + public closeDialogFinished(): void { + history.back(); + this.closeDialog(); + } + + protected render(): TemplateResult { + if (!this.entry_id || !this.node_id) { + return html``; + } + + return html` + + ${this._status === "" + ? html` +
+ +
+ ${this.hass.localize( + "ui.panel.config.zwave_js.remove_failed_node.introduction" + )} +
+
+ + ${this.hass.localize( + "ui.panel.config.zwave_js.remove_failed_node.remove_device" + )} + + ` + : ``} + ${this._status === "started" + ? html` +
+ +
+

+ + ${this.hass.localize( + "ui.panel.config.zwave_js.remove_failed_node.in_progress" + )} + +

+
+
+ ` + : ``} + ${this._status === "failed" + ? html` +
+ +
+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.remove_failed_node.removal_failed" + )} +

+ ${this._error + ? html`

${this._error.message}

` + : ``} +
+
+ + ${this.hass.localize("ui.common.close")} + + ` + : ``} + ${this._status === "finished" + ? html` +
+ +
+

+ ${this.hass.localize( + "ui.panel.config.zwave_js.remove_failed_node.removal_finished", + "id", + this._node!.node_id + )} +

+
+
+ + ${this.hass.localize("ui.common.close")} + + ` + : ``} +
+ `; + } + + private _startExclusion(): void { + if (!this.hass) { + return; + } + this._status = "started"; + this._subscribed = removeFailedNode( + this.hass, + this.entry_id!, + this.node_id!, + (message: any) => this._handleMessage(message) + ).catch((error) => { + this._status = "failed"; + this._error = error; + }); + } + + private _handleMessage(message: any): void { + if (message.event === "exclusion started") { + this._status = "started"; + } + if (message.event === "node removed") { + this._status = "finished"; + this._node = message.node; + this._unsubscribe(); + } + } + + private async _unsubscribe(): Promise { + if (this._subscribed) { + const unsubFunc = await this._subscribed; + if (unsubFunc instanceof Function) { + unsubFunc(); + } + this._subscribed = undefined; + } + if (this._status !== "finished") { + this._status = ""; + } + } + + static get styles(): CSSResultGroup { + return [ + haStyleDialog, + css` + .success { + color: var(--success-color); + } + + .failed { + color: var(--warning-color); + } + + .flex-container { + display: flex; + align-items: center; + } + + ha-svg-icon { + width: 68px; + height: 48px; + } + + .flex-container ha-circular-progress, + .flex-container ha-svg-icon { + margin-right: 20px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-zwave_js-remove-failed-node": DialogZWaveJSRemoveFailedNode; + } +} diff --git a/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-failed-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-failed-node.ts new file mode 100644 index 0000000000..e64f0ee483 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-remove-failed-node.ts @@ -0,0 +1,20 @@ +import { fireEvent } from "../../../../../common/dom/fire_event"; + +export interface ZWaveJSRemoveFailedNodeDialogParams { + entry_id: string; + node_id: number; +} + +export const loadRemoveFailedNodeDialog = () => + import("./dialog-zwave_js-remove-failed-node"); + +export const showZWaveJSRemoveFailedNodeDialog = ( + element: HTMLElement, + removeFailedNodeDialogParams: ZWaveJSRemoveFailedNodeDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-zwave_js-remove-failed-node", + dialogImport: loadRemoveFailedNodeDialog, + dialogParams: removeFailedNodeDialogParams, + }); +}; diff --git a/src/translations/en.json b/src/translations/en.json index 39ebb12067..13d6484223 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2691,7 +2691,8 @@ "node_ready": "Node Ready", "device_config": "Configure Device", "reinterview_device": "Re-interview Device", - "heal_node": "Heal Node" + "heal_node": "Heal Node", + "remove_failed": "Remove Failed Device" }, "node_config": { "header": "Z-Wave Device Configuration", @@ -2744,6 +2745,14 @@ "exclusion_failed": "The node could not be removed. Please check the logs for more information.", "exclusion_finished": "Node {id} has been removed from your Z-Wave network." }, + "remove_failed_node": { + "title": "Remove a Failed Z-Wave Device", + "introduction": "Remove a failed device from your Z-Wave network. Use this if you are unable to exclude a device normally because it is broken.", + "remove_device": "Remove Device", + "in_progress": "The device removal is in progress.", + "removal_finished": "Node {id} has been removed from your Z-Wave network.", + "removal_failed": "The device could not be removed from your Z-Wave network." + }, "reinterview_node": { "title": "Re-interview a Z-Wave Device", "introduction": "Re-interview a device on your Z-Wave network. Use this feature if your device has missing or incorrect functionality.",