Zwave -> OZW migration (#7765)

Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
This commit is contained in:
Bram Kragten 2021-01-27 16:01:18 +01:00 committed by GitHub
parent 2ce70206c6
commit 9243d300cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 629 additions and 14 deletions

View File

@ -71,7 +71,7 @@ export const updateDeviceRegistryEntry = (
...updates,
});
const fetchDeviceRegistry = (conn) =>
export const fetchDeviceRegistry = (conn) =>
conn.sendMessagePromise({
type: "config/device_registry/list",
});

View File

@ -73,6 +73,14 @@ 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",
@ -147,7 +155,7 @@ export const fetchOZWNetworkStatus = (
): Promise<OZWInstance> =>
hass.callWS({
type: "ozw/network_status",
ozw_instance: ozw_instance,
ozw_instance,
});
export const fetchOZWNetworkStatistics = (
@ -156,7 +164,7 @@ export const fetchOZWNetworkStatistics = (
): Promise<OZWNetworkStatistics> =>
hass.callWS({
type: "ozw/network_statistics",
ozw_instance: ozw_instance,
ozw_instance,
});
export const fetchOZWNodes = (
@ -165,7 +173,7 @@ export const fetchOZWNodes = (
): Promise<OZWDevice[]> =>
hass.callWS({
type: "ozw/get_nodes",
ozw_instance: ozw_instance,
ozw_instance,
});
export const fetchOZWNodeStatus = (
@ -175,8 +183,8 @@ export const fetchOZWNodeStatus = (
): Promise<OZWDevice> =>
hass.callWS({
type: "ozw/node_status",
ozw_instance: ozw_instance,
node_id: node_id,
ozw_instance,
node_id,
});
export const fetchOZWNodeMetadata = (
@ -186,8 +194,8 @@ export const fetchOZWNodeMetadata = (
): Promise<OZWDeviceMetaDataResponse> =>
hass.callWS({
type: "ozw/node_metadata",
ozw_instance: ozw_instance,
node_id: node_id,
ozw_instance,
node_id,
});
export const fetchOZWNodeConfig = (
@ -197,8 +205,8 @@ export const fetchOZWNodeConfig = (
): Promise<OZWDeviceConfig[]> =>
hass.callWS({
type: "ozw/get_config_parameters",
ozw_instance: ozw_instance,
node_id: node_id,
ozw_instance,
node_id,
});
export const refreshNodeInfo = (
@ -208,6 +216,15 @@ export const refreshNodeInfo = (
): Promise<OZWDevice> =>
hass.callWS({
type: "ozw/refresh_node_info",
ozw_instance: ozw_instance,
node_id: node_id,
ozw_instance,
node_id,
});
export const migrateZwave = (
hass: HomeAssistant,
dry_run = true
): Promise<OZWMigrationData> =>
hass.callWS({
type: "ozw/migrate_zwave",
dry_run,
});

View File

@ -42,6 +42,11 @@ export interface ZWaveAttributes {
wake_up_interval?: number;
}
export interface ZWaveMigrationConfig {
usb_path: string;
network_key: string;
}
export const ZWAVE_NETWORK_STATE_STOPPED = 0;
export const ZWAVE_NETWORK_STATE_FAILED = 1;
export const ZWAVE_NETWORK_STATE_STARTED = 5;
@ -55,6 +60,20 @@ export const fetchNetworkStatus = (
type: "zwave/network_status",
});
export const startOzwConfigFlow = (
hass: HomeAssistant
): Promise<{ flow_id: string }> =>
hass.callWS({
type: "zwave/start_ozw_config_flow",
});
export const fetchMigrationConfig = (
hass: HomeAssistant
): Promise<ZWaveMigrationConfig> =>
hass.callWS({
type: "zwave/get_migration_config",
});
export const fetchValues = (hass: HomeAssistant, nodeId: number) =>
hass.callApi<ZWaveValue[]>("GET", `zwave/values/${nodeId}`);

View File

@ -293,9 +293,9 @@ class HaPanelConfig extends HassRouterPage {
),
},
zwave: {
tag: "ha-config-zwave",
tag: "zwave-config-router",
load: () =>
import("./integrations/integration-panels/zwave/ha-config-zwave"),
import("./integrations/integration-panels/zwave/zwave-config-router"),
},
mqtt: {
tag: "mqtt-config-panel",

View File

@ -102,6 +102,36 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
</app-toolbar>
</app-header>
<ha-config-section is-wide="[[isWide]]">
<ha-card
class="content"
header="[[localize('ui.panel.config.zwave.migration.ozw.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>
</div>
<div class="card-actions">
<a href="/config/zwave/migration"
><mwc-button>Start Migration to OZW</mwc-button></a
>
</div>
</ha-card>
</ha-config-section>
<zwave-network
id="zwave-network"
is-wide="[[isWide]]"

View File

@ -0,0 +1,63 @@
import { customElement, property } from "lit-element";
import {
HassRouterPage,
RouterOptions,
} from "../../../../../layouts/hass-router-page";
import { HomeAssistant } from "../../../../../types";
import { navigate } from "../../../../../common/navigate";
@customElement("zwave-config-router")
class ZWaveConfigRouter extends HassRouterPage {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public isWide!: boolean;
@property() public narrow!: boolean;
private _configEntry = new URLSearchParams(window.location.search).get(
"config_entry"
);
protected routerOptions: RouterOptions = {
defaultPage: "dashboard",
showLoading: true,
routes: {
dashboard: {
tag: "ha-config-zwave",
load: () =>
import(/* webpackChunkName: "ha-config-zwave" */ "./ha-config-zwave"),
},
migration: {
tag: "zwave-migration",
load: () =>
import(/* webpackChunkName: "zwave-migration" */ "./zwave-migration"),
},
},
};
protected updatePageEl(el): void {
el.route = this.routeTail;
el.hass = this.hass;
el.isWide = this.isWide;
el.narrow = this.narrow;
el.configEntryId = this._configEntry;
const searchParams = new URLSearchParams(window.location.search);
if (this._configEntry && !searchParams.has("config_entry")) {
searchParams.append("config_entry", this._configEntry);
navigate(
this,
`${this.routeTail.prefix}${
this.routeTail.path
}?${searchParams.toString()}`,
true
);
}
}
}
declare global {
interface HTMLElementTagNameMap {
"zwave-config-router": ZWaveConfigRouter;
}
}

View File

@ -0,0 +1,480 @@
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@material/mwc-button/mwc-button";
import "../../../../../components/ha-icon-button";
import "../../../../../components/ha-circular-progress";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import "../../../../../components/buttons/ha-call-api-button";
import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card";
import "../../../../../components/ha-icon";
import {
fetchNetworkStatus,
ZWaveNetworkStatus,
ZWAVE_NETWORK_STATE_STOPPED,
fetchMigrationConfig,
ZWaveMigrationConfig,
startOzwConfigFlow,
} from "../../../../../data/zwave";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section";
import "../../../../../layouts/hass-subpage";
import { showConfigFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-config-flow";
import { migrateZwave, OZWMigrationData } from "../../../../../data/ozw";
import { navigate } from "../../../../../common/navigate";
import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
import { computeStateName } from "../../../../../common/entity/compute_state_name";
import {
computeDeviceName,
DeviceRegistryEntry,
fetchDeviceRegistry,
} from "../../../../../data/device_registry";
@customElement("zwave-migration")
export class ZwaveMigration extends LitElement {
@property({ type: Object }) public hass!: HomeAssistant;
@property({ type: Object }) public route!: Route;
@property({ type: Boolean }) public narrow!: boolean;
@property({ type: Boolean }) public isWide!: boolean;
@internalProperty() private _networkStatus?: ZWaveNetworkStatus;
@internalProperty() private _unsub?: Promise<UnsubscribeFunc>;
@internalProperty() private _step = 0;
@internalProperty() private _stoppingNetwork = false;
@internalProperty() private _migrationConfig?: ZWaveMigrationConfig;
@internalProperty() private _migrationData?: OZWMigrationData;
@internalProperty() private _migratedZwaveEntities?: string[];
@internalProperty() private _deviceRegistry?: DeviceRegistryEntry[];
public disconnectedCallback(): void {
this._unsubscribe();
}
protected render(): TemplateResult {
return html`
<hass-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
back-path="/config/zwave"
>
<ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}>
<div slot="header">
${this.hass.localize("ui.panel.config.zwave.migration.ozw.header")}
</div>
<div slot="introduction">
${this.hass.localize(
"ui.panel.config.zwave.migration.ozw.introduction"
)}
</div>
${!this.hass.config.components.includes("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
? 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 or a snapshot 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>
${this.hass.config.components.includes("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._computeDeviceName(
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>`
: ""}
`}
</ha-config-section>
</hass-subpage>
`;
}
private async _getMigrationConfig(): Promise<void> {
this._migrationConfig = await fetchMigrationConfig(this.hass!);
}
private async _unsubscribe(): Promise<void> {
if (this._unsub) {
(await this._unsub)();
this._unsub = undefined;
}
}
private _continue(): void {
this._step++;
}
private async _stopNetwork(): Promise<void> {
this._stoppingNetwork = true;
await this._getNetworkStatus();
if (this._networkStatus?.state === ZWAVE_NETWORK_STATE_STOPPED) {
this._networkStopped();
return;
}
this._unsub = this.hass!.connection.subscribeEvents(
() => this._networkStopped(),
"zwave.network_stop"
);
this.hass!.callService("zwave", "stop_network");
}
private async _setupOzw() {
const ozwConfigFlow = await startOzwConfigFlow(this.hass);
if (
!this.hass.config.components.includes("hassio") &&
this.hass.config.components.includes("ozw")
) {
this._getMigrationData();
this._step = 3;
return;
}
showConfigFlowDialog(this, {
continueFlowId: ozwConfigFlow.flow_id,
dialogClosedCallback: () => {
if (this.hass.config.components.includes("ozw")) {
this._getMigrationData();
this._step = 3;
}
},
showAdvanced: this.hass.userData?.showAdvanced,
});
this.hass.loadBackendTranslation("title", "ozw", true);
}
private async _getMigrationData() {
this._migrationData = await migrateZwave(this.hass, true);
this._migratedZwaveEntities = Object.keys(
this._migrationData.migration_entity_map
);
if (Object.keys(this._migrationData.migration_device_map).length) {
this._deviceRegistry = await fetchDeviceRegistry(this.hass);
}
}
private _computeDeviceName(deviceId) {
const device = this._deviceRegistry?.find(
(devReg) => devReg.id === deviceId
);
if (!device) {
return deviceId;
}
return computeDeviceName(device, this.hass);
}
private async _doMigrate() {
const data = await migrateZwave(this.hass, false);
if (!data.migrated) {
showAlertDialog(this, { title: "Migration failed!" });
return;
}
this._step = 4;
}
private _navigateOzw() {
navigate(this, "/config/ozw");
}
private _networkStopped(): void {
this._unsubscribe();
this._getMigrationConfig();
this._stoppingNetwork = false;
this._step = 2;
}
private async _getNetworkStatus(): Promise<void> {
this._networkStatus = await fetchNetworkStatus(this.hass!);
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
.content {
margin-top: 24px;
}
.flex-container {
display: flex;
align-items: center;
}
.flex-container ha-circular-progress {
margin-right: 20px;
}
blockquote {
display: block;
background-color: var(--secondary-background-color);
color: var(--primary-text-color);
padding: 8px;
margin: 8px 0;
font-size: 0.9em;
font-family: monospace;
}
ha-card {
margin: 0 auto;
max-width: 600px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zwave-migration": ZwaveMigration;
}
}

View File

@ -2335,6 +2335,12 @@
"unknown": "unknown",
"wakeup_interval": "Wakeup 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."
}
},
"network_management": {
"header": "Z-Wave Network Management",
"introduction": "Run commands that affect the Z-Wave network. You won't get feedback on whether most commands succeeded, but you can check the OZW Log to try to find out."