Add UI for Z-Wave JS Device Reinterview (#8957)

This commit is contained in:
Charles Garwood 2021-04-27 16:30:15 -04:00 committed by GitHub
parent 36586b798e
commit 83e65e2cc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 342 additions and 1 deletions

View File

@ -1,3 +1,4 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { HomeAssistant } from "../types";
import { DeviceRegistryEntry } from "./device_registry";
@ -71,6 +72,11 @@ export interface ZWaveJSDataCollectionStatus {
opted_in: boolean;
}
export interface ZWaveJSRefreshNodeStatusMessage {
event: string;
stage?: string;
}
export enum NodeStatus {
Unknown,
Asleep,
@ -151,6 +157,22 @@ export const setNodeConfigParameter = (
return hass.callWS(data);
};
export const reinterviewNode = (
hass: HomeAssistant,
entry_id: string,
node_id: number,
callbackFunction: (message: ZWaveJSRefreshNodeStatusMessage) => void
): Promise<UnsubscribeFunc> => {
return hass.connection.subscribeMessage(
(message: any) => callbackFunction(message),
{
type: "zwave_js/refresh_node_info",
entry_id: entry_id,
node_id: node_id,
}
);
};
export const getIdentifiersFromDevice = function (
device: DeviceRegistryEntry
): ZWaveJSNodeIdentifiers | undefined {

View File

@ -11,9 +11,13 @@ import {
TemplateResult,
} from "lit-element";
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
import {
getIdentifiersFromDevice,
ZWaveJSNodeIdentifiers,
} from "../../../../../../data/zwave_js";
import { haStyle } from "../../../../../../resources/styles";
import { HomeAssistant } from "../../../../../../types";
import { showZWaveJSReinterviewNodeDialog } from "../../../../integrations/integration-panels/zwave_js/show-dialog-zwave_js-reinterview-node";
@customElement("ha-device-actions-zwave_js")
export class HaDeviceActionsZWaveJS extends LitElement {
@ -23,9 +27,19 @@ export class HaDeviceActionsZWaveJS extends LitElement {
@internalProperty() private _entryId?: string;
@internalProperty() private _nodeId?: number;
protected updated(changedProperties: PropertyValues) {
if (changedProperties.has("device")) {
this._entryId = this.device.config_entries[0];
const identifiers:
| ZWaveJSNodeIdentifiers
| undefined = getIdentifiersFromDevice(this.device);
if (!identifiers) {
return;
}
this._nodeId = identifiers.node_id;
}
}
@ -40,9 +54,22 @@ export class HaDeviceActionsZWaveJS extends LitElement {
)}
</mwc-button>
</a>
<mwc-button @click=${this._reinterviewClicked}
>Re-interview Device</mwc-button
>
`;
}
private async _reinterviewClicked() {
if (!this._nodeId || !this._entryId) {
return;
}
showZWaveJSReinterviewNodeDialog(this, {
entry_id: this._entryId,
node_id: this._nodeId,
});
}
static get styles(): CSSResult[] {
return [
haStyle,

View File

@ -0,0 +1,262 @@
import "@material/mwc-button/mwc-button";
import { mdiCheckCircle, mdiCloseCircle } from "@mdi/js";
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 { ZWaveJSReinterviewNodeDialogParams } from "./show-dialog-zwave_js-reinterview-node";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { reinterviewNode } from "../../../../../data/zwave_js";
@customElement("dialog-zwave_js-reinterview-node")
class DialogZWaveJSReinterviewNode extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@internalProperty() private entry_id?: string;
@internalProperty() private node_id?: number;
@internalProperty() private _status?: string;
@internalProperty() private _stages?: string[];
private _subscribed?: Promise<UnsubscribeFunc>;
public async showDialog(
params: ZWaveJSReinterviewNodeDialogParams
): Promise<void> {
this._stages = undefined;
this.entry_id = params.entry_id;
this.node_id = params.node_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.reinterview_node.title")
)}
>
${!this._status
? html`
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.reinterview_node.introduction"
)}
</p>
<p>
<em>
${this.hass.localize(
"ui.panel.config.zwave_js.reinterview_node.battery_device_warning"
)}
</em>
</p>
<mwc-button slot="primaryAction" @click=${this._startReinterview}>
${this.hass.localize(
"ui.panel.config.zwave_js.reinterview_node.start_reinterview"
)}
</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.reinterview_node.in_progress"
)}
</b>
</p>
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.reinterview_node.run_in_background"
)}
</p>
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
</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.reinterview_node.interview_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.reinterview_node.interview_complete"
)}
</p>
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
</mwc-button>
`
: ``}
${this._stages
? html`
<div class="stages">
${this._stages.map(
(stage) => html`
<span class="stage">
<ha-svg-icon
.path=${mdiCheckCircle}
class="success"
></ha-svg-icon>
${stage}
</span>
`
)}
</div>
`
: ""}
</ha-dialog>
`;
}
private _startReinterview(): void {
if (!this.hass) {
return;
}
this._subscribed = reinterviewNode(
this.hass,
this.entry_id!,
this.node_id!,
this._handleMessage.bind(this)
);
}
private _handleMessage(message: any): void {
if (message.event === "interview started") {
this._status = "started";
}
if (message.event === "interview stage completed") {
if (this._stages === undefined) {
this._stages = [message.stage];
} else {
this._stages = [...this._stages, message.stage];
}
}
if (message.event === "interview failed") {
this._unsubscribe();
this._status = "failed";
}
if (message.event === "interview completed") {
this._unsubscribe();
this._status = "finished";
}
}
private _unsubscribe(): void {
if (this._subscribed) {
this._subscribed.then((unsub) => unsub());
this._subscribed = undefined;
}
}
public closeDialog(): void {
this.entry_id = undefined;
this.node_id = undefined;
this._status = undefined;
this._stages = undefined;
this._unsubscribe();
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
static get styles(): CSSResult[] {
return [
haStyleDialog,
css`
.success {
color: var(--success-color);
}
.failed {
color: var(--warning-color);
}
.flex-container {
display: flex;
align-items: center;
}
.stages {
margin-top: 16px;
}
.stage ha-svg-icon {
width: 16px;
height: 16px;
}
.stage {
padding: 8px;
}
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-reinterview-node": DialogZWaveJSReinterviewNode;
}
}

View File

@ -0,0 +1,20 @@
import { fireEvent } from "../../../../../common/dom/fire_event";
export interface ZWaveJSReinterviewNodeDialogParams {
entry_id: string;
node_id: number;
}
export const loadReinterviewNodeDialog = () =>
import("./dialog-zwave_js-reinterview-node");
export const showZWaveJSReinterviewNodeDialog = (
element: HTMLElement,
reinterviewNodeDialogParams: ZWaveJSReinterviewNodeDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-zwave_js-reinterview-node",
dialogImport: loadReinterviewNodeDialog,
dialogParams: reinterviewNodeDialogParams,
});
};

View File

@ -2649,6 +2649,16 @@
"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."
},
"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.",
"battery_device_warning": "You will need to wake battery powered devices before starting the re-interview. Refer to your device's manual for instructions on how to wake the device.",
"run_in_background": "You can close this dialog and the interview will continue in the background.",
"start_reinterview": "Start Re-interview",
"in_progress": "The device is being interviewed. This may take some time.",
"interview_failed": "The device interview failed. Additional information may be available in the logs.",
"interview_complete": "Device interview complete."
}
}
},