diff --git a/src/data/zha.ts b/src/data/zha.ts index 3cd291e22d..c0382b00db 100644 --- a/src/data/zha.ts +++ b/src/data/zha.ts @@ -432,6 +432,15 @@ export const listZHANetworkBackups = ( type: "zha/network/backups/list", }); +export const changeZHANetworkChannel = ( + hass: HomeAssistant, + newChannel: "auto" | number +): Promise => + hass.callWS({ + type: "zha/network/change_channel", + new_channel: newChannel, + }); + export const INITIALIZED = "INITIALIZED"; export const INTERVIEW_COMPLETE = "INTERVIEW_COMPLETE"; export const CONFIGURED = "CONFIGURED"; diff --git a/src/panels/config/integrations/integration-panels/zha/dialog-zha-change-channel.ts b/src/panels/config/integrations/integration-panels/zha/dialog-zha-change-channel.ts new file mode 100644 index 0000000000..84f2c468ec --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zha/dialog-zha-change-channel.ts @@ -0,0 +1,149 @@ +import { html, LitElement, TemplateResult, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import { stopPropagation } from "../../../../../common/dom/stop_propagation"; +import { HassDialog } from "../../../../../dialogs/make-dialog-manager"; +import { changeZHANetworkChannel } from "../../../../../data/zha"; +import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box"; +import { createCloseHeading } from "../../../../../components/ha-dialog"; +import { HomeAssistant } from "../../../../../types"; +import "../../../../../components/buttons/ha-progress-button"; +import "../../../../../components/ha-button"; +import "../../../../../components/ha-select"; +import "../../../../../components/ha-list-item"; +import { ZHAChangeChannelDialogParams } from "./show-dialog-zha-change-channel"; + +const VALID_CHANNELS = [ + "auto", + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, +]; + +@customElement("dialog-zha-change-channel") +class DialogZHAChangeChannel extends LitElement implements HassDialog { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _migrationInProgress = false; + + @state() private _params?: ZHAChangeChannelDialogParams; + + @state() private _newChannel?: "auto" | number; + + public async showDialog(params: ZHAChangeChannelDialogParams): Promise { + this._params = params; + this._newChannel = params.currentChannel; + } + + public closeDialog(): void { + this._params = undefined; + this._newChannel = undefined; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + protected render(): TemplateResult | typeof nothing { + if (!this._params) { + return nothing; + } + + return html` + +

+ ${this.hass.localize( + "ui.panel.config.zha.change_channel_dialog.migration_warning" + )} +

+ +

+ + ${VALID_CHANNELS.map( + (newChannel) => + html`${newChannel}` + )} + +

+ + + ${this.hass.localize( + "ui.panel.config.zha.change_channel_dialog.change_channel" + )} + + + ${this.hass.localize("ui.dialogs.generic.cancel")} +
+ `; + } + + private _newChannelChosen(evt: Event): void { + const value: string = (evt.target! as HTMLSelectElement).value; + this._newChannel = value === "auto" ? "auto" : parseInt(value, 10); + } + + private async _changeNetworkChannel(): Promise { + try { + this._migrationInProgress = true; + await changeZHANetworkChannel(this.hass, this._newChannel!); + } finally { + this._migrationInProgress = false; + } + + await showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.zha.change_channel_dialog.channel_has_been_changed" + ), + text: this.hass.localize( + "ui.panel.config.zha.change_channel_dialog.devices_will_rejoin" + ), + }); + + this.closeDialog(); + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-zha-change-channel": DialogZHAChangeChannel; + } +} diff --git a/src/panels/config/integrations/integration-panels/zha/show-dialog-zha-change-channel.ts b/src/panels/config/integrations/integration-panels/zha/show-dialog-zha-change-channel.ts new file mode 100644 index 0000000000..2b5f9500f4 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/zha/show-dialog-zha-change-channel.ts @@ -0,0 +1,19 @@ +import { fireEvent } from "../../../../../common/dom/fire_event"; + +export interface ZHAChangeChannelDialogParams { + currentChannel: number; +} + +export const loadZHAChangeChannelDialog = () => + import("./dialog-zha-change-channel"); + +export const showZHAChangeChannelDialog = ( + element: HTMLElement, + zhaChangeChannelParams: ZHAChangeChannelDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-zha-change-channel", + dialogImport: loadZHAChangeChannelDialog, + dialogParams: zhaChangeChannelParams, + }); +}; diff --git a/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts b/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts index cf63246e92..7176894b84 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts @@ -1,5 +1,11 @@ import "@material/mwc-button/mwc-button"; -import { mdiFolderMultipleOutline, mdiLan, mdiNetwork, mdiPlus } from "@mdi/js"; +import { + mdiFolderMultipleOutline, + mdiLan, + mdiNetwork, + mdiPlus, + mdiPencil, +} from "@mdi/js"; import { css, CSSResultGroup, @@ -16,6 +22,7 @@ import { import { computeRTL } from "../../../../../common/util/compute_rtl"; import "../../../../../components/ha-card"; import "../../../../../components/ha-fab"; +import "../../../../../components/ha-icon-button"; import { fileDownload } from "../../../../../util/file_download"; import "../../../../../components/ha-icon-next"; import "../../../../../layouts/hass-tabs-subpage"; @@ -26,6 +33,8 @@ import type { HomeAssistant, Route } from "../../../../../types"; import "../../../ha-config-section"; import "../../../../../components/ha-form/ha-form"; import "../../../../../components/buttons/ha-progress-button"; +import "../../../../../components/ha-settings-row"; +import { showZHAChangeChannelDialog } from "./show-dialog-zha-change-channel"; import { fetchZHAConfiguration, updateZHAConfiguration, @@ -126,31 +135,52 @@ class ZHAConfigDashboard extends LitElement { )} > ${this._networkSettings - ? html`
-
- PAN ID: - ${this._networkSettings.settings.network_info.pan_id} -
-
- Extended PAN ID: - ${this._networkSettings.settings.network_info.extended_pan_id} -
-
- Channel: - ${this._networkSettings.settings.network_info.channel} -
-
- Coordinator IEEE: - ${this._networkSettings.settings.node_info.ieee} -
-
- Network key: - ${this._networkSettings.settings.network_info.network_key.key} -
-
- Radio type: - ${this._networkSettings.radio_type} -
+ ? html`
+ + PAN ID + ${this._networkSettings.settings.network_info.pan_id} + + + + ${this._networkSettings.settings.network_info + .extended_pan_id} + Extended PAN ID + + + + Channel + ${this._networkSettings.settings.network_info + .channel} + + + + + + + Coordinator IEEE + ${this._networkSettings.settings.node_info.ieee} + + + + Radio type + ${this._networkSettings.radio_type} +
` : ""}
@@ -224,6 +254,12 @@ class ZHAConfigDashboard extends LitElement { this._networkSettings = await fetchZHANetworkSettings(this.hass!); } + private async _showChannelMigrationDialog(): Promise { + showZHAChangeChannelDialog(this, { + currentChannel: this._networkSettings!.settings.network_info.channel, + }); + } + private async _createAndDownloadBackup(): Promise { let backup_and_metadata: ZHANetworkBackupAndMetadata; @@ -310,10 +346,16 @@ class ZHAConfigDashboard extends LitElement { margin-top: 2px; } - .network-settings > .card-actions { - display: flex; - justify-content: space-between; - align-items: center; + .network-settings ha-settings-row { + padding-left: 0; + padding-right: 0; + + --paper-item-body-two-line-min-height: 55px; + } + + .network-settings ha-settings-row ha-icon-button { + margin-top: -16px; + margin-bottom: -16px; } `, ]; diff --git a/src/translations/en.json b/src/translations/en.json index 8b2cf3530d..ad9c5f4b24 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3463,7 +3463,8 @@ "update_button": "Update Configuration", "download_backup": "Download Backup", "migrate_radio": "Migrate Radio", - "network_settings_title": "Network Settings" + "network_settings_title": "Network Settings", + "change_channel": "Change channel" }, "add_device_page": { "spinner": "Searching for Zigbee devices…", @@ -3557,6 +3558,14 @@ "lqi": "LQI", "relationship": "Relationship", "depth": "Depth" + }, + "change_channel_dialog": { + "title": "Change network channel", + "new_channel": "New channel", + "change_channel": "Change channel", + "migration_warning": "Zigbee channel migration is an experimental feature and relies on devices on your network to support it. Device support for this feature varies and only a portion of your network may end up migrating!", + "channel_has_been_changed": "Network channel has been changed", + "devices_will_rejoin": "Devices will re-join the network over time. This may take a few minutes." } }, "zwave_js": {