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;
}
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 = [
"ProtocolInfo",
"Probe",
@ -219,12 +211,3 @@ export const refreshNodeInfo = (
ozw_instance,
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",
});
export const startOzwConfigFlow = (
export const startZwaveJsConfigFlow = (
hass: HomeAssistant
): Promise<{ flow_id: string }> =>
hass.callWS({
type: "zwave/start_ozw_config_flow",
type: "zwave/start_zwave_js_config_flow",
});
export const fetchMigrationConfig = (

View File

@ -174,6 +174,25 @@ export interface RequestedGrant {
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 = (
hass: HomeAssistant,
entry_id: string
@ -307,8 +326,8 @@ export const reinterviewNode = (
(message: any) => callbackFunction(message),
{
type: "zwave_js/refresh_node_info",
entry_id: entry_id,
node_id: node_id,
entry_id,
node_id,
}
);
@ -319,8 +338,8 @@ export const healNode = (
): Promise<boolean> =>
hass.callWS({
type: "zwave_js/heal_node",
entry_id: entry_id,
node_id: node_id,
entry_id,
node_id,
});
export const removeFailedNode = (
@ -333,8 +352,8 @@ export const removeFailedNode = (
(message: any) => callbackFunction(message),
{
type: "zwave_js/remove_failed_node",
entry_id: entry_id,
node_id: node_id,
entry_id,
node_id,
}
);
@ -344,7 +363,7 @@ export const healNetwork = (
): Promise<UnsubscribeFunc> =>
hass.callWS({
type: "zwave_js/begin_healing_network",
entry_id: entry_id,
entry_id,
});
export const stopHealNetwork = (
@ -353,9 +372,24 @@ export const stopHealNetwork = (
): Promise<UnsubscribeFunc> =>
hass.callWS({
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 = (
hass: HomeAssistant,
entry_id: string,
@ -365,7 +399,7 @@ export const subscribeHealNetworkProgress = (
(message: any) => callbackFunction(message),
{
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-card
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">
<p>
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>
[[localize('ui.panel.config.zwave.migration.zwave_js.introduction')]]
</div>
<div class="card-actions">
<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>
</ha-card>

View File

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

View File

@ -2667,9 +2667,9 @@
"wakeup_interval": "Wake-up Interval"
},
"migration": {
"ozw": {
"header": "Migrate to OpenZWave",
"introduction": "This wizard will help you migrate from the legacy Z-Wave integration to the OpenZWave integration that is currently in beta."
"zwave_js": {
"header": "Migrate to Z-Wave JS",
"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": {