Add migration wizard for zwave -> zwave_js (#10097)

This commit is contained in:
Bram Kragten 2021-09-29 17:55:20 +02:00 committed by GitHub
parent a89da0dac0
commit 8721776839
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 376 additions and 314 deletions

View File

@ -73,14 +73,6 @@ export interface OZWDeviceConfig {
help: string; help: string;
} }
export interface OZWMigrationData {
migration_device_map: Record<string, string>;
zwave_entity_ids: string[];
ozw_entity_ids: string[];
migration_entity_map: Record<string, string>;
migrated: boolean;
}
export const nodeQueryStages = [ export const nodeQueryStages = [
"ProtocolInfo", "ProtocolInfo",
"Probe", "Probe",
@ -219,12 +211,3 @@ export const refreshNodeInfo = (
ozw_instance, ozw_instance,
node_id, node_id,
}); });
export const migrateZwave = (
hass: HomeAssistant,
dry_run = true
): Promise<OZWMigrationData> =>
hass.callWS({
type: "ozw/migrate_zwave",
dry_run,
});

View File

@ -60,11 +60,11 @@ export const fetchNetworkStatus = (
type: "zwave/network_status", type: "zwave/network_status",
}); });
export const startOzwConfigFlow = ( export const startZwaveJsConfigFlow = (
hass: HomeAssistant hass: HomeAssistant
): Promise<{ flow_id: string }> => ): Promise<{ flow_id: string }> =>
hass.callWS({ hass.callWS({
type: "zwave/start_ozw_config_flow", type: "zwave/start_zwave_js_config_flow",
}); });
export const fetchMigrationConfig = ( export const fetchMigrationConfig = (

View File

@ -174,6 +174,25 @@ export interface RequestedGrant {
export const nodeStatus = ["unknown", "asleep", "awake", "dead", "alive"]; export const nodeStatus = ["unknown", "asleep", "awake", "dead", "alive"];
export interface ZWaveJsMigrationData {
migration_device_map: Record<string, string>;
zwave_entity_ids: string[];
zwave_js_entity_ids: string[];
migration_entity_map: Record<string, string>;
migrated: boolean;
}
export const migrateZwave = (
hass: HomeAssistant,
entry_id: string,
dry_run = true
): Promise<ZWaveJsMigrationData> =>
hass.callWS({
type: "zwave_js/migrate_zwave",
entry_id,
dry_run,
});
export const fetchNetworkStatus = ( export const fetchNetworkStatus = (
hass: HomeAssistant, hass: HomeAssistant,
entry_id: string entry_id: string
@ -307,8 +326,8 @@ export const reinterviewNode = (
(message: any) => callbackFunction(message), (message: any) => callbackFunction(message),
{ {
type: "zwave_js/refresh_node_info", type: "zwave_js/refresh_node_info",
entry_id: entry_id, entry_id,
node_id: node_id, node_id,
} }
); );
@ -319,8 +338,8 @@ export const healNode = (
): Promise<boolean> => ): Promise<boolean> =>
hass.callWS({ hass.callWS({
type: "zwave_js/heal_node", type: "zwave_js/heal_node",
entry_id: entry_id, entry_id,
node_id: node_id, node_id,
}); });
export const removeFailedNode = ( export const removeFailedNode = (
@ -333,8 +352,8 @@ export const removeFailedNode = (
(message: any) => callbackFunction(message), (message: any) => callbackFunction(message),
{ {
type: "zwave_js/remove_failed_node", type: "zwave_js/remove_failed_node",
entry_id: entry_id, entry_id,
node_id: node_id, node_id,
} }
); );
@ -344,7 +363,7 @@ export const healNetwork = (
): Promise<UnsubscribeFunc> => ): Promise<UnsubscribeFunc> =>
hass.callWS({ hass.callWS({
type: "zwave_js/begin_healing_network", type: "zwave_js/begin_healing_network",
entry_id: entry_id, entry_id,
}); });
export const stopHealNetwork = ( export const stopHealNetwork = (
@ -353,9 +372,24 @@ export const stopHealNetwork = (
): Promise<UnsubscribeFunc> => ): Promise<UnsubscribeFunc> =>
hass.callWS({ hass.callWS({
type: "zwave_js/stop_healing_network", type: "zwave_js/stop_healing_network",
entry_id: entry_id, entry_id,
}); });
export const subscribeNodeReady = (
hass: HomeAssistant,
entry_id: string,
node_id: number,
callbackFunction: (message) => void
): Promise<UnsubscribeFunc> =>
hass.connection.subscribeMessage(
(message: any) => callbackFunction(message),
{
type: "zwave_js/node_ready",
entry_id,
node_id,
}
);
export const subscribeHealNetworkProgress = ( export const subscribeHealNetworkProgress = (
hass: HomeAssistant, hass: HomeAssistant,
entry_id: string, entry_id: string,
@ -365,7 +399,7 @@ export const subscribeHealNetworkProgress = (
(message: any) => callbackFunction(message), (message: any) => callbackFunction(message),
{ {
type: "zwave_js/subscribe_heal_network_progress", type: "zwave_js/subscribe_heal_network_progress",
entry_id: entry_id, entry_id,
} }
); );

View File

@ -104,28 +104,14 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
<ha-config-section is-wide="[[isWide]]"> <ha-config-section is-wide="[[isWide]]">
<ha-card <ha-card
class="content" class="content"
header="[[localize('ui.panel.config.zwave.migration.ozw.header')]]" header="[[localize('ui.panel.config.zwave.migration.zwave_js.header')]]"
> >
<div class="card-content"> <div class="card-content">
<p> [[localize('ui.panel.config.zwave.migration.zwave_js.introduction')]]
If you are experiencing problems with your Z-Wave devices, you
can migrate to the newer OZW integration, that is currently in
beta.
</p>
<p>
Be aware that the future of OZW is not guaranteed, as the
development has stopped.
</p>
<p>
If you are currently not experiencing issues with your Z-Wave
devices, we recommend you to wait for the successor of the OZW
integration, Z-Wave JS, that is in active development at the
moment.
</p>
</div> </div>
<div class="card-actions"> <div class="card-actions">
<a href="/config/zwave/migration" <a href="/config/zwave/migration"
><mwc-button>Start Migration to OZW</mwc-button></a ><mwc-button>Start Migration to Z-Wave JS</mwc-button></a
> >
</div> </div>
</ha-card> </ha-card>

View File

@ -6,7 +6,6 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../../../common/config/is_component_loaded";
import { computeStateName } from "../../../../../common/entity/compute_state_name"; import { computeStateName } from "../../../../../common/entity/compute_state_name";
import { navigate } from "../../../../../common/navigate";
import "../../../../../components/buttons/ha-call-api-button"; import "../../../../../components/buttons/ha-call-api-button";
import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card"; import "../../../../../components/ha-card";
@ -15,16 +14,25 @@ import "../../../../../components/ha-icon";
import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-icon-button";
import { import {
computeDeviceName, computeDeviceName,
DeviceRegistryEntry,
fetchDeviceRegistry,
subscribeDeviceRegistry, subscribeDeviceRegistry,
} from "../../../../../data/device_registry"; } from "../../../../../data/device_registry";
import { migrateZwave, OZWMigrationData } from "../../../../../data/ozw"; import {
migrateZwave,
ZWaveJsMigrationData,
fetchNetworkStatus as fetchZwaveJsNetworkStatus,
fetchNodeStatus,
getIdentifiersFromDevice,
subscribeNodeReady,
} from "../../../../../data/zwave_js";
import { import {
fetchMigrationConfig, fetchMigrationConfig,
fetchNetworkStatus, startZwaveJsConfigFlow,
startOzwConfigFlow,
ZWaveMigrationConfig, ZWaveMigrationConfig,
ZWaveNetworkStatus, ZWaveNetworkStatus,
ZWAVE_NETWORK_STATE_STOPPED, ZWAVE_NETWORK_STATE_STOPPED,
fetchNetworkStatus,
} from "../../../../../data/zwave"; } from "../../../../../data/zwave";
import { showConfigFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-config-flow"; import { showConfigFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-config-flow";
import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
@ -32,6 +40,8 @@ import "../../../../../layouts/hass-subpage";
import { haStyle } from "../../../../../resources/styles"; import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types"; import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section"; import "../../../ha-config-section";
import { computeStateDomain } from "../../../../../common/entity/compute_state_domain";
import "../../../../../components/ha-alert";
@customElement("zwave-migration") @customElement("zwave-migration")
export class ZwaveMigration extends LitElement { export class ZwaveMigration extends LitElement {
@ -51,12 +61,18 @@ export class ZwaveMigration extends LitElement {
@state() private _migrationConfig?: ZWaveMigrationConfig; @state() private _migrationConfig?: ZWaveMigrationConfig;
@state() private _migrationData?: OZWMigrationData; @state() private _migrationData?: ZWaveJsMigrationData;
@state() private _migratedZwaveEntities?: string[]; @state() private _migratedZwaveEntities?: string[];
@state() private _deviceNameLookup: { [id: string]: string } = {}; @state() private _deviceNameLookup: { [id: string]: string } = {};
@state() private _waitingOnDevices?: DeviceRegistryEntry[];
private _zwaveJsEntryId?: string;
private _nodeReadySubscriptions?: Promise<UnsubscribeFunc>[];
private _unsub?: Promise<UnsubscribeFunc>; private _unsub?: Promise<UnsubscribeFunc>;
private _unsubDevices?: UnsubscribeFunc; private _unsubDevices?: UnsubscribeFunc;
@ -79,56 +95,43 @@ export class ZwaveMigration extends LitElement {
> >
<ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}> <ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}>
<div slot="header"> <div slot="header">
${this.hass.localize("ui.panel.config.zwave.migration.ozw.header")} ${this.hass.localize(
"ui.panel.config.zwave.migration.zwave_js.header"
)}
</div> </div>
<div slot="introduction"> <div slot="introduction">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.zwave.migration.ozw.introduction" "ui.panel.config.zwave.migration.zwave_js.introduction"
)} )}
</div> </div>
${!isComponentLoaded(this.hass, "hassio") && ${html`
!isComponentLoaded(this.hass, "mqtt")
? html`
<ha-card class="content" header="MQTT Required">
<div class="card-content">
<p>
OpenZWave requires MQTT. Please setup an MQTT broker and
the MQTT integration to proceed with the migration.
</p>
</div>
</ha-card>
`
: html`
${this._step === 0 ${this._step === 0
? html` ? html`
<ha-card class="content" header="Introduction"> <ha-card class="content" header="Introduction">
<div class="card-content"> <div class="card-content">
<p> <p>
This wizard will walk through the following steps to This wizard will walk through the following steps to
migrate from the legacy Z-Wave integration to migrate from the legacy Z-Wave integration to Z-Wave JS.
OpenZWave.
</p> </p>
<ol> <ol>
<li>Stop the Z-Wave network</li> <li>Stop the Z-Wave network</li>
${!isComponentLoaded(this.hass, "hassio")
? html`<li>Configure and start Z-Wave JS</li>`
: ""}
<li>Set up the Z-Wave JS integration</li>
<li> <li>
<i Migrate entities and devices to the new integration
>If running Home Assistant Core in Docker or in
Python venv:</i
>
Configure and start OZWDaemon
</li>
<li>Set up the OpenZWave integration</li>
<li>
Migrate entities and devices to the new
integration
</li> </li>
<li>Remove legacy Z-Wave integration</li> <li>Remove legacy Z-Wave integration</li>
</ol> </ol>
<p> <p>
<b> <b>
Please take a backup of your environment before ${isComponentLoaded(this.hass, "hassio")
proceeding. ? html`Please
<a href="/hassio/backups">make a backup</a>
before proceeding.`
: "Please make a backup of your installation before proceeding."}
</b> </b>
</p> </p>
</div> </div>
@ -139,16 +142,31 @@ export class ZwaveMigration extends LitElement {
</div> </div>
</ha-card> </ha-card>
` `
: ``} : this._step === 1
${this._step === 1
? html` ? html`
<ha-card class="content" header="Stop Z-Wave Network"> <ha-card class="content" header="Stop Z-Wave Network">
<div class="card-content"> <div class="card-content">
<p> <p>
We need to stop the Z-Wave network to perform the We need to stop the Z-Wave network to perform the
migration. Home Assistant will not be able to migration. Home Assistant will not be able to control
control Z-Wave devices while the network is stopped. Z-Wave devices while the network is stopped.
</p> </p>
${Object.values(this.hass.states)
.filter(
(entityState) =>
computeStateDomain(entityState) === "zwave" &&
entityState.state !== "ready"
)
.map(
(entityState) =>
html`<ha-alert alert-type="warning">
Device ${computeStateName(entityState)}
(${entityState.entity_id}) is not ready yet! For
the best result, wake the device up if it is
battery powered and wait for this device to become
ready.
</ha-alert>`
)}
${this._stoppingNetwork ${this._stoppingNetwork
? html` ? html`
<div class="flex-container"> <div class="flex-container">
@ -167,63 +185,59 @@ export class ZwaveMigration extends LitElement {
</div> </div>
</ha-card> </ha-card>
` `
: ``} : this._step === 2
${this._step === 2
? html` ? html`
<ha-card class="content" header="Set up OZWDaemon"> <ha-card class="content" header="Set up Z-Wave JS">
<div class="card-content"> <div class="card-content">
<p>Now it's time to set up the OZW integration.</p> <p>Now it's time to set up the Z-Wave JS integration.</p>
${isComponentLoaded(this.hass, "hassio") ${isComponentLoaded(this.hass, "hassio")
? html` ? html`
<p> <p>
The OZWDaemon runs in a Home Assistant addon Z-Wave JS runs as a Home Assistant add-on that
that will be setup next. Make sure to check will be setup next. Make sure to check the
the checkbox for the addon. checkbox to use the add-on.
</p> </p>
` `
: html` : html`
<p> <p>
If you're using Home Assistant Core in Docker You are not running Home Assistant OS (the default
or a Python venv, see the installation type) or Home Assistant Supervised,
so we can not setup Z-Wave JS automaticaly. Follow
the
<a <a
href="https://github.com/OpenZWave/qt-openzwave/blob/master/README.md" href="https://www.home-assistant.io/integrations/zwave_js/#advanced-installation-instructions"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
>advanced installation instructions</a
> >
OZWDaemon readme to install Z-Wave JS.
</a>
for setup instructions.
</p> </p>
<p> <p>
Here's the current Z-Wave configuration. Here's the current Z-Wave configuration. You'll
You'll need these values when setting up OZW need these values when setting up Z-Wave JS.
daemon.
</p> </p>
${this._migrationConfig ${this._migrationConfig
? html`<blockquote> ? html`<blockquote>
USB Path: USB Path: ${this._migrationConfig.usb_path}<br />
${this._migrationConfig.usb_path}<br />
Network Key: Network Key:
${this._migrationConfig.network_key} ${this._migrationConfig.network_key}
</blockquote>` </blockquote>`
: ``} : ``}
<p> <p>
Once OZWDaemon is installed, running, and Once Z-Wave JS is installed and running, click
connected to the MQTT broker click Continue to 'Continue' to set up the Z-Wave JS integration and
set up the OpenZWave integration and migrate migrate your devices and entities.
your devices and entities.
</p> </p>
`} `}
</div> </div>
<div class="card-actions"> <div class="card-actions">
<mwc-button @click=${this._setupOzw}> <mwc-button @click=${this._setupZwaveJs}>
Continue Continue
</mwc-button> </mwc-button>
</div> </div>
</ha-card> </ha-card>
` `
: ``} : this._step === 3
${this._step === 3
? html` ? html`
<ha-card <ha-card
class="content" class="content"
@ -231,22 +245,33 @@ export class ZwaveMigration extends LitElement {
> >
<div class="card-content"> <div class="card-content">
<p> <p>
Now it's time to migrate your devices and entities Now it's time to migrate your devices and entities from
from the legacy Z-Wave integration to the OZW the legacy Z-Wave integration to the Z-Wave JS
integration, to make sure all your UI and integration, to make sure all your UI's and automations
automations keep working. keep working.
</p> </p>
${this._waitingOnDevices?.map(
(device) =>
html`<ha-alert alert-type="warning">
Device ${computeDeviceName(device, this.hass)} is
not ready yet! For the best result, wake the device
up if it is battery powered and wait for this device
to become ready.
</ha-alert>`
)}
${this._migrationData ${this._migrationData
? html` ? html`
<p>Below is a list of what will be migrated.</p> <p>Below is a list of what will be migrated.</p>
${this._migratedZwaveEntities!.length !== ${this._migratedZwaveEntities!.length !==
this._migrationData.zwave_entity_ids.length this._migrationData.zwave_entity_ids.length
? html`<h3 class="warning"> ? html`<ha-alert
Not all entities can be migrated! The alert-type="warning"
following entities will not be migrated title="Not all entities can be migrated!"
and might need manual adjustments to >
your config: The following entities will not be migrated
</h3> and might need manual adjustments to your
config:
</ha-alert>
<ul> <ul>
${this._migrationData.zwave_entity_ids.map( ${this._migrationData.zwave_entity_ids.map(
(entity_id) => (entity_id) =>
@ -254,9 +279,11 @@ export class ZwaveMigration extends LitElement {
entity_id entity_id
) )
? html`<li> ? html`<li>
${computeStateName( ${entity_id in this.hass.states
? computeStateName(
this.hass.states[entity_id] this.hass.states[entity_id]
)} )
: ""}
(${entity_id}) (${entity_id})
</li>` </li>`
: "" : ""
@ -269,14 +296,12 @@ export class ZwaveMigration extends LitElement {
? html`<h3>Devices that will be migrated:</h3> ? html`<h3>Devices that will be migrated:</h3>
<ul> <ul>
${Object.keys( ${Object.keys(
this._migrationData this._migrationData.migration_device_map
.migration_device_map
).map( ).map(
(device_id) => (device_id) =>
html`<li> html`<li>
${this._deviceNameLookup[ ${this._deviceNameLookup[device_id] ||
device_id device_id}
] || device_id}
</li>` </li>`
)} )}
</ul>` </ul>`
@ -284,18 +309,17 @@ export class ZwaveMigration extends LitElement {
${Object.keys( ${Object.keys(
this._migrationData.migration_entity_map this._migrationData.migration_entity_map
).length ).length
? html`<h3> ? html`<h3>Entities that will be migrated:</h3>
Entities that will be migrated:
</h3>
<ul> <ul>
${Object.keys( ${Object.keys(
this._migrationData this._migrationData.migration_entity_map
.migration_entity_map
).map( ).map(
(entity_id) => html`<li> (entity_id) => html`<li>
${computeStateName( ${entity_id in this.hass.states
? computeStateName(
this.hass.states[entity_id] this.hass.states[entity_id]
)} )
: ""}
(${entity_id}) (${entity_id})
</li>` </li>`
)} )}
@ -315,19 +339,24 @@ export class ZwaveMigration extends LitElement {
</div> </div>
</ha-card> </ha-card>
` `
: ``} : this._step === 4
${this._step === 4
? html`<ha-card class="content" header="Done!"> ? html`<ha-card class="content" header="Done!">
<div class="card-content"> <div class="card-content">
That was all! You are now migrated to the new OZW That was all! You are now migrated to the new Z-Wave JS
integration, check if all your devices and entities are integration, check if all your devices and entities are back
back the way they where, if not all entities could be the way they where, if not all entities could be migrated
migrated you might have to change those manually. you might have to change those manually.
<p>
If you have 'zwave' in your configurtion.yaml file, you
should remove it now.
</p>
</div> </div>
<div class="card-actions"> <div class="card-actions">
<mwc-button @click=${this._navigateOzw}> <a
Go to OZW config panel href=${`/config/zwave_js?config_entry=${this._zwaveJsEntryId}`}
</mwc-button> >
<mwc-button> Go to Z-Wave JS config panel </mwc-button>
</a>
</div> </div>
</ha-card>` </ha-card>`
: ""} : ""}
@ -367,29 +396,63 @@ export class ZwaveMigration extends LitElement {
this.hass!.callService("zwave", "stop_network"); this.hass!.callService("zwave", "stop_network");
} }
private async _setupOzw() { private async _setupZwaveJs() {
const ozwConfigFlow = await startOzwConfigFlow(this.hass); const zwaveJsConfigFlow = await startZwaveJsConfigFlow(this.hass);
if (isComponentLoaded(this.hass, "ozw")) {
this._getMigrationData();
this._step = 3;
return;
}
showConfigFlowDialog(this, { showConfigFlowDialog(this, {
continueFlowId: ozwConfigFlow.flow_id, continueFlowId: zwaveJsConfigFlow.flow_id,
dialogClosedCallback: () => { dialogClosedCallback: (params) => {
if (isComponentLoaded(this.hass, "ozw")) { if (params.entryId) {
this._getMigrationData(); this._zwaveJsEntryId = params.entryId;
this._getZwaveJSNodesStatus();
this._step = 3; this._step = 3;
} }
}, },
showAdvanced: this.hass.userData?.showAdvanced, showAdvanced: this.hass.userData?.showAdvanced,
}); });
this.hass.loadBackendTranslation("title", "ozw", true); this.hass.loadBackendTranslation("title", "zwave_js", true);
}
private async _getZwaveJSNodesStatus() {
if (this._nodeReadySubscriptions?.length) {
const unsubs = await Promise.all(this._nodeReadySubscriptions);
unsubs.forEach((unsub) => {
unsub();
});
}
this._nodeReadySubscriptions = [];
const networkStatus = await fetchZwaveJsNetworkStatus(
this.hass,
this._zwaveJsEntryId!
);
const nodeStatePromisses = networkStatus.controller.nodes.map((nodeId) =>
fetchNodeStatus(this.hass, this._zwaveJsEntryId!, nodeId)
);
const nodesNotReady = (await Promise.all(nodeStatePromisses)).filter(
(node) => !node.ready
);
this._getMigrationData();
if (nodesNotReady.length === 0) {
this._waitingOnDevices = [];
return;
}
this._nodeReadySubscriptions = nodesNotReady.map((node) =>
subscribeNodeReady(this.hass, this._zwaveJsEntryId!, node.node_id, () => {
this._getZwaveJSNodesStatus();
})
);
const deviceReg = await fetchDeviceRegistry(this.hass);
this._waitingOnDevices = deviceReg
.map((device) => getIdentifiersFromDevice(device))
.filter(Boolean);
} }
private async _getMigrationData() { private async _getMigrationData() {
try { try {
this._migrationData = await migrateZwave(this.hass, true); this._migrationData = await migrateZwave(
this.hass,
this._zwaveJsEntryId!,
true
);
} catch (err) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Failed to get migration data!", title: "Failed to get migration data!",
@ -430,7 +493,7 @@ export class ZwaveMigration extends LitElement {
} }
private async _doMigrate() { private async _doMigrate() {
const data = await migrateZwave(this.hass, false); const data = await migrateZwave(this.hass, this._zwaveJsEntryId!, false);
if (!data.migrated) { if (!data.migrated) {
showAlertDialog(this, { title: "Migration failed!" }); showAlertDialog(this, { title: "Migration failed!" });
return; return;
@ -438,10 +501,6 @@ export class ZwaveMigration extends LitElement {
this._step = 4; this._step = 4;
} }
private _navigateOzw() {
navigate("/config/ozw");
}
private _networkStopped(): void { private _networkStopped(): void {
this._unsubscribe(); this._unsubscribe();
this._getMigrationConfig(); this._getMigrationConfig();

View File

@ -2667,9 +2667,9 @@
"wakeup_interval": "Wake-up Interval" "wakeup_interval": "Wake-up Interval"
}, },
"migration": { "migration": {
"ozw": { "zwave_js": {
"header": "Migrate to OpenZWave", "header": "Migrate to Z-Wave JS",
"introduction": "This wizard will help you migrate from the legacy Z-Wave integration to the OpenZWave integration that is currently in beta." "introduction": "This integration is no longer maintained, and we advise you to move to the new Z-Wave JS integration. This wizard will help you migrate from the legacy Z-Wave integration to the new Z-Wave JS integration."
} }
}, },
"network_management": { "network_management": {