From 8721776839398072f3ad79ec05650f891489b0d4 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 29 Sep 2021 17:55:20 +0200 Subject: [PATCH] Add migration wizard for zwave -> zwave_js (#10097) --- src/data/ozw.ts | 17 - src/data/zwave.ts | 4 +- src/data/zwave_js.ts | 52 +- .../zwave/ha-config-zwave.js | 20 +- .../zwave/zwave-migration.ts | 591 ++++++++++-------- src/translations/en.json | 6 +- 6 files changed, 376 insertions(+), 314 deletions(-) diff --git a/src/data/ozw.ts b/src/data/ozw.ts index b8af5798f1..011a685fd8 100644 --- a/src/data/ozw.ts +++ b/src/data/ozw.ts @@ -73,14 +73,6 @@ export interface OZWDeviceConfig { help: string; } -export interface OZWMigrationData { - migration_device_map: Record; - zwave_entity_ids: string[]; - ozw_entity_ids: string[]; - migration_entity_map: Record; - 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 => - hass.callWS({ - type: "ozw/migrate_zwave", - dry_run, - }); diff --git a/src/data/zwave.ts b/src/data/zwave.ts index 34c336322e..fb6fbe935e 100644 --- a/src/data/zwave.ts +++ b/src/data/zwave.ts @@ -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 = ( diff --git a/src/data/zwave_js.ts b/src/data/zwave_js.ts index b20d4cf6b4..f08c322864 100644 --- a/src/data/zwave_js.ts +++ b/src/data/zwave_js.ts @@ -174,6 +174,25 @@ export interface RequestedGrant { export const nodeStatus = ["unknown", "asleep", "awake", "dead", "alive"]; +export interface ZWaveJsMigrationData { + migration_device_map: Record; + zwave_entity_ids: string[]; + zwave_js_entity_ids: string[]; + migration_entity_map: Record; + migrated: boolean; +} + +export const migrateZwave = ( + hass: HomeAssistant, + entry_id: string, + dry_run = true +): Promise => + 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 => 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 => 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 => 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 => + 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, } ); diff --git a/src/panels/config/integrations/integration-panels/zwave/ha-config-zwave.js b/src/panels/config/integrations/integration-panels/zwave/ha-config-zwave.js index a8230b1fee..7d9fb20bcc 100644 --- a/src/panels/config/integrations/integration-panels/zwave/ha-config-zwave.js +++ b/src/panels/config/integrations/integration-panels/zwave/ha-config-zwave.js @@ -104,28 +104,14 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
-

- If you are experiencing problems with your Z-Wave devices, you - can migrate to the newer OZW integration, that is currently in - beta. -

-

- Be aware that the future of OZW is not guaranteed, as the - development has stopped. -

-

- 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. -

+ [[localize('ui.panel.config.zwave.migration.zwave_js.introduction')]]
Start Migration to OZWStart Migration to Z-Wave JS
diff --git a/src/panels/config/integrations/integration-panels/zwave/zwave-migration.ts b/src/panels/config/integrations/integration-panels/zwave/zwave-migration.ts index 31df96cffe..15d104b643 100644 --- a/src/panels/config/integrations/integration-panels/zwave/zwave-migration.ts +++ b/src/panels/config/integrations/integration-panels/zwave/zwave-migration.ts @@ -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[]; + private _unsub?: Promise; private _unsubDevices?: UnsubscribeFunc; @@ -79,259 +95,272 @@ export class ZwaveMigration extends LitElement { >
- ${this.hass.localize("ui.panel.config.zwave.migration.ozw.header")} + ${this.hass.localize( + "ui.panel.config.zwave.migration.zwave_js.header" + )}
${this.hass.localize( - "ui.panel.config.zwave.migration.ozw.introduction" + "ui.panel.config.zwave.migration.zwave_js.introduction" )}
- ${!isComponentLoaded(this.hass, "hassio") && - !isComponentLoaded(this.hass, "mqtt") - ? html` - + ${html` + ${this._step === 0 + ? html` + +
+

+ This wizard will walk through the following steps to + migrate from the legacy Z-Wave integration to Z-Wave JS. +

+
    +
  1. Stop the Z-Wave network
  2. + ${!isComponentLoaded(this.hass, "hassio") + ? html`
  3. Configure and start Z-Wave JS
  4. ` + : ""} +
  5. Set up the Z-Wave JS integration
  6. +
  7. + Migrate entities and devices to the new integration +
  8. +
  9. Remove legacy Z-Wave integration
  10. +
+

+ + ${isComponentLoaded(this.hass, "hassio") + ? html`Please + make a backup + before proceeding.` + : "Please make a backup of your installation before proceeding."} + +

+
+
+ + Continue + +
+
+ ` + : this._step === 1 + ? html` + +
+

+ 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. +

+ ${Object.values(this.hass.states) + .filter( + (entityState) => + computeStateDomain(entityState) === "zwave" && + entityState.state !== "ready" + ) + .map( + (entityState) => + html` + 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. + ` + )} + ${this._stoppingNetwork + ? html` +
+ +

Stopping Z-Wave Network...

+
+ ` + : ``} +
+
+ + Stop Network + +
+
+ ` + : this._step === 2 + ? html` + +
+

Now it's time to set up the Z-Wave JS integration.

+ ${isComponentLoaded(this.hass, "hassio") + ? html` +

+ 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. +

+ ` + : html` +

+ 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 + advanced installation instructions + to install Z-Wave JS. +

+

+ Here's the current Z-Wave configuration. You'll + need these values when setting up Z-Wave JS. +

+ ${this._migrationConfig + ? html`
+ USB Path: ${this._migrationConfig.usb_path}
+ Network Key: + ${this._migrationConfig.network_key} +
` + : ``} +

+ Once Z-Wave JS is installed and running, click + 'Continue' to set up the Z-Wave JS integration and + migrate your devices and entities. +

+ `} +
+
+ + Continue + +
+
+ ` + : this._step === 3 + ? html` + +
+

+ 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. +

+ ${this._waitingOnDevices?.map( + (device) => + html` + 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. + ` + )} + ${this._migrationData + ? html` +

Below is a list of what will be migrated.

+ ${this._migratedZwaveEntities!.length !== + this._migrationData.zwave_entity_ids.length + ? html` + The following entities will not be migrated + and might need manual adjustments to your + config: + +
    + ${this._migrationData.zwave_entity_ids.map( + (entity_id) => + !this._migratedZwaveEntities!.includes( + entity_id + ) + ? html`
  • + ${entity_id in this.hass.states + ? computeStateName( + this.hass.states[entity_id] + ) + : ""} + (${entity_id}) +
  • ` + : "" + )} +
` + : ""} + ${Object.keys( + this._migrationData.migration_device_map + ).length + ? html`

Devices that will be migrated:

+
    + ${Object.keys( + this._migrationData.migration_device_map + ).map( + (device_id) => + html`
  • + ${this._deviceNameLookup[device_id] || + device_id} +
  • ` + )} +
` + : ""} + ${Object.keys( + this._migrationData.migration_entity_map + ).length + ? html`

Entities that will be migrated:

+
    + ${Object.keys( + this._migrationData.migration_entity_map + ).map( + (entity_id) => html`
  • + ${entity_id in this.hass.states + ? computeStateName( + this.hass.states[entity_id] + ) + : ""} + (${entity_id}) +
  • ` + )} +
` + : ""} + ` + : html`
+

Loading migration data...

+ + +
`} +
+
+ + Migrate + +
+
+ ` + : this._step === 4 + ? html`
+ 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.

- 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.

-
- ` - : html` - ${this._step === 0 - ? html` - -
-

- This wizard will walk through the following steps to - migrate from the legacy Z-Wave integration to - OpenZWave. -

-
    -
  1. Stop the Z-Wave network
  2. -
  3. - If running Home Assistant Core in Docker or in - Python venv: - Configure and start OZWDaemon -
  4. -
  5. Set up the OpenZWave integration
  6. -
  7. - Migrate entities and devices to the new - integration -
  8. -
  9. Remove legacy Z-Wave integration
  10. -
-

- - Please take a backup of your environment before - proceeding. - -

-
-
- - Continue - -
-
- ` - : ``} - ${this._step === 1 - ? html` - -
-

- 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. -

- ${this._stoppingNetwork - ? html` -
- -

Stopping Z-Wave Network...

-
- ` - : ``} -
-
- - Stop Network - -
-
- ` - : ``} - ${this._step === 2 - ? html` - -
-

Now it's time to set up the OZW integration.

- ${isComponentLoaded(this.hass, "hassio") - ? html` -

- The OZWDaemon runs in a Home Assistant addon - that will be setup next. Make sure to check - the checkbox for the addon. -

- ` - : html` -

- If you're using Home Assistant Core in Docker - or a Python venv, see the - - OZWDaemon readme - - for setup instructions. -

-

- Here's the current Z-Wave configuration. - You'll need these values when setting up OZW - daemon. -

- ${this._migrationConfig - ? html`
- USB Path: - ${this._migrationConfig.usb_path}
- Network Key: - ${this._migrationConfig.network_key} -
` - : ``} -

- 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. -

- `} -
-
- - Continue - -
-
- ` - : ``} - ${this._step === 3 - ? html` - -
-

- 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. -

- ${this._migrationData - ? html` -

Below is a list of what will be migrated.

- ${this._migratedZwaveEntities!.length !== - this._migrationData.zwave_entity_ids.length - ? html`

- Not all entities can be migrated! The - following entities will not be migrated - and might need manual adjustments to - your config: -

-
    - ${this._migrationData.zwave_entity_ids.map( - (entity_id) => - !this._migratedZwaveEntities!.includes( - entity_id - ) - ? html`
  • - ${computeStateName( - this.hass.states[entity_id] - )} - (${entity_id}) -
  • ` - : "" - )} -
` - : ""} - ${Object.keys( - this._migrationData.migration_device_map - ).length - ? html`

Devices that will be migrated:

-
    - ${Object.keys( - this._migrationData - .migration_device_map - ).map( - (device_id) => - html`
  • - ${this._deviceNameLookup[ - device_id - ] || device_id} -
  • ` - )} -
` - : ""} - ${Object.keys( - this._migrationData.migration_entity_map - ).length - ? html`

- Entities that will be migrated: -

-
    - ${Object.keys( - this._migrationData - .migration_entity_map - ).map( - (entity_id) => html`
  • - ${computeStateName( - this.hass.states[entity_id] - )} - (${entity_id}) -
  • ` - )} -
` - : ""} - ` - : html`
-

Loading migration data...

- - -
`} -
-
- - Migrate - -
-
- ` - : ``} - ${this._step === 4 - ? html` -
- 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. -
-
- - Go to OZW config panel - -
-
` - : ""} - `} + +
` + : ""} + `}
`; @@ -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(); diff --git a/src/translations/en.json b/src/translations/en.json index e6852688bc..6cd95097d1 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -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": {