mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-24 13:27:22 +00:00
Reconfigure ZHA device take 2 (#8990)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
7304544c37
commit
9690434cac
@ -55,6 +55,52 @@ export interface Cluster {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface ClusterConfigurationData {
|
||||
cluster_name: string;
|
||||
cluster_id: number;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface ClusterAttributeData {
|
||||
cluster_name: string;
|
||||
cluster_id: number;
|
||||
attributes: AttributeConfigurationStatus[];
|
||||
}
|
||||
|
||||
export interface AttributeConfigurationStatus {
|
||||
id: number;
|
||||
name: string;
|
||||
success: boolean | undefined;
|
||||
min: number;
|
||||
max: number;
|
||||
change: number;
|
||||
}
|
||||
|
||||
export interface ClusterConfigurationStatus {
|
||||
cluster: Cluster;
|
||||
bindSuccess: boolean | undefined;
|
||||
attributes: Map<number, AttributeConfigurationStatus>;
|
||||
}
|
||||
|
||||
interface ClusterConfigurationBindEvent {
|
||||
type: "zha_channel_bind";
|
||||
zha_channel_msg_data: ClusterConfigurationData;
|
||||
}
|
||||
|
||||
interface ClusterConfigurationReportConfigurationEvent {
|
||||
type: "zha_channel_configure_reporting";
|
||||
zha_channel_msg_data: ClusterAttributeData;
|
||||
}
|
||||
|
||||
interface ClusterConfigurationEventFinish {
|
||||
type: "zha_channel_cfg_done";
|
||||
}
|
||||
|
||||
export type ClusterConfigurationEvent =
|
||||
| ClusterConfigurationReportConfigurationEvent
|
||||
| ClusterConfigurationBindEvent
|
||||
| ClusterConfigurationEventFinish;
|
||||
|
||||
export interface Command {
|
||||
name: string;
|
||||
id: number;
|
||||
@ -89,10 +135,10 @@ export interface ZHAGroupMember {
|
||||
export const reconfigureNode = (
|
||||
hass: HomeAssistant,
|
||||
ieeeAddress: string,
|
||||
callbackFunction: any
|
||||
callbackFunction: (message: ClusterConfigurationEvent) => void
|
||||
) => {
|
||||
return hass.connection.subscribeMessage(
|
||||
(message) => callbackFunction(message),
|
||||
(message: ClusterConfigurationEvent) => callbackFunction(message),
|
||||
{
|
||||
type: "zha/devices/reconfigure",
|
||||
ieee: ieeeAddress,
|
||||
@ -323,3 +369,7 @@ export const DEVICE_MESSAGE_TYPES = [
|
||||
DEVICE_FULLY_INITIALIZED,
|
||||
];
|
||||
export const LOG_OUTPUT = "log_output";
|
||||
export const ZHA_CHANNEL_MSG = "zha_channel_message";
|
||||
export const ZHA_CHANNEL_MSG_BIND = "zha_channel_bind";
|
||||
export const ZHA_CHANNEL_MSG_CFG_RPT = "zha_channel_configure_reporting";
|
||||
export const ZHA_CHANNEL_CFG_DONE = "zha_channel_cfg_done";
|
||||
|
@ -8,42 +8,62 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||
import { mdiCheckCircle, mdiCloseCircle } from "@mdi/js";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { haStyleDialog } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { ZHAReconfigureDeviceDialogParams } from "./show-dialog-zha-reconfigure-device";
|
||||
import { IronAutogrowTextareaElement } from "@polymer/iron-autogrow-textarea";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import "../../../../../components/ha-circular-progress";
|
||||
import { LOG_OUTPUT, reconfigureNode } from "../../../../../data/zha";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import {
|
||||
AttributeConfigurationStatus,
|
||||
Cluster,
|
||||
ClusterConfigurationEvent,
|
||||
ClusterConfigurationStatus,
|
||||
fetchClustersForZhaNode,
|
||||
reconfigureNode,
|
||||
ZHA_CHANNEL_CFG_DONE,
|
||||
ZHA_CHANNEL_MSG_BIND,
|
||||
ZHA_CHANNEL_MSG_CFG_RPT,
|
||||
} from "../../../../../data/zha";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||
|
||||
@customElement("dialog-zha-reconfigure-device")
|
||||
class DialogZHAReconfigureDevice extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@internalProperty() private _active = false;
|
||||
@internalProperty() private _status?: string;
|
||||
|
||||
@internalProperty() private _formattedEvents = "";
|
||||
@internalProperty() private _stages?: string[];
|
||||
|
||||
@internalProperty()
|
||||
private _params: ZHAReconfigureDeviceDialogParams | undefined = undefined;
|
||||
@internalProperty() private _clusterConfigurationStatuses?: Map<
|
||||
number,
|
||||
ClusterConfigurationStatus
|
||||
> = new Map();
|
||||
|
||||
private _subscribed?: Promise<() => Promise<void>>;
|
||||
@internalProperty() private _params:
|
||||
| ZHAReconfigureDeviceDialogParams
|
||||
| undefined = undefined;
|
||||
|
||||
private _reconfigureDeviceTimeoutHandle: any = undefined;
|
||||
@internalProperty() private _allSuccessful = true;
|
||||
|
||||
public async showDialog(
|
||||
params: ZHAReconfigureDeviceDialogParams
|
||||
): Promise<void> {
|
||||
@internalProperty() private _showDetails = false;
|
||||
|
||||
private _subscribed?: Promise<UnsubscribeFunc>;
|
||||
|
||||
public showDialog(params: ZHAReconfigureDeviceDialogParams): void {
|
||||
this._params = params;
|
||||
this._subscribe(params);
|
||||
this._stages = undefined;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._unsubscribe();
|
||||
this._formattedEvents = "";
|
||||
this._params = undefined;
|
||||
this._status = undefined;
|
||||
this._stages = undefined;
|
||||
this._clusterConfigurationStatuses = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
@ -51,58 +71,311 @@ class DialogZHAReconfigureDevice extends LitElement {
|
||||
if (!this._params) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
hideActions
|
||||
@closing="${this.closeDialog}"
|
||||
@closed="${this.closeDialog}"
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize(`ui.dialogs.zha_reconfigure_device.heading`)
|
||||
this.hass.localize(`ui.dialogs.zha_reconfigure_device.heading`) +
|
||||
": " +
|
||||
(this._params?.device.user_given_name || this._params?.device.name)
|
||||
)}
|
||||
>
|
||||
<div class="searching">
|
||||
${this._active
|
||||
? html`
|
||||
<h1>
|
||||
${this._params?.device.user_given_name ||
|
||||
this._params?.device.name}
|
||||
</h1>
|
||||
<ha-circular-progress
|
||||
active
|
||||
alt="Searching"
|
||||
></ha-circular-progress>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<paper-textarea
|
||||
readonly
|
||||
max-rows="10"
|
||||
class="log"
|
||||
value="${this._formattedEvents}"
|
||||
>
|
||||
</paper-textarea>
|
||||
${!this._status
|
||||
? html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.zha_reconfigure_device.introduction"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<em>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.zha_reconfigure_device.battery_device_warning"
|
||||
)}
|
||||
</em>
|
||||
</p>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click=${this._startReconfiguration}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.zha_reconfigure_device.start_reconfiguration"
|
||||
)}
|
||||
</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.dialogs.zha_reconfigure_device.in_progress"
|
||||
)}
|
||||
</b>
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.zha_reconfigure_device.run_in_background"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.dialogs.generic.close")}
|
||||
</mwc-button>
|
||||
<mwc-button slot="secondaryAction" @click=${this._toggleDetails}>
|
||||
${this._showDetails
|
||||
? this.hass.localize(
|
||||
`ui.dialogs.zha_reconfigure_device.button_hide`
|
||||
)
|
||||
: this.hass.localize(
|
||||
`ui.dialogs.zha_reconfigure_device.button_show`
|
||||
)}
|
||||
</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.dialogs.zha_reconfigure_device.configuration_failed"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.dialogs.generic.close")}
|
||||
</mwc-button>
|
||||
<mwc-button slot="secondaryAction" @click=${this._toggleDetails}>
|
||||
${this._showDetails
|
||||
? this.hass.localize(
|
||||
`ui.dialogs.zha_reconfigure_device.button_hide`
|
||||
)
|
||||
: this.hass.localize(
|
||||
`ui.dialogs.zha_reconfigure_device.button_show`
|
||||
)}
|
||||
</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.dialogs.zha_reconfigure_device.configuration_complete"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.dialogs.generic.close")}
|
||||
</mwc-button>
|
||||
<mwc-button slot="secondaryAction" @click=${this._toggleDetails}>
|
||||
${this._showDetails
|
||||
? this.hass.localize(
|
||||
`ui.dialogs.zha_reconfigure_device.button_hide`
|
||||
)
|
||||
: this.hass.localize(
|
||||
`ui.dialogs.zha_reconfigure_device.button_show`
|
||||
)}
|
||||
</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>
|
||||
`
|
||||
: ""}
|
||||
${this._showDetails
|
||||
? html`
|
||||
<div class="wrapper">
|
||||
<h2 class="grid-item">
|
||||
${this.hass.localize(
|
||||
`ui.dialogs.zha_reconfigure_device.cluster_header`
|
||||
)}
|
||||
</h2>
|
||||
<h2 class="grid-item">
|
||||
${this.hass.localize(
|
||||
`ui.dialogs.zha_reconfigure_device.bind_header`
|
||||
)}
|
||||
</h2>
|
||||
<h2 class="grid-item">
|
||||
${this.hass.localize(
|
||||
`ui.dialogs.zha_reconfigure_device.reporting_header`
|
||||
)}
|
||||
</h2>
|
||||
|
||||
${this._clusterConfigurationStatuses!.size > 0
|
||||
? html`
|
||||
${Array.from(
|
||||
this._clusterConfigurationStatuses!.values()
|
||||
).map(
|
||||
(clusterStatus) => html`
|
||||
<div class="grid-item">
|
||||
${clusterStatus.cluster.name}
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
${clusterStatus.bindSuccess !== undefined
|
||||
? clusterStatus.bindSuccess
|
||||
? html`
|
||||
<span class="stage">
|
||||
<ha-svg-icon
|
||||
.path=${mdiCheckCircle}
|
||||
class="success"
|
||||
></ha-svg-icon>
|
||||
</span>
|
||||
`
|
||||
: html`
|
||||
<span class="stage">
|
||||
<ha-svg-icon
|
||||
.path=${mdiCloseCircle}
|
||||
class="failed"
|
||||
></ha-svg-icon>
|
||||
</span>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
${clusterStatus.attributes.size > 0
|
||||
? html`
|
||||
<div class="attributes">
|
||||
<div class="grid-item">
|
||||
${this.hass.localize(
|
||||
`ui.dialogs.zha_reconfigure_device.attribute`
|
||||
)}
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
`ui.dialogs.zha_reconfigure_device.min_max_change`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
${Array.from(
|
||||
clusterStatus.attributes.values()
|
||||
).map(
|
||||
(attribute) => html`
|
||||
<span class="grid-item">
|
||||
${attribute.name}:
|
||||
${attribute.success
|
||||
? html`
|
||||
<span class="stage">
|
||||
<ha-svg-icon
|
||||
.path=${mdiCheckCircle}
|
||||
class="success"
|
||||
></ha-svg-icon>
|
||||
</span>
|
||||
`
|
||||
: html`
|
||||
<span class="stage">
|
||||
<ha-svg-icon
|
||||
.path=${mdiCloseCircle}
|
||||
class="failed"
|
||||
></ha-svg-icon>
|
||||
</span>
|
||||
`}
|
||||
</span>
|
||||
<div class="grid-item">
|
||||
${attribute.min}/${attribute.max}/${attribute.change}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleMessage(message: any): void {
|
||||
if (message.type === LOG_OUTPUT) {
|
||||
this._formattedEvents += message.log_entry.message + "\n";
|
||||
const paperTextArea = this.shadowRoot!.querySelector("paper-textarea");
|
||||
if (paperTextArea) {
|
||||
const textArea = (paperTextArea.inputElement as IronAutogrowTextareaElement)
|
||||
.textarea;
|
||||
textArea.scrollTop = textArea.scrollHeight;
|
||||
private async _startReconfiguration(): Promise<void> {
|
||||
if (!this.hass || !this._params) {
|
||||
return;
|
||||
}
|
||||
this._clusterConfigurationStatuses = new Map(
|
||||
(await fetchClustersForZhaNode(this.hass, this._params.device.ieee)).map(
|
||||
(cluster: Cluster) => [
|
||||
cluster.id,
|
||||
{
|
||||
cluster: cluster,
|
||||
bindSuccess: undefined,
|
||||
attributes: new Map<number, AttributeConfigurationStatus>(),
|
||||
},
|
||||
]
|
||||
)
|
||||
);
|
||||
this._subscribe(this._params);
|
||||
this._status = "started";
|
||||
}
|
||||
|
||||
private _handleMessage(message: ClusterConfigurationEvent): void {
|
||||
if (message.type === ZHA_CHANNEL_CFG_DONE) {
|
||||
this._unsubscribe();
|
||||
this._status = this._allSuccessful ? "finished" : "failed";
|
||||
} else {
|
||||
const clusterConfigurationStatus = this._clusterConfigurationStatuses!.get(
|
||||
message.zha_channel_msg_data.cluster_id
|
||||
);
|
||||
if (message.type === ZHA_CHANNEL_MSG_BIND) {
|
||||
if (!this._stages) {
|
||||
this._stages = ["binding"];
|
||||
}
|
||||
const success = message.zha_channel_msg_data.success;
|
||||
clusterConfigurationStatus!.bindSuccess = success;
|
||||
this._allSuccessful = this._allSuccessful && success;
|
||||
}
|
||||
if (message.type === ZHA_CHANNEL_MSG_CFG_RPT) {
|
||||
if (this._stages && !this._stages.includes("reporting")) {
|
||||
this._stages.push("reporting");
|
||||
}
|
||||
const attributes = message.zha_channel_msg_data.attributes;
|
||||
Object.keys(attributes).forEach((name) => {
|
||||
const attribute = attributes[name];
|
||||
clusterConfigurationStatus!.attributes.set(attribute.id, attribute);
|
||||
this._allSuccessful = this._allSuccessful && attribute.success;
|
||||
});
|
||||
}
|
||||
this.requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private _unsubscribe(): void {
|
||||
this._active = false;
|
||||
if (this._reconfigureDeviceTimeoutHandle) {
|
||||
clearTimeout(this._reconfigureDeviceTimeoutHandle);
|
||||
}
|
||||
if (this._subscribed) {
|
||||
this._subscribed.then((unsub) => unsub());
|
||||
this._subscribed = undefined;
|
||||
@ -113,33 +386,66 @@ class DialogZHAReconfigureDevice extends LitElement {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
this._active = true;
|
||||
this._subscribed = reconfigureNode(
|
||||
this.hass,
|
||||
params.device.ieee,
|
||||
this._handleMessage.bind(this)
|
||||
);
|
||||
this._reconfigureDeviceTimeoutHandle = setTimeout(
|
||||
() => this._unsubscribe(),
|
||||
60000
|
||||
);
|
||||
}
|
||||
|
||||
private _toggleDetails() {
|
||||
this._showDetails = !this._showDetails;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-circular-progress {
|
||||
padding: 20px;
|
||||
.wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 1fr 2fr;
|
||||
}
|
||||
.searching {
|
||||
margin-top: 20px;
|
||||
.attributes {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
.grid-item {
|
||||
border: 1px solid;
|
||||
padding: 7px;
|
||||
}
|
||||
.success {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.failed {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.log {
|
||||
padding: 16px;
|
||||
|
||||
.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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -760,7 +760,22 @@
|
||||
"update": "Update"
|
||||
},
|
||||
"zha_reconfigure_device": {
|
||||
"heading": "Reconfiguring device"
|
||||
"heading": "Reconfiguring device",
|
||||
"configuring_alt": "Configuring",
|
||||
"introduction": "Reconfigure a device on your Zigbee network. Use this feature if your device is not functioning correctly.",
|
||||
"battery_device_warning": "You will need to wake battery powered devices before starting the reconfiguration process. Refer to your device's manual for instructions on how to wake the device.",
|
||||
"run_in_background": "You can close this dialog and the reconfiguration will continue in the background.",
|
||||
"start_reconfiguration": "Start Reconfiguration",
|
||||
"in_progress": "The device is being reconfigured. This may take some time.",
|
||||
"configuration_failed": "The device reconfiguration failed. Additional information may be available in the logs.",
|
||||
"configuration_complete": "Device reconfiguration complete.",
|
||||
"button_show": "Show Details",
|
||||
"button_hide": "Hide Details",
|
||||
"cluster_header": "Cluster",
|
||||
"bind_header": "Binding",
|
||||
"reporting_header": "Reporting",
|
||||
"attribute": "Attribute",
|
||||
"min_max_change": "min/max/change"
|
||||
},
|
||||
"zha_device_info": {
|
||||
"manuf": "by {manufacturer}",
|
||||
|
Loading…
x
Reference in New Issue
Block a user