Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/data/zwave_js.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { HomeAssistant } from "../types";
import { DeviceRegistryEntry } from "./device_registry";

Expand Down Expand Up @@ -71,6 +72,11 @@ export interface ZWaveJSDataCollectionStatus {
opted_in: boolean;
}

export interface ZWaveJSRefreshNodeStatusMessage {
event: string;
stage?: string;
}

export enum NodeStatus {
Unknown,
Asleep,
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
}
}

Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Comment thread
cgarwood marked this conversation as resolved.
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;
}
}
Original file line number Diff line number Diff line change
@@ -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,
});
};
10 changes: 10 additions & 0 deletions src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2646,6 +2646,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."
}
}
},
Expand Down