Add wizards for adding and removing Z-Wave JS nodes (#8174)

This commit is contained in:
Charles Garwood 2021-01-25 04:46:50 -05:00 committed by GitHub
parent d228f38471
commit a3339c9d5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 661 additions and 2 deletions

View File

@ -0,0 +1,307 @@
import "@material/mwc-button/mwc-button";
import { mdiCheckCircle, mdiCloseCircle } from "@mdi/js";
import "../../../../../components/ha-switch";
import "../../../../../components/ha-formfield";
import {
CSSResult,
customElement,
html,
LitElement,
property,
internalProperty,
TemplateResult,
css,
} from "lit-element";
import "../../../../../components/ha-circular-progress";
import { createCloseHeading } from "../../../../../components/ha-dialog";
import { haStyleDialog } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types";
import { ZWaveJSAddNodeDialogParams } from "./show-dialog-zwave_js-add-node";
import { fireEvent } from "../../../../../common/dom/fire_event";
export interface ZWaveJSAddNodeDevice {
id: string;
name: string;
}
@customElement("dialog-zwave_js-add-node")
class DialogZWaveJSAddNode extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@internalProperty() private entry_id?: string;
@internalProperty() private _use_secure_inclusion = false;
@internalProperty() private _status = "";
@internalProperty() private _device?: ZWaveJSAddNodeDevice;
private _addNodeTimeoutHandle?: number;
private _subscribed?: Promise<() => Promise<void>>;
public disconnectedCallback(): void {
super.disconnectedCallback();
this._unsubscribe();
}
public async showDialog(params: ZWaveJSAddNodeDialogParams): Promise<void> {
this.entry_id = params.entry_id;
}
protected render(): TemplateResult {
if (!this.entry_id) {
return html``;
}
return html`
<ha-dialog
open
@closed="${this.closeDialog}"
.heading=${createCloseHeading(
this.hass,
this.hass.localize("ui.panel.config.zwave_js.add_node.title")
)}
>
${this._status === ""
? html`
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.add_node.introduction"
)}
</p>
<div class="secure_inclusion_field">
<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.zwave_js.add_node.use_secure_inclusion"
)}
>
<ha-switch
@change=${this._secureInclusionToggleChanged}
.checked=${this._use_secure_inclusion}
></ha-switch>
</ha-formfield>
<p>
<em>
<small>
${this.hass!.localize(
"ui.panel.config.zwave_js.add_node.secure_inclusion_warning"
)}
</small>
</em>
</p>
</div>
<mwc-button slot="primaryAction" @click=${this._startInclusion}>
${this._use_secure_inclusion
? html`${this.hass.localize(
"ui.panel.config.zwave_js.add_node.start_secure_inclusion"
)}`
: html` ${this.hass.localize(
"ui.panel.config.zwave_js.add_node.start_inclusion"
)}`}
</mwc-button>
`
: ``}
${this._status === "started"
? html`
<div class="flex-container">
<ha-circular-progress active></ha-circular-progress>
<div class="status">
<p>
<b
>${this.hass.localize(
"ui.panel.config.zwave_js.add_node.controller_in_inclusion_mode"
)}</b
>
</p>
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.add_node.follow_device_instructions"
)}
</p>
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize(
"ui.panel.config.zwave_js.add_node.cancel_inclusion"
)}
</mwc-button>
`
: ``}
${this._status === "failed"
? html`
<div class="flex-container">
<ha-svg-icon
.path=${mdiCloseCircle}
class="failed"
></ha-svg-icon>
<div class="status">
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.add_node.inclusion_failed"
)}
</p>
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
</mwc-button>
`
: ``}
${this._status === "finished"
? html`
<div class="flex-container">
<ha-svg-icon
.path=${mdiCheckCircle}
class="success"
></ha-svg-icon>
<div class="status">
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.add_node.inclusion_finished"
)}
</p>
<a href="${`/config/devices/device/${this._device!.id}`}">
<mwc-button>
${this.hass.localize(
"ui.panel.config.zwave_js.add_node.view_device"
)}
</mwc-button>
</a>
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
</mwc-button>
`
: ``}
</ha-dialog>
`;
}
private async _secureInclusionToggleChanged(ev): Promise<void> {
const target = ev.target;
this._use_secure_inclusion = target.checked;
}
private _startInclusion(): void {
if (!this.hass) {
return;
}
this._subscribed = this.hass.connection.subscribeMessage(
(message) => this._handleMessage(message),
{
type: "zwave_js/add_node",
entry_id: this.entry_id,
secure: this._use_secure_inclusion,
}
);
this._addNodeTimeoutHandle = window.setTimeout(
() => this._unsubscribe(),
90000
);
}
private _handleMessage(message: any): void {
if (message.event === "inclusion started") {
this._status = "started";
}
if (message.event === "inclusion failed") {
this._unsubscribe();
this._status = "failed";
}
if (message.event === "inclusion stopped") {
if (this._status !== "finished") {
this._status = "";
}
this._unsubscribe();
}
if (message.event === "device registered") {
this._device = message.device;
this._status = "finished";
this._unsubscribe();
}
}
private _unsubscribe(): void {
if (this._subscribed) {
this._subscribed.then((unsub) => unsub());
this._subscribed = undefined;
}
if (this._status === "started") {
this.hass.callWS({
type: "zwave_js/stop_inclusion",
entry_id: this.entry_id,
});
}
if (this._status !== "finished") {
this._status = "";
}
if (this._addNodeTimeoutHandle) {
clearTimeout(this._addNodeTimeoutHandle);
}
}
public closeDialog(): void {
this._unsubscribe();
this.entry_id = undefined;
this._status = "";
this._device = undefined;
this._use_secure_inclusion = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
static get styles(): CSSResult[] {
return [
haStyleDialog,
css`
.secure_inclusion_field {
margin-top: 48px;
}
.success {
color: green;
}
.failed {
color: red;
}
blockquote {
display: block;
background-color: #ddd;
padding: 8px;
margin: 8px 0;
font-size: 0.9em;
}
blockquote em {
font-size: 0.9em;
margin-top: 6px;
}
.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-add-node": DialogZWaveJSAddNode;
}
}

View File

@ -0,0 +1,264 @@
import "@material/mwc-button/mwc-button";
import {
CSSResult,
customElement,
html,
LitElement,
property,
internalProperty,
TemplateResult,
css,
} from "lit-element";
import "../../../../../components/ha-circular-progress";
import { createCloseHeading } from "../../../../../components/ha-dialog";
import { haStyleDialog } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types";
import { ZWaveJSRemoveNodeDialogParams } from "./show-dialog-zwave_js-remove-node";
import { mdiCheckCircle, mdiCloseCircle } from "@mdi/js";
import { fireEvent } from "../../../../../common/dom/fire_event";
export interface ZWaveJSRemovedNode {
node_id: number;
manufacturer: string;
label: string;
}
@customElement("dialog-zwave_js-remove-node")
class DialogZWaveJSRemoveNode extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@internalProperty() private entry_id?: string;
@internalProperty() private _status = "";
@internalProperty() private _node?: ZWaveJSRemovedNode;
private _removeNodeTimeoutHandle?: number;
private _subscribed?: Promise<() => Promise<void>>;
public disconnectedCallback(): void {
super.disconnectedCallback();
this._unsubscribe();
}
public async showDialog(
params: ZWaveJSRemoveNodeDialogParams
): Promise<void> {
this.entry_id = params.entry_id;
}
protected render(): TemplateResult {
if (!this.entry_id) {
return html``;
}
return html`
<ha-dialog
open
@closed="${this.closeDialog}"
.heading=${createCloseHeading(
this.hass,
this.hass.localize("ui.panel.config.zwave_js.remove_node.title")
)}
>
${this._status === ""
? html`
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.remove_node.introduction"
)}
</p>
<mwc-button slot="primaryAction" @click=${this._startExclusion}>
${this.hass.localize(
"ui.panel.config.zwave_js.remove_node.start_exclusion"
)}
</mwc-button>
`
: ``}
${this._status === "started"
? html`
<div class="flex-container">
<ha-circular-progress active></ha-circular-progress>
<div class="status">
<p>
<b
>${this.hass.localize(
"ui.panel.config.zwave_js.remove_node.controller_in_exclusion_mode"
)}</b
>
</p>
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.remove_node.follow_device_instructions"
)}
</p>
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize(
"ui.panel.config.zwave_js.remove_node.cancel_exclusion"
)}
</mwc-button>
`
: ``}
${this._status === "failed"
? html`
<div class="flex-container">
<ha-svg-icon
.path=${mdiCloseCircle}
class="failed"
></ha-svg-icon>
<div class="status">
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.remove_node.exclusion_failed"
)}
</p>
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
</mwc-button>
`
: ``}
${this._status === "finished"
? html`
<div class="flex-container">
<ha-svg-icon
.path=${mdiCheckCircle}
class="success"
></ha-svg-icon>
<div class="status">
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.remove_node.exclusion_finished",
"id",
this._node!.node_id
)}
</p>
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
</mwc-button>
`
: ``}
</ha-dialog>
`;
}
private _startExclusion(): void {
if (!this.hass) {
return;
}
this._subscribed = this.hass.connection.subscribeMessage(
(message) => this._handleMessage(message),
{
type: "zwave_js/remove_node",
entry_id: this.entry_id,
}
);
this._removeNodeTimeoutHandle = window.setTimeout(
() => this._unsubscribe(),
120000
);
}
private _handleMessage(message: any): void {
if (message.event === "exclusion started") {
this._status = "started";
}
if (message.event === "exclusion failed") {
this._unsubscribe();
this._status = "failed";
}
if (message.event === "exclusion stopped") {
if (this._status !== "finished") {
this._status = "";
}
this._unsubscribe();
}
if (message.event === "node removed") {
this._status = "finished";
this._node = message.node;
this._unsubscribe();
}
}
private _unsubscribe(): void {
if (this._subscribed) {
this._subscribed.then((unsub) => unsub());
this._subscribed = undefined;
}
if (this._status === "started") {
this.hass.callWS({
type: "zwave_js/stop_exclusion",
entry_id: this.entry_id,
});
}
if (this._status !== "finished") {
this._status = "";
}
if (this._removeNodeTimeoutHandle) {
clearTimeout(this._removeNodeTimeoutHandle);
}
}
public closeDialog(): void {
this._unsubscribe();
this.entry_id = undefined;
this._status = "";
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
static get styles(): CSSResult[] {
return [
haStyleDialog,
css`
.success {
color: green;
}
.failed {
color: red;
}
blockquote {
display: block;
background-color: #ddd;
padding: 8px;
margin: 8px 0;
font-size: 0.9em;
}
blockquote em {
font-size: 0.9em;
margin-top: 6px;
}
.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-node": DialogZWaveJSRemoveNode;
}
}

View File

@ -0,0 +1,18 @@
import { fireEvent } from "../../../../../common/dom/fire_event";
export interface ZWaveJSAddNodeDialogParams {
entry_id: string;
}
export const loadAddNodeDialog = () => import("./dialog-zwave_js-add-node");
export const showZWaveJSAddNodeDialog = (
element: HTMLElement,
addNodeDialogParams: ZWaveJSAddNodeDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-zwave_js-add-node",
dialogImport: loadAddNodeDialog,
dialogParams: addNodeDialogParams,
});
};

View File

@ -0,0 +1,19 @@
import { fireEvent } from "../../../../../common/dom/fire_event";
export interface ZWaveJSRemoveNodeDialogParams {
entry_id: string;
}
export const loadRemoveNodeDialog = () =>
import("./dialog-zwave_js-remove-node");
export const showZWaveJSRemoveNodeDialog = (
element: HTMLElement,
removeNodeDialogParams: ZWaveJSRemoveNodeDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-zwave_js-remove-node",
dialogImport: loadRemoveNodeDialog,
dialogParams: removeNodeDialogParams,
});
};

View File

@ -21,6 +21,8 @@ import "../../../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section";
import { showZWaveJSAddNodeDialog } from "./show-dialog-zwave_js-add-node";
import { showZWaveJSRemoveNodeDialog } from "./show-dialog-zwave_js-remove-node";
import { configTabs } from "./zwave_js-config-router";
@customElement("zwave_js-config-dashboard")
@ -137,6 +139,16 @@ class ZWaveJSConfigDashboard extends LitElement {
)}
</mwc-button>
</a>
<mwc-button @click=${this._addNodeClicked}>
${this.hass.localize(
"ui.panel.config.zwave_js.common.add_node"
)}
</mwc-button>
<mwc-button @click=${this._removeNodeClicked}>
${this.hass.localize(
"ui.panel.config.zwave_js.common.remove_node"
)}
</mwc-button>
</div>
</ha-card>
`
@ -155,6 +167,18 @@ class ZWaveJSConfigDashboard extends LitElement {
}
}
private async _addNodeClicked() {
showZWaveJSAddNodeDialog(this, {
entry_id: this.configEntryId!,
});
}
private async _removeNodeClicked() {
showZWaveJSRemoveNodeDialog(this, {
entry_id: this.configEntryId!,
});
}
static get styles(): CSSResultArray {
return [
haStyle,

View File

@ -2387,7 +2387,10 @@
"common": {
"network": "Network",
"node_id": "Node ID",
"home_id": "Home ID"
"home_id": "Home ID",
"close": "Close",
"add_node": "Add Node",
"remove_node": "Remove Node"
},
"dashboard": {
"header": "Manage your Z-Wave Network",
@ -2413,6 +2416,30 @@
"connected": "Connected",
"connecting": "Connecting",
"unknown": "Unknown"
},
"add_node": {
"title": "Add a Z-Wave Node",
"introduction": "This wizard will guide you through adding a node to your Z-Wave network.",
"use_secure_inclusion": "Use secure inclusion",
"secure_inclusion_warning": "Secure devices require additional bandwidth; too many secure devices can slow down your Z-Wave network. We recommend only using secure inclusion for devices that require it, like locks or garage door openers.",
"start_inclusion": "Start Inclusion",
"start_secure_inclusion": "Start Secure Inclusion",
"cancel_inclusion": "Cancel Inclusion",
"controller_in_inclusion_mode": "Your Z-Wave controller is now in inclusion mode.",
"follow_device_instructions": "Follow the directions that came with your device to trigger pairing on the device.",
"inclusion_failed": "The node could not be added. Please check the logs for more information.",
"inclusion_finished": "The node has been added. It may take a few minutes for all entities to show up as we finish setting up the node in the background.",
"view_device": "View Device"
},
"remove_node": {
"title": "Remove a Z-Wave Node",
"introduction": "Remove a node from your Z-Wave network, and remove the associated device and entities from Home Assistant.",
"start_exclusion": "Start Exclusion",
"cancel_exclusion": "Cancel Exclusion",
"controller_in_exclusion_mode": "Your Z-Wave controller is now in exclusion mode.",
"follow_device_instructions": "Follow the directions that came with your device to trigger exclusion on the device.",
"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."
}
}
},