Remove zwave and ozw panels (#11911)

Remove zwave and ozw panels
This commit is contained in:
Bram Kragten 2022-03-04 23:10:44 +01:00 committed by GitHub
parent 604b79696e
commit 8f8017ecff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 9 additions and 5676 deletions

View File

@ -1,4 +1,3 @@
import "web-animations-js/web-animations-next-lite.min";
import "../../../src/resources/ha-style";
import "../../../src/resources/roboto";
import "./layout/hc-lovelace";

View File

@ -79,7 +79,6 @@
"@polymer/iron-icon": "^3.0.1",
"@polymer/iron-input": "^3.0.1",
"@polymer/iron-resizable-behavior": "^3.0.1",
"@polymer/paper-dropdown-menu": "^3.2.0",
"@polymer/paper-input": "^3.2.1",
"@polymer/paper-item": "^3.0.1",
"@polymer/paper-listbox": "^3.0.1",
@ -136,7 +135,6 @@
"vis-network": "^8.5.4",
"vue": "^2.6.12",
"vue2-daterange-picker": "^0.5.1",
"web-animations-js": "^2.3.2",
"workbox-cacheable-response": "^6.4.2",
"workbox-core": "^6.4.2",
"workbox-expiration": "^6.4.2",

View File

@ -1,213 +0,0 @@
import { HomeAssistant } from "../types";
import { DeviceRegistryEntry } from "./device_registry";
export interface OZWNodeIdentifiers {
ozw_instance: number;
node_id: number;
}
export interface OZWDevice {
node_id: number;
node_query_stage: string;
is_awake: boolean;
is_failed: boolean;
is_zwave_plus: boolean;
ozw_instance: number;
event: string;
node_manufacturer_name: string;
node_product_name: string;
}
export interface OZWDeviceMetaDataResponse {
node_id: number;
ozw_instance: number;
metadata: OZWDeviceMetaData;
}
export interface OZWDeviceMetaData {
OZWInfoURL: string;
ZWAProductURL: string;
ProductPic: string;
Description: string;
ProductManualURL: string;
ProductPageURL: string;
InclusionHelp: string;
ExclusionHelp: string;
ResetHelp: string;
WakeupHelp: string;
ProductSupportURL: string;
Frequency: string;
Name: string;
ProductPicBase64: string;
}
export interface OZWInstance {
ozw_instance: number;
OZWDaemon_Version: string;
OpenZWave_Version: string;
QTOpenZWave_Version: string;
Status: string;
getControllerPath: string;
homeID: string;
}
export interface OZWNetworkStatistics {
ozw_instance: number;
node_count: number;
readCnt: number;
writeCnt: number;
ACKCnt: number;
CANCnt: number;
NAKCnt: number;
dropped: number;
retries: number;
}
export interface OZWDeviceConfig {
label: string;
type: string;
value: string | number;
parameter: number;
min: number;
max: number;
help: string;
}
export const nodeQueryStages = [
"ProtocolInfo",
"Probe",
"WakeUp",
"ManufacturerSpecific1",
"NodeInfo",
"NodePlusInfo",
"ManufacturerSpecific2",
"Versions",
"Instances",
"Static",
"CacheLoad",
"Associations",
"Neighbors",
"Session",
"Dynamic",
"Configuration",
"Complete",
];
export const networkOnlineStatuses = [
"driverAllNodesQueried",
"driverAllNodesQueriedSomeDead",
"driverAwakeNodesQueried",
];
export const networkStartingStatuses = [
"starting",
"started",
"Ready",
"driverReady",
];
export const networkOfflineStatuses = [
"Offline",
"stopped",
"driverFailed",
"driverReset",
"driverRemoved",
"driverAllNodesOnFire",
];
export const getIdentifiersFromDevice = function (
device: DeviceRegistryEntry
): OZWNodeIdentifiers | undefined {
if (!device) {
return undefined;
}
const ozwIdentifier = device.identifiers.find(
(identifier) => identifier[0] === "ozw"
);
if (!ozwIdentifier) {
return undefined;
}
const identifiers = ozwIdentifier[1].split(".");
return {
node_id: parseInt(identifiers[1]),
ozw_instance: parseInt(identifiers[0]),
};
};
export const fetchOZWInstances = (
hass: HomeAssistant
): Promise<OZWInstance[]> =>
hass.callWS({
type: "ozw/get_instances",
});
export const fetchOZWNetworkStatus = (
hass: HomeAssistant,
ozw_instance: number
): Promise<OZWInstance> =>
hass.callWS({
type: "ozw/network_status",
ozw_instance,
});
export const fetchOZWNetworkStatistics = (
hass: HomeAssistant,
ozw_instance: number
): Promise<OZWNetworkStatistics> =>
hass.callWS({
type: "ozw/network_statistics",
ozw_instance,
});
export const fetchOZWNodes = (
hass: HomeAssistant,
ozw_instance: number
): Promise<OZWDevice[]> =>
hass.callWS({
type: "ozw/get_nodes",
ozw_instance,
});
export const fetchOZWNodeStatus = (
hass: HomeAssistant,
ozw_instance: number,
node_id: number
): Promise<OZWDevice> =>
hass.callWS({
type: "ozw/node_status",
ozw_instance,
node_id,
});
export const fetchOZWNodeMetadata = (
hass: HomeAssistant,
ozw_instance: number,
node_id: number
): Promise<OZWDeviceMetaDataResponse> =>
hass.callWS({
type: "ozw/node_metadata",
ozw_instance,
node_id,
});
export const fetchOZWNodeConfig = (
hass: HomeAssistant,
ozw_instance: number,
node_id: number
): Promise<OZWDeviceConfig[]> =>
hass.callWS({
type: "ozw/get_config_parameters",
ozw_instance,
node_id,
});
export const refreshNodeInfo = (
hass: HomeAssistant,
ozw_instance: number,
node_id: number
): Promise<OZWDevice> =>
hass.callWS({
type: "ozw/refresh_node_info",
ozw_instance,
node_id,
});

View File

@ -1,81 +0,0 @@
import { HomeAssistant } from "../types";
export interface ZWaveNetworkStatus {
state: number;
}
export interface ZWaveValue {
key: number;
value: {
index: number;
instance: number;
label: string;
poll_intensity: number;
};
}
export interface ZWaveConfigItem {
key: number;
value: {
data: any;
data_items: any[];
help: string;
label: string;
max: number;
min: number;
type: string;
};
}
export interface ZWaveConfigServiceData {
node_id: number;
parameter: number;
value: number | string;
}
export interface ZWaveNode {
attributes: ZWaveAttributes;
}
export interface ZWaveAttributes {
node_id: number;
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;
export const ZWAVE_NETWORK_STATE_AWAKED = 7;
export const ZWAVE_NETWORK_STATE_READY = 10;
export const fetchNetworkStatus = (
hass: HomeAssistant
): Promise<ZWaveNetworkStatus> =>
hass.callWS({
type: "zwave/network_status",
});
export const startZwaveJsConfigFlow = (
hass: HomeAssistant
): Promise<{ flow_id: string }> =>
hass.callWS({
type: "zwave/start_zwave_js_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}`);
export const fetchNodeConfig = (hass: HomeAssistant, nodeId: number) =>
hass.callApi<ZWaveConfigItem[]>("GET", `zwave/config/${nodeId}`);

View File

@ -1,84 +0,0 @@
import "@material/mwc-button/mwc-button";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property } from "lit/decorators";
import { navigate } from "../../../../../../common/navigate";
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
import {
getIdentifiersFromDevice,
OZWNodeIdentifiers,
} from "../../../../../../data/ozw";
import { haStyle } from "../../../../../../resources/styles";
import { HomeAssistant } from "../../../../../../types";
import { showOZWRefreshNodeDialog } from "../../../../integrations/integration-panels/ozw/show-dialog-ozw-refresh-node";
@customElement("ha-device-actions-ozw")
export class HaDeviceActionsOzw extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public device!: DeviceRegistryEntry;
@property()
private node_id = 0;
@property()
private ozw_instance = 1;
protected updated(changedProperties: PropertyValues) {
if (changedProperties.has("device")) {
const identifiers: OZWNodeIdentifiers | undefined =
getIdentifiersFromDevice(this.device);
if (!identifiers) {
return;
}
this.ozw_instance = identifiers.ozw_instance;
this.node_id = identifiers.node_id;
}
}
protected render(): TemplateResult {
if (!this.ozw_instance || !this.node_id) {
return html``;
}
return html`
<mwc-button @click=${this._nodeDetailsClicked}>
${this.hass.localize("ui.panel.config.ozw.node.button")}
</mwc-button>
<mwc-button @click=${this._refreshNodeClicked}>
${this.hass.localize("ui.panel.config.ozw.refresh_node.button")}
</mwc-button>
`;
}
private async _refreshNodeClicked() {
showOZWRefreshNodeDialog(this, {
node_id: this.node_id,
ozw_instance: this.ozw_instance,
});
}
private async _nodeDetailsClicked() {
navigate(
`/config/ozw/network/${this.ozw_instance}/node/${this.node_id}/dashboard`
);
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
:host {
display: flex;
flex-direction: column;
align-items: flex-start;
}
`,
];
}
}

View File

@ -1,99 +0,0 @@
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
import {
fetchOZWNodeStatus,
getIdentifiersFromDevice,
OZWDevice,
OZWNodeIdentifiers,
} from "../../../../../../data/ozw";
import { haStyle } from "../../../../../../resources/styles";
import { HomeAssistant } from "../../../../../../types";
@customElement("ha-device-info-ozw")
export class HaDeviceInfoOzw extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public device!: DeviceRegistryEntry;
@property()
private node_id = 0;
@property()
private ozw_instance = 1;
@state() private _ozwDevice?: OZWDevice;
protected updated(changedProperties: PropertyValues) {
if (changedProperties.has("device")) {
const identifiers: OZWNodeIdentifiers | undefined =
getIdentifiersFromDevice(this.device);
if (!identifiers) {
return;
}
this.ozw_instance = identifiers.ozw_instance;
this.node_id = identifiers.node_id;
this._fetchNodeDetails();
}
}
protected async _fetchNodeDetails() {
this._ozwDevice = await fetchOZWNodeStatus(
this.hass,
this.ozw_instance,
this.node_id
);
}
protected render(): TemplateResult {
if (!this._ozwDevice) {
return html``;
}
return html`
<h4>
${this.hass.localize("ui.panel.config.ozw.device_info.zwave_info")}
</h4>
<div>
${this.hass.localize("ui.panel.config.ozw.common.node_id")}:
${this._ozwDevice.node_id}
</div>
<div>
${this.hass.localize("ui.panel.config.ozw.device_info.stage")}:
${this._ozwDevice.node_query_stage}
</div>
<div>
${this.hass.localize("ui.panel.config.ozw.common.ozw_instance")}:
${this._ozwDevice.ozw_instance}
</div>
<div>
${this.hass.localize("ui.panel.config.ozw.device_info.node_failed")}:
${this._ozwDevice.is_failed
? this.hass.localize("ui.common.yes")
: this.hass.localize("ui.common.no")}
</div>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
h4 {
margin-bottom: 4px;
}
div {
word-break: break-all;
margin-top: 2px;
}
`,
];
}
}

View File

@ -27,7 +27,7 @@ import { HomeAssistant } from "../../../../../../types";
export class HaDeviceInfoZWaveJS extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public device!: DeviceRegistryEntry;
@property({ attribute: false }) public device!: DeviceRegistryEntry;
@state() private _entryId?: string;
@ -173,3 +173,9 @@ export class HaDeviceInfoZWaveJS extends LitElement {
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-device-info-zwave_js": HaDeviceInfoZWaveJS;
}
}

View File

@ -902,22 +902,6 @@ export class HaConfigDevicePage extends LitElement {
></ha-device-actions-mqtt>
`);
}
if (domains.includes("ozw")) {
import("./device-detail/integration-elements/ozw/ha-device-actions-ozw");
import("./device-detail/integration-elements/ozw/ha-device-info-ozw");
deviceInfo.push(html`
<ha-device-info-ozw
.hass=${this.hass}
.device=${device}
></ha-device-info-ozw>
`);
deviceActions.push(html`
<ha-device-actions-ozw
.hass=${this.hass}
.device=${device}
></ha-device-actions-ozw>
`);
}
if (domains.includes("zha")) {
import("./device-detail/integration-elements/zha/ha-device-actions-zha");
import("./device-detail/integration-elements/zha/ha-device-info-zha");

View File

@ -376,21 +376,11 @@ class HaPanelConfig extends HassRouterPage {
"./integrations/integration-panels/zha/zha-config-dashboard-router"
),
},
zwave: {
tag: "zwave-config-router",
load: () =>
import("./integrations/integration-panels/zwave/zwave-config-router"),
},
mqtt: {
tag: "mqtt-config-panel",
load: () =>
import("./integrations/integration-panels/mqtt/mqtt-config-panel"),
},
ozw: {
tag: "ozw-config-router",
load: () =>
import("./integrations/integration-panels/ozw/ozw-config-router"),
},
zwave_js: {
tag: "zwave_js-config-router",
load: () =>

View File

@ -55,8 +55,6 @@ const integrationsWithPanel = {
hassio: "/hassio/dashboard",
mqtt: "/config/mqtt",
zha: "/config/zha/dashboard",
ozw: "/config/ozw/dashboard",
zwave: "/config/zwave",
zwave_js: "/config/zwave_js/dashboard",
};

View File

@ -1,269 +0,0 @@
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../../components/ha-circular-progress";
import "../../../../../components/ha-code-editor";
import { createCloseHeading } from "../../../../../components/ha-dialog";
import {
fetchOZWNodeMetadata,
nodeQueryStages,
OZWDevice,
OZWDeviceMetaData,
} from "../../../../../data/ozw";
import { haStyleDialog } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types";
import { OZWRefreshNodeDialogParams } from "./show-dialog-ozw-refresh-node";
@customElement("dialog-ozw-refresh-node")
class DialogOZWRefreshNode extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _node_id?: number;
@state() private _ozw_instance = 1;
@state() private _nodeMetaData?: OZWDeviceMetaData;
@state() private _node?: OZWDevice;
@state() private _active = false;
@state() private _complete = false;
private _refreshDevicesTimeoutHandle?: number;
private _subscribed?: Promise<() => Promise<void>>;
public disconnectedCallback(): void {
super.disconnectedCallback();
this._unsubscribe();
}
protected updated(changedProperties: PropertyValues): void {
super.update(changedProperties);
if (changedProperties.has("node_id")) {
this._fetchData();
}
}
private async _fetchData() {
if (!this._node_id) {
return;
}
const metaDataResponse = await fetchOZWNodeMetadata(
this.hass,
this._ozw_instance,
this._node_id
);
this._nodeMetaData = metaDataResponse.metadata;
}
public async showDialog(params: OZWRefreshNodeDialogParams): Promise<void> {
this._node_id = params.node_id;
this._ozw_instance = params.ozw_instance;
this._fetchData();
}
protected render(): TemplateResult {
if (!this._node_id) {
return html``;
}
return html`
<ha-dialog
open
@closed=${this._close}
.heading=${createCloseHeading(
this.hass,
this.hass.localize("ui.panel.config.ozw.refresh_node.title")
)}
>
${this._complete
? html`
<p>
${this.hass.localize(
"ui.panel.config.ozw.refresh_node.complete"
)}
</p>
<mwc-button slot="primaryAction" @click=${this._close}>
${this.hass.localize("ui.common.close")}
</mwc-button>
`
: html`
${this._active
? html`
<div class="flex-container">
<ha-circular-progress active></ha-circular-progress>
<div>
<p>
<b>
${this.hass.localize(
"ui.panel.config.ozw.refresh_node.refreshing_description"
)}
</b>
</p>
${this._node
? html`
<p>
${this.hass.localize(
"ui.panel.config.ozw.refresh_node.node_status"
)}:
${this._node.node_query_stage}
(${this.hass.localize(
"ui.panel.config.ozw.refresh_node.step"
)}
${nodeQueryStages.indexOf(
this._node.node_query_stage
) + 1}/17)
</p>
<p>
<em>
${this.hass.localize(
"ui.panel.config.ozw.node_query_stages." +
this._node.node_query_stage.toLowerCase()
)}</em
>
</p>
`
: ``}
</div>
</div>
`
: html`
${this.hass.localize(
"ui.panel.config.ozw.refresh_node.description"
)}
<p>
${this.hass.localize(
"ui.panel.config.ozw.refresh_node.battery_note"
)}
</p>
`}
${this._nodeMetaData?.WakeupHelp !== ""
? html`
<b>
${this.hass.localize(
"ui.panel.config.ozw.refresh_node.wakeup_header"
)}
${this._nodeMetaData!.Name}
</b>
<blockquote>
${this._nodeMetaData!.WakeupHelp}
<br />
<em>
${this.hass.localize(
"ui.panel.config.ozw.refresh_node.wakeup_instructions_source"
)}
</em>
</blockquote>
`
: ""}
${!this._active
? html`
<mwc-button
slot="primaryAction"
@click=${this._startRefresh}
>
${this.hass.localize(
"ui.panel.config.ozw.refresh_node.start_refresh_button"
)}
</mwc-button>
`
: html``}
`}
</ha-dialog>
`;
}
private _startRefresh(): void {
this._subscribe();
}
private _handleMessage(message: any): void {
if (message.type === "node_updated") {
this._node = message;
if (message.node_query_stage === "Complete") {
this._unsubscribe();
this._complete = true;
}
}
}
private _unsubscribe(): void {
this._active = false;
if (this._refreshDevicesTimeoutHandle) {
clearTimeout(this._refreshDevicesTimeoutHandle);
}
if (this._subscribed) {
this._subscribed.then((unsub) => unsub());
this._subscribed = undefined;
}
}
private _subscribe(): void {
if (!this.hass) {
return;
}
this._active = true;
this._subscribed = this.hass.connection.subscribeMessage(
(message) => this._handleMessage(message),
{
type: "ozw/refresh_node_info",
node_id: this._node_id,
ozw_instance: this._ozw_instance,
}
);
this._refreshDevicesTimeoutHandle = window.setTimeout(
() => this._unsubscribe(),
120000
);
}
private _close(): void {
this._complete = false;
this._node_id = undefined;
this._node = undefined;
}
static get styles(): CSSResultGroup {
return [
haStyleDialog,
css`
blockquote {
display: block;
background-color: #ddd;
padding: 8px;
margin: 8px 0;
font-size: 0.9em;
}
blockquote em {
font-size: 0.9em;
margin-top: 6px;
}
.flex-container {
display: flex;
align-items: center;
}
.flex-container ha-circular-progress {
margin-right: 20px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-ozw-refresh-node": DialogOZWRefreshNode;
}
}

View File

@ -1,260 +0,0 @@
import "@material/mwc-button/mwc-button";
import { mdiCheckCircle, mdiCircle, mdiCloseCircle, mdiZWave } from "@mdi/js";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { navigate } from "../../../../../common/navigate";
import "../../../../../components/ha-card";
import "../../../../../components/ha-icon-next";
import {
fetchOZWInstances,
networkOfflineStatuses,
networkOnlineStatuses,
networkStartingStatuses,
OZWInstance,
} from "../../../../../data/ozw";
import "../../../../../layouts/hass-error-screen";
import "../../../../../layouts/hass-loading-screen";
import "../../../../../layouts/hass-tabs-subpage";
import type { PageNavigation } from "../../../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section";
import "../../../../../components/ha-alert";
export const ozwTabs: PageNavigation[] = [];
@customElement("ozw-config-dashboard")
class OZWConfigDashboard 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;
@property() public configEntryId?: string;
@state() private _instances?: OZWInstance[];
protected firstUpdated() {
this._fetchData();
}
protected render(): TemplateResult {
if (!this._instances) {
return html`<hass-loading-screen></hass-loading-screen>`;
}
if (this._instances.length === 0) {
return html`<hass-error-screen
.hass=${this.hass}
.error=${this.hass.localize(
"ui.panel.config.ozw.select_instance.none_found"
)}
></hass-error-screen>`;
}
return html`
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
.tabs=${ozwTabs}
back-path="/config/integrations"
>
<ha-alert
alert-type="warning"
title="This integration will stop working soon"
>
The OpenZWave integration is deprecated and will no longer receive any
updates. The technical dependencies will render this integration
unusable in the near future. We strongly advise you to migrate to the
new
<a
href="https://www.home-assistant.io/integrations/zwave_js"
target="_blank"
rel="noreferrer"
>Z-Wave JS integration</a
>.
<a
slot="action"
href="https://alerts.home-assistant.io/#ozw.markdown"
target="_blank"
rel="noreferrer"
>
<mwc-button>learn more</mwc-button>
</a>
</ha-alert>
<ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}>
<div slot="header">
${this.hass.localize("ui.panel.config.ozw.select_instance.header")}
</div>
<div slot="introduction">
${this.hass.localize(
"ui.panel.config.ozw.select_instance.introduction"
)}
</div>
${this._instances.length > 0
? html`
${this._instances.map((instance) => {
let status = "unknown";
let icon = mdiCircle;
if (networkOnlineStatuses.includes(instance.Status)) {
status = "online";
icon = mdiCheckCircle;
}
if (networkStartingStatuses.includes(instance.Status)) {
status = "starting";
}
if (networkOfflineStatuses.includes(instance.Status)) {
status = "offline";
icon = mdiCloseCircle;
}
return html`
<ha-card>
<a
href="/config/ozw/network/${instance.ozw_instance}"
role="option"
tabindex="-1"
>
<paper-icon-item>
<ha-svg-icon .path=${mdiZWave} slot="item-icon">
</ha-svg-icon>
<paper-item-body>
${this.hass.localize(
"ui.panel.config.ozw.common.instance"
)}
${instance.ozw_instance}
<div secondary>
<ha-svg-icon
.path=${icon}
class="network-status-icon ${status}"
></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.ozw.network_status." + status
)}
-
${this.hass.localize(
"ui.panel.config.ozw.network_status.details." +
instance.Status.toLowerCase()
)}<br />
${this.hass.localize(
"ui.panel.config.ozw.common.controller"
)}
: ${instance.getControllerPath}<br />
OZWDaemon ${instance.OZWDaemon_Version} (OpenZWave
${instance.OpenZWave_Version})
</div>
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-icon-item>
</a>
</ha-card>
`;
})}
`
: ""}
</ha-config-section>
</hass-tabs-subpage>
`;
}
private async _fetchData() {
this._instances = await fetchOZWInstances(this.hass!);
if (this._instances.length === 1) {
navigate(`/config/ozw/network/${this._instances[0].ozw_instance}`, {
replace: true,
});
}
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
ha-card:last-child {
margin-bottom: 24px;
}
ha-config-section {
margin-top: -12px;
}
:host([narrow]) ha-config-section {
margin-top: -20px;
}
ha-alert {
display: block;
margin: 16px;
}
ha-alert a {
text-decoration: none;
}
ha-card {
overflow: hidden;
}
ha-card a {
text-decoration: none;
color: var(--primary-text-color);
}
paper-item-body {
margin: 16px 0;
}
a {
text-decoration: none;
color: var(--primary-text-color);
position: relative;
display: block;
outline: 0;
}
ha-svg-icon.network-status-icon {
height: 14px;
width: 14px;
}
.online {
color: green;
}
.starting {
color: orange;
}
.offline {
color: red;
}
ha-svg-icon,
ha-icon-next {
color: var(--secondary-text-color);
}
.iron-selected paper-item::before,
a:not(.iron-selected):focus::before {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
pointer-events: none;
content: "";
transition: opacity 15ms linear;
will-change: opacity;
}
a:not(.iron-selected):focus::before {
background-color: currentColor;
opacity: var(--dark-divider-opacity);
}
.iron-selected paper-item:focus::before,
.iron-selected:focus paper-item::before {
opacity: 0.2;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ozw-config-dashboard": OZWConfigDashboard;
}
}

View File

@ -1,67 +0,0 @@
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import {
HassRouterPage,
RouterOptions,
} from "../../../../../layouts/hass-router-page";
import { HomeAssistant, Route } from "../../../../../types";
export const computeTail = memoizeOne((route: Route) => {
const dividerPos = route.path.indexOf("/", 1);
return dividerPos === -1
? {
prefix: route.prefix + route.path,
path: "",
}
: {
prefix: route.prefix + route.path.substr(0, dividerPos),
path: route.path.substr(dividerPos),
};
});
@customElement("ozw-config-router")
class OZWConfigRouter 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: "ozw-config-dashboard",
load: () => import("./ozw-config-dashboard"),
},
network: {
tag: "ozw-network-router",
load: () => import("./ozw-network-router"),
},
},
};
protected updatePageEl(el): void {
el.route = this.routeTail;
el.hass = this.hass;
el.isWide = this.isWide;
el.narrow = this.narrow;
el.configEntryId = this._configEntry;
if (this._currentPage === "network") {
const path = this.routeTail.path.split("/");
el.ozwInstance = path[1];
el.route = computeTail(this.routeTail);
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ozw-config-router": OZWConfigRouter;
}
}

View File

@ -1,245 +0,0 @@
import "@material/mwc-button/mwc-button";
import { mdiCheckCircle, mdiCircle, mdiCloseCircle } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { navigate } from "../../../../../common/navigate";
import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card";
import {
fetchOZWNetworkStatistics,
fetchOZWNetworkStatus,
networkOfflineStatuses,
networkOnlineStatuses,
networkStartingStatuses,
OZWInstance,
OZWNetworkStatistics,
} from "../../../../../data/ozw";
import "../../../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section";
import { ozwNetworkTabs } from "./ozw-network-router";
@customElement("ozw-network-dashboard")
class OZWNetworkDashboard 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;
@property() public configEntryId?: string;
@property() public ozwInstance?: number;
@state() private _network?: OZWInstance;
@state() private _statistics?: OZWNetworkStatistics;
@state() private _status = "unknown";
@state() private _icon = mdiCircle;
protected firstUpdated() {
if (!this.ozwInstance) {
navigate("/config/ozw/dashboard", { replace: true });
} else if (this.hass) {
this._fetchData();
}
}
protected render(): TemplateResult {
return html`
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
.tabs=${ozwNetworkTabs(this.ozwInstance!)}
>
<ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}>
<div slot="header">
${this.hass.localize("ui.panel.config.ozw.network.header")}
</div>
<div slot="introduction">
${this.hass.localize("ui.panel.config.ozw.network.introduction")}
</div>
${this._network
? html`
<ha-card class="content network-status">
<div class="card-content">
<div class="details">
<ha-svg-icon
.path=${this._icon}
class="network-status-icon ${classMap({
[this._status]: true,
})}"
slot="item-icon"
></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.ozw.common.network"
)}
${this.hass.localize(
`ui.panel.config.ozw.network_status.${this._status}`
)}
<br />
<small>
${this.hass.localize(
`ui.panel.config.ozw.network_status.details.${this._network.Status.toLowerCase()}`
)}
</small>
</div>
<div class="secondary">
${this.hass.localize(
"ui.panel.config.ozw.common.ozw_instance"
)}
${this._network.ozw_instance}
${this._statistics
? html`
&bull;
${this.hass.localize(
"ui.panel.config.ozw.network.node_count",
"count",
this._statistics.node_count
)}
`
: ``}
<br />
${this.hass.localize(
"ui.panel.config.ozw.common.controller"
)}:
${this._network.getControllerPath}<br />
OZWDaemon ${this._network.OZWDaemon_Version} (OpenZWave
${this._network.OpenZWave_Version})
</div>
</div>
<div class="card-actions">
${this._generateServiceButton("add_node")}
${this._generateServiceButton("remove_node")}
${this._generateServiceButton("cancel_command")}
</div>
</ha-card>
`
: ``}
</ha-config-section>
</hass-tabs-subpage>
`;
}
private async _fetchData() {
if (!this.ozwInstance) return;
this._network = await fetchOZWNetworkStatus(this.hass!, this.ozwInstance);
this._statistics = await fetchOZWNetworkStatistics(
this.hass!,
this.ozwInstance
);
if (networkOnlineStatuses.includes(this._network!.Status)) {
this._status = "online";
this._icon = mdiCheckCircle;
}
if (networkStartingStatuses.includes(this._network!.Status)) {
this._status = "starting";
}
if (networkOfflineStatuses.includes(this._network!.Status)) {
this._status = "offline";
this._icon = mdiCloseCircle;
}
}
private _generateServiceButton(service: string) {
const serviceData = { instance_id: this.ozwInstance };
return html`
<ha-call-service-button
.hass=${this.hass}
domain="ozw"
.service=${service}
.serviceData=${serviceData}
>
${this.hass!.localize(`ui.panel.config.ozw.services.${service}`)}
</ha-call-service-button>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.secondary {
color: var(--secondary-text-color);
}
.online {
color: green;
}
.starting {
color: orange;
}
.offline {
color: red;
}
.content {
margin-top: 24px;
}
.sectionHeader {
position: relative;
padding-right: 40px;
}
.network-status {
text-align: center;
}
.network-status div.details {
font-size: 1.5rem;
margin-bottom: 16px;
}
.network-status ha-svg-icon {
display: block;
margin: 0px auto 16px;
width: 48px;
height: 48px;
}
.network-status small {
font-size: 1rem;
}
ha-card {
margin: 0 auto;
max-width: 600px;
}
.card-actions.warning ha-call-service-button {
color: var(--error-color);
}
.toggle-help-icon {
position: absolute;
top: -6px;
right: 0;
color: var(--primary-color);
}
ha-service-description {
display: block;
color: grey;
padding: 0 8px 12px;
}
[hidden] {
display: none;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ozw-network-dashboard": OZWNetworkDashboard;
}
}

View File

@ -1,131 +0,0 @@
import "@material/mwc-button/mwc-button";
import { mdiAlert, mdiCheck } from "@mdi/js";
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { HASSDomEvent } from "../../../../../common/dom/fire_event";
import { navigate } from "../../../../../common/navigate";
import "../../../../../components/buttons/ha-call-service-button";
import {
DataTableColumnContainer,
RowClickedEvent,
} from "../../../../../components/data-table/ha-data-table";
import "../../../../../components/ha-card";
import { fetchOZWNodes, OZWDevice } from "../../../../../data/ozw";
import "../../../../../layouts/hass-tabs-subpage";
import "../../../../../layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section";
import { ozwNetworkTabs } from "./ozw-network-router";
export interface NodeRowData extends OZWDevice {
node?: NodeRowData;
id?: number;
}
@customElement("ozw-network-nodes")
class OZWNetworkNodes 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;
@property() public configEntryId?: string;
@property() public ozwInstance = 0;
@state() private _nodes: OZWDevice[] = [];
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({
node_id: {
title: this.hass.localize("ui.panel.config.ozw.nodes_table.id"),
sortable: true,
type: "numeric",
width: "72px",
filterable: true,
direction: "asc",
},
node_product_name: {
title: this.hass.localize("ui.panel.config.ozw.nodes_table.model"),
sortable: true,
width: narrow ? "75%" : "25%",
},
node_manufacturer_name: {
title: this.hass.localize(
"ui.panel.config.ozw.nodes_table.manufacturer"
),
sortable: true,
hidden: narrow,
width: "25%",
},
node_query_stage: {
title: this.hass.localize(
"ui.panel.config.ozw.nodes_table.query_stage"
),
sortable: true,
width: narrow ? "25%" : "15%",
},
is_zwave_plus: {
title: this.hass.localize("ui.panel.config.ozw.nodes_table.zwave_plus"),
hidden: narrow,
template: (value: boolean) =>
value ? html` <ha-svg-icon .path=${mdiCheck}></ha-svg-icon>` : "",
},
is_failed: {
title: this.hass.localize("ui.panel.config.ozw.nodes_table.failed"),
hidden: narrow,
template: (value: boolean) =>
value ? html` <ha-svg-icon .path=${mdiAlert}></ha-svg-icon>` : "",
},
})
);
protected firstUpdated() {
if (!this.ozwInstance) {
navigate("/config/ozw/dashboard", { replace: true });
} else if (this.hass) {
this._fetchData();
}
}
protected render(): TemplateResult {
return html`
<hass-tabs-subpage-data-table
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
.tabs=${ozwNetworkTabs(this.ozwInstance)}
.columns=${this._columns(this.narrow)}
.data=${this._nodes}
id="node_id"
@row-click=${this._handleRowClicked}
clickable
>
</hass-tabs-subpage-data-table>
`;
}
private async _fetchData() {
this._nodes = await fetchOZWNodes(this.hass!, this.ozwInstance!);
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const nodeId = ev.detail.id;
navigate(`/config/ozw/network/${this.ozwInstance}/node/${nodeId}`);
}
static get styles(): CSSResultGroup {
return haStyle;
}
}
declare global {
interface HTMLElementTagNameMap {
"ozw-network-nodes": OZWNetworkNodes;
}
}

View File

@ -1,74 +0,0 @@
import { mdiNetwork, mdiServerNetwork } from "@mdi/js";
import { customElement, property } from "lit/decorators";
import {
HassRouterPage,
RouterOptions,
} from "../../../../../layouts/hass-router-page";
import { PageNavigation } from "../../../../../layouts/hass-tabs-subpage";
import { HomeAssistant } from "../../../../../types";
import { computeTail } from "./ozw-config-router";
export const ozwNetworkTabs = (instance: number): PageNavigation[] => [
{
translationKey: "ui.panel.config.ozw.navigation.network",
path: `/config/ozw/network/${instance}/dashboard`,
iconPath: mdiServerNetwork,
},
{
translationKey: "ui.panel.config.ozw.navigation.nodes",
path: `/config/ozw/network/${instance}/nodes`,
iconPath: mdiNetwork,
},
];
@customElement("ozw-network-router")
class OZWNetworkRouter extends HassRouterPage {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public isWide!: boolean;
@property() public narrow!: boolean;
@property() public ozwInstance!: number;
private _configEntry = new URLSearchParams(window.location.search).get(
"config_entry"
);
protected routerOptions: RouterOptions = {
defaultPage: "dashboard",
showLoading: true,
routes: {
dashboard: {
tag: "ozw-network-dashboard",
load: () => import("./ozw-network-dashboard"),
},
nodes: {
tag: "ozw-network-nodes",
load: () => import("./ozw-network-nodes"),
},
node: {
tag: "ozw-node-router",
load: () => import("./ozw-node-router"),
},
},
};
protected updatePageEl(el): void {
el.route = computeTail(this.routeTail);
el.hass = this.hass;
el.isWide = this.isWide;
el.narrow = this.narrow;
el.configEntryId = this._configEntry;
el.ozwInstance = this.ozwInstance;
if (this._currentPage === "node") {
el.nodeId = this.routeTail.path.split("/")[1];
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ozw-network-router": OZWNetworkRouter;
}
}

View File

@ -1,265 +0,0 @@
import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { navigate } from "../../../../../common/navigate";
import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card";
import {
fetchOZWNodeConfig,
fetchOZWNodeMetadata,
fetchOZWNodeStatus,
OZWDevice,
OZWDeviceConfig,
OZWDeviceMetaDataResponse,
} from "../../../../../data/ozw";
import { ERR_NOT_FOUND } from "../../../../../data/websocket_api";
import "../../../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section";
import { ozwNodeTabs } from "./ozw-node-router";
import { showOZWRefreshNodeDialog } from "./show-dialog-ozw-refresh-node";
@customElement("ozw-node-config")
class OZWNodeConfig 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;
@property() public configEntryId?: string;
@property() public ozwInstance?;
@property() public nodeId?;
@state() private _node?: OZWDevice;
@state() private _metadata?: OZWDeviceMetaDataResponse;
@state() private _config?: OZWDeviceConfig[];
@state() private _error?: string;
protected firstUpdated() {
if (!this.ozwInstance) {
navigate("/config/ozw/dashboard", { replace: true });
} else if (!this.nodeId) {
navigate(`/config/ozw/network/${this.ozwInstance}/nodes`, {
replace: true,
});
} else {
this._fetchData();
}
}
protected render(): TemplateResult {
if (this._error) {
return html`
<hass-error-screen
.hass=${this.hass}
.error=${this.hass.localize(
"ui.panel.config.ozw.node." + this._error
)}
></hass-error-screen>
`;
}
return html`
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
.tabs=${ozwNodeTabs(this.ozwInstance, this.nodeId)}
>
<ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}>
<div slot="header">
${this.hass.localize("ui.panel.config.ozw.node_config.header")}
</div>
<div slot="introduction">
${this.hass.localize(
"ui.panel.config.ozw.node_config.introduction"
)}
<p>
<em>
${this.hass.localize(
"ui.panel.config.ozw.node_config.help_source"
)}
</em>
</p>
<p>
Note: This panel is currently read-only. The ability to change
values will come in a later update.
</p>
</div>
${this._node
? html`
<ha-card class="content">
<div class="card-content">
<b>
${this._node.node_manufacturer_name}
${this._node.node_product_name} </b
><br />
${this.hass.localize("ui.panel.config.ozw.common.node_id")}:
${this._node.node_id}<br />
${this.hass.localize(
"ui.panel.config.ozw.common.query_stage"
)}:
${this._node.node_query_stage}
${this._metadata?.metadata.ProductManualURL
? html` <a
href=${this._metadata.metadata.ProductManualURL}
>
<p>
${this.hass.localize(
"ui.panel.config.ozw.node_metadata.product_manual"
)}
</p>
</a>`
: ``}
</div>
<div class="card-actions">
<mwc-button @click=${this._refreshNodeClicked}>
${this.hass.localize(
"ui.panel.config.ozw.refresh_node.button"
)}
</mwc-button>
</div>
</ha-card>
${this._metadata?.metadata.WakeupHelp
? html`
<ha-card
class="content"
header=${this.hass.localize(
"ui.panel.config.ozw.common.wakeup_instructions"
)}
>
<div class="card-content">
<span class="secondary">
${this.hass.localize(
"ui.panel.config.ozw.node_config.wakeup_help"
)}
</span>
<p>${this._metadata.metadata.WakeupHelp}</p>
</div>
</ha-card>
`
: ``}
${this._config
? html`
${this._config.map(
(item) => html`
<ha-card class="content">
<div class="card-content">
<b>${item.label}</b><br />
<span class="secondary">${item.help}</span>
<p>${item.value}</p>
</div>
</ha-card>
`
)}
`
: ``}
`
: ``}
</ha-config-section>
</hass-tabs-subpage>
`;
}
private async _fetchData() {
if (!this.ozwInstance || !this.nodeId) {
return;
}
try {
const nodeProm = fetchOZWNodeStatus(
this.hass!,
this.ozwInstance,
this.nodeId
);
const metadataProm = fetchOZWNodeMetadata(
this.hass!,
this.ozwInstance,
this.nodeId
);
const configProm = fetchOZWNodeConfig(
this.hass!,
this.ozwInstance,
this.nodeId
);
[this._node, this._metadata, this._config] = await Promise.all([
nodeProm,
metadataProm,
configProm,
]);
} catch (err: any) {
if (err.code === ERR_NOT_FOUND) {
this._error = ERR_NOT_FOUND;
return;
}
throw err;
}
}
private async _refreshNodeClicked() {
showOZWRefreshNodeDialog(this, {
node_id: this.nodeId,
ozw_instance: this.ozwInstance,
});
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.secondary {
color: var(--secondary-text-color);
font-size: 0.9em;
}
.content {
margin-top: 24px;
}
.sectionHeader {
position: relative;
padding-right: 40px;
}
ha-card {
margin: 0 auto;
max-width: 600px;
}
[hidden] {
display: none;
}
blockquote {
display: block;
background-color: #ddd;
padding: 8px;
margin: 8px 0;
font-size: 0.9em;
}
blockquote em {
font-size: 0.9em;
margin-top: 6px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ozw-node-config": OZWNodeConfig;
}
}

View File

@ -1,254 +0,0 @@
import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { navigate } from "../../../../../common/navigate";
import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card";
import {
fetchOZWNodeMetadata,
fetchOZWNodeStatus,
OZWDevice,
OZWDeviceMetaDataResponse,
} from "../../../../../data/ozw";
import { ERR_NOT_FOUND } from "../../../../../data/websocket_api";
import "../../../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section";
import { ozwNodeTabs } from "./ozw-node-router";
import { showOZWRefreshNodeDialog } from "./show-dialog-ozw-refresh-node";
@customElement("ozw-node-dashboard")
class OZWNodeDashboard 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;
@property() public configEntryId?: string;
@property() public ozwInstance?;
@property() public nodeId?;
@state() private _node?: OZWDevice;
@state() private _metadata?: OZWDeviceMetaDataResponse;
@state() private _not_found = false;
protected firstUpdated() {
if (!this.ozwInstance) {
navigate("/config/ozw/dashboard", { replace: true });
} else if (!this.nodeId) {
navigate(`/config/ozw/network/${this.ozwInstance}/nodes`, {
replace: true,
});
} else if (this.hass) {
this._fetchData();
}
}
protected render(): TemplateResult {
if (this._not_found) {
return html`
<hass-error-screen
.hass=${this.hass}
.error=${this.hass.localize("ui.panel.config.ozw.node.not_found")}
></hass-error-screen>
`;
}
return html`
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
.tabs=${ozwNodeTabs(this.ozwInstance, this.nodeId)}
>
<ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}>
<div slot="header">Node Management</div>
<div slot="introduction">
View the status of a node and manage its configuration.
</div>
${this._node
? html`
<ha-card class="content">
<div class="card-content flex">
<div class="node-details">
<b>
${this._node.node_manufacturer_name}
${this._node.node_product_name}
</b>
<br />
Node ID: ${this._node.node_id}<br />
Query Stage: ${this._node.node_query_stage}
${this._metadata?.metadata.ProductManualURL
? html` <a
href=${this._metadata.metadata.ProductManualURL}
>
<p>Product Manual</p>
</a>`
: ``}
</div>
${this._metadata?.metadata.ProductPicBase64
? html`<img
class="product-image"
src="data:image/png;base64,${this._metadata?.metadata
.ProductPicBase64}"
/>`
: ``}
</div>
<div class="card-actions">
<mwc-button @click=${this._refreshNodeClicked}>
Refresh Node
</mwc-button>
</div>
</ha-card>
${this._metadata
? html`
<ha-card class="content" header="Description">
<div class="card-content">
${this._metadata.metadata.Description}
</div>
</ha-card>
<ha-card class="content" header="Inclusion">
<div class="card-content">
${this._metadata.metadata.InclusionHelp}
</div>
</ha-card>
<ha-card class="content" header="Exclusion">
<div class="card-content">
${this._metadata.metadata.ExclusionHelp}
</div>
</ha-card>
<ha-card class="content" header="Reset">
<div class="card-content">
${this._metadata.metadata.ResetHelp}
</div>
</ha-card>
${this._metadata.metadata.WakeupHelp
? html`
<ha-card class="content" header="WakeUp">
<div class="card-content">
${this._metadata.metadata.WakeupHelp}
</div>
</ha-card>
`
: ``}
`
: ``}
`
: ``}
</ha-config-section>
</hass-tabs-subpage>
`;
}
private async _fetchData() {
if (!this.ozwInstance || !this.nodeId) {
return;
}
try {
this._node = await fetchOZWNodeStatus(
this.hass!,
this.ozwInstance,
this.nodeId
);
this._metadata = await fetchOZWNodeMetadata(
this.hass!,
this.ozwInstance,
this.nodeId
);
} catch (err: any) {
if (err.code === ERR_NOT_FOUND) {
this._not_found = true;
return;
}
throw err;
}
}
private async _refreshNodeClicked() {
showOZWRefreshNodeDialog(this, {
node_id: this.nodeId,
ozw_instance: this.ozwInstance,
});
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.secondary {
color: var(--secondary-text-color);
}
.content {
margin-top: 24px;
}
.content:last-child {
margin-bottom: 24px;
}
.sectionHeader {
position: relative;
padding-right: 40px;
}
ha-card {
margin: 0 auto;
max-width: 600px;
}
.flex {
display: flex;
justify-content: space-between;
}
.card-actions.warning ha-call-service-button {
color: var(--error-color);
}
.toggle-help-icon {
position: absolute;
top: -6px;
right: 0;
color: var(--primary-color);
}
ha-service-description {
display: block;
color: grey;
padding: 0 8px 12px;
}
[hidden] {
display: none;
}
.product-image {
padding: 12px;
max-height: 140px;
max-width: 140px;
}
.card-actions {
clear: right;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ozw-node-dashboard": OZWNodeDashboard;
}
}

View File

@ -1,84 +0,0 @@
import { mdiNetwork, mdiWrench } from "@mdi/js";
import { customElement, property } from "lit/decorators";
import { navigate } from "../../../../../common/navigate";
import {
HassRouterPage,
RouterOptions,
} from "../../../../../layouts/hass-router-page";
import { PageNavigation } from "../../../../../layouts/hass-tabs-subpage";
import { HomeAssistant } from "../../../../../types";
export const ozwNodeTabs = (
instance: number,
node: number
): PageNavigation[] => [
{
translationKey: "ui.panel.config.ozw.navigation.node.dashboard",
path: `/config/ozw/network/${instance}/node/${node}/dashboard`,
iconPath: mdiNetwork,
},
{
translationKey: "ui.panel.config.ozw.navigation.node.config",
path: `/config/ozw/network/${instance}/node/${node}/config`,
iconPath: mdiWrench,
},
];
@customElement("ozw-node-router")
class OZWNodeRouter extends HassRouterPage {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public isWide!: boolean;
@property() public narrow!: boolean;
@property() public ozwInstance!: number;
@property() public nodeId!: number;
private _configEntry = new URLSearchParams(window.location.search).get(
"config_entry"
);
protected routerOptions: RouterOptions = {
defaultPage: "dashboard",
showLoading: true,
routes: {
dashboard: {
tag: "ozw-node-dashboard",
load: () => import("./ozw-node-dashboard"),
},
config: {
tag: "ozw-node-config",
load: () => import("./ozw-node-config"),
},
},
};
protected updatePageEl(el): void {
el.route = this.routeTail;
el.hass = this.hass;
el.isWide = this.isWide;
el.narrow = this.narrow;
el.configEntryId = this._configEntry;
el.ozwInstance = this.ozwInstance;
el.nodeId = this.nodeId;
const searchParams = new URLSearchParams(window.location.search);
if (this._configEntry && !searchParams.has("config_entry")) {
searchParams.append("config_entry", this._configEntry);
navigate(
`${this.routeTail.prefix}${
this.routeTail.path
}?${searchParams.toString()}`,
{ replace: true }
);
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ozw-node-router": OZWNodeRouter;
}
}

View File

@ -1,19 +0,0 @@
import { fireEvent } from "../../../../../common/dom/fire_event";
export interface OZWRefreshNodeDialogParams {
ozw_instance: number;
node_id: number;
}
export const loadRefreshNodeDialog = () => import("./dialog-ozw-refresh-node");
export const showOZWRefreshNodeDialog = (
element: HTMLElement,
refreshNodeDialogParams: OZWRefreshNodeDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-ozw-refresh-node",
dialogImport: loadRefreshNodeDialog,
dialogParams: refreshNodeDialogParams,
});
};

View File

@ -1,765 +0,0 @@
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { computeStateDomain } from "../../../../../common/entity/compute_state_domain";
import { computeStateName } from "../../../../../common/entity/compute_state_name";
import { sortStatesByName } from "../../../../../common/entity/states_sort_by_name";
import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card";
import "../../../../../components/ha-alert";
import "../../../../../components/ha-icon";
import "../../../../../components/ha-icon-button";
import "../../../../../components/ha-icon-button-arrow-prev";
import "../../../../../components/ha-menu-button";
import "../../../../../components/ha-service-description";
import "../../../../../layouts/ha-app-layout";
import { EventsMixin } from "../../../../../mixins/events-mixin";
import LocalizeMixin from "../../../../../mixins/localize-mixin";
import "../../../../../styles/polymer-ha-style";
import "../../../ha-config-section";
import "../../../ha-form-style";
import "./zwave-groups";
import "./zwave-log";
import "./zwave-network";
import "./zwave-node-config";
import "./zwave-node-protection";
import "./zwave-usercodes";
import "./zwave-values";
/*
* @appliesMixin LocalizeMixin
* @appliesMixin EventsMixin
*/
class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
static get template() {
return html`
<style include="iron-flex ha-style ha-form-style">
app-toolbar {
border-bottom: 1px solid var(--divider-color);
}
ha-alert {
display: block;
margin: 16px;
}
ha-alert a {
text-decoration: none;
}
.content {
margin-top: 24px;
}
.sectionHeader {
position: relative;
padding-right: 40px;
}
.node-info {
margin-left: 16px;
}
.help-text {
padding-left: 24px;
padding-right: 24px;
}
ha-card {
margin: 0 auto;
max-width: 600px;
}
.device-picker {
@apply --layout-horizontal;
@apply --layout-center-center;
padding-left: 24px;
padding-right: 24px;
padding-bottom: 24px;
}
ha-service-description {
display: block;
color: grey;
}
ha-service-description[hidden] {
display: none;
}
.toggle-help-icon {
position: absolute;
top: -6px;
right: 0;
color: var(--primary-color);
}
</style>
<ha-app-layout>
<app-header slot="header" fixed="">
<app-toolbar>
<ha-icon-button-arrow-prev
hass="[[hass]]"
on-click="_backTapped"
></ha-icon-button-arrow-prev>
<div main-title="">[[localize('component.zwave.title')]]</div>
</app-toolbar>
</app-header>
<ha-alert
alert-type="warning"
title="This integration will stop working soon"
>
This Z-Wave integration is deprecated and will no longer receive any
updates. The technical dependencies will render this integration
unusable in the near future. We strongly advise you to migrate to the
new
<a
href="https://www.home-assistant.io/integrations/zwave_js"
target="_blank"
rel="noreferrer"
>Z-Wave JS integration</a
>.
<a
slot="action"
href="https://alerts.home-assistant.io/#zwave.markdown"
target="_blank"
rel="noreferrer"
>
<mwc-button>learn more</mwc-button>
</a>
</ha-alert>
<ha-config-section is-wide="[[isWide]]">
<ha-card
class="content"
header="[[localize('ui.panel.config.zwave.migration.zwave_js.header')]]"
>
<div class="card-content">
[[localize('ui.panel.config.zwave.migration.zwave_js.introduction')]]
</div>
<div class="card-actions">
<a href="/config/zwave/migration"
><mwc-button>Start Migration to Z-Wave JS</mwc-button></a
>
</div>
</ha-card>
</ha-config-section>
<zwave-network
id="zwave-network"
is-wide="[[isWide]]"
hass="[[hass]]"
></zwave-network>
<!-- Node card -->
<ha-config-section is-wide="[[isWide]]">
<div class="sectionHeader" slot="header">
<span
>[[localize('ui.panel.config.zwave.node_management.header')]]</span
>
<ha-icon-button
class="toggle-help-icon"
on-click="toggleHelp"
label="[[localize('ui.common.help')]]"
>
<ha-icon icon="hass:help-circle"></ha-icon>
</ha-icon-button>
</div>
<span slot="introduction">
[[localize('ui.panel.config.zwave.node_management.introduction')]]
</span>
<ha-card class="content">
<div class="device-picker">
<paper-dropdown-menu
dynamic-align=""
label="[[localize('ui.panel.config.zwave.node_management.nodes')]]"
class="flex"
>
<paper-listbox
slot="dropdown-content"
selected="{{selectedNode}}"
>
<template is="dom-repeat" items="[[nodes]]" as="state">
<paper-item>[[computeSelectCaption(state)]]</paper-item>
</template>
</paper-listbox>
</paper-dropdown-menu>
</div>
<template is="dom-if" if="[[!computeIsNodeSelected(selectedNode)]]">
<template is="dom-if" if="[[showHelp]]">
<div style="color: grey; padding: 12px">
[[localize('ui.panel.config.zwave.node_management.introduction')]]
</div>
</template>
</template>
<template is="dom-if" if="[[computeIsNodeSelected(selectedNode)]]">
<div class="card-actions">
<ha-call-service-button
hass="[[hass]]"
domain="zwave"
service="refresh_node"
service-data="[[computeNodeServiceData(selectedNode)]]"
>
[[localize('ui.panel.config.zwave.services.refresh_node')]]
</ha-call-service-button>
<ha-service-description
hass="[[hass]]"
domain="zwave"
service="refresh_node"
hidden$="[[!showHelp]]"
>
</ha-service-description>
<template is="dom-if" if="[[nodeFailed]]">
<ha-call-service-button
hass="[[hass]]"
domain="zwave"
service="remove_failed_node"
service-data="[[computeNodeServiceData(selectedNode)]]"
>
[[localize('ui.panel.config.zwave.services.remove_failed_node')]]
</ha-call-service-button>
<ha-service-description
hass="[[hass]]"
domain="zwave"
service="remove_failed_node"
hidden$="[[!showHelp]]"
>
</ha-service-description>
<ha-call-service-button
hass="[[hass]]"
domain="zwave"
service="replace_failed_node"
service-data="[[computeNodeServiceData(selectedNode)]]"
>
[[localize('ui.panel.config.zwave.services.replace_failed_node')]]
</ha-call-service-button>
<ha-service-description
hass="[[hass]]"
domain="zwave"
service="replace_failed_node"
hidden$="[[!showHelp]]"
>
</ha-service-description>
</template>
<ha-call-service-button
hass="[[hass]]"
domain="zwave"
service="print_node"
service-data="[[computeNodeServiceData(selectedNode)]]"
>
[[localize('ui.panel.config.zwave.services.print_node')]]
</ha-call-service-button>
<ha-service-description
hass="[[hass]]"
domain="zwave"
service="print_node"
hidden$="[[!showHelp]]"
>
</ha-service-description>
<ha-call-service-button
hass="[[hass]]"
domain="zwave"
service="heal_node"
service-data="[[computeHealNodeServiceData(selectedNode)]]"
>
[[localize('ui.panel.config.zwave.services.heal_node')]]
</ha-call-service-button>
<ha-service-description
hass="[[hass]]"
domain="zwave"
service="heal_node"
hidden$="[[!showHelp]]"
>
</ha-service-description>
<ha-call-service-button
hass="[[hass]]"
domain="zwave"
service="test_node"
service-data="[[computeNodeServiceData(selectedNode)]]"
>
[[localize('ui.panel.config.zwave.services.test_node')]]
</ha-call-service-button>
<ha-service-description
hass="[[hass]]"
domain="zwave"
service="test_node"
hidden$="[[!showHelp]]"
>
</ha-service-description>
<mwc-button on-click="_nodeMoreInfo"
>[[localize('ui.panel.config.zwave.services.node_info')]]</mwc-button
>
</div>
<div class="device-picker">
<paper-dropdown-menu
label="[[localize('ui.panel.config.zwave.node_management.entities')]]"
dynamic-align=""
class="flex"
>
<paper-listbox
slot="dropdown-content"
selected="{{selectedEntity}}"
>
<template is="dom-repeat" items="[[entities]]" as="state">
<paper-item>[[state.entity_id]]</paper-item>
</template>
</paper-listbox>
</paper-dropdown-menu>
</div>
<template
is="dom-if"
if="[[!computeIsEntitySelected(selectedEntity)]]"
>
<div class="card-actions">
<ha-call-service-button
hass="[[hass]]"
domain="zwave"
service="refresh_entity"
service-data="[[computeRefreshEntityServiceData(selectedEntity)]]"
>
[[localize('ui.panel.config.zwave.services.refresh_entity')]]
</ha-call-service-button>
<ha-service-description
hass="[[hass]]"
domain="zwave"
service="refresh_entity"
hidden$="[[!showHelp]]"
>
</ha-service-description>
<mwc-button on-click="_entityMoreInfo"
>[[localize('ui.panel.config.zwave.node_management.entity_info')]]</mwc-button
>
</div>
<div class="form-group">
<ha-formfield
label="[[localize('ui.panel.config.zwave.node_management.exclude_entity')]]"
>
<ha-checkbox
checked="[[entityIgnored]]"
class="form-control"
on-change="entityIgnoredChanged"
>
</ha-checkbox>
</ha-formfield>
<paper-input
disabled="{{entityIgnored}}"
label="[[localize('ui.panel.config.zwave.node_management.pooling_intensity')]]"
type="number"
min="0"
value="{{entityPollingIntensity}}"
>
</paper-input>
</div>
<div class="card-actions">
<ha-call-service-button
hass="[[hass]]"
domain="zwave"
service="set_poll_intensity"
service-data="[[computePollIntensityServiceData(entityPollingIntensity)]]"
>
[[localize('ui.common.save')]]
</ha-call-service-button>
</div>
</template>
</template>
</ha-card>
<template is="dom-if" if="[[computeIsNodeSelected(selectedNode)]]">
<!-- Value card -->
<zwave-values
hass="[[hass]]"
nodes="[[nodes]]"
selected-node="[[selectedNode]]"
values="[[values]]"
></zwave-values>
<!-- Group card -->
<zwave-groups
hass="[[hass]]"
nodes="[[nodes]]"
selected-node="[[selectedNode]]"
groups="[[groups]]"
></zwave-groups>
<!-- Config card -->
<zwave-node-config
hass="[[hass]]"
nodes="[[nodes]]"
selected-node="[[selectedNode]]"
config="[[config]]"
></zwave-node-config>
</template>
<!-- Protection card -->
<template is="dom-if" if="{{_protectionNode}}">
<zwave-node-protection
hass="[[hass]]"
nodes="[[nodes]]"
selected-node="[[selectedNode]]"
protection="[[_protection]]"
></zwave-node-protection>
</template>
<!-- User Codes -->
<template is="dom-if" if="{{hasNodeUserCodes}}">
<zwave-usercodes
id="zwave-usercodes"
hass="[[hass]]"
nodes="[[nodes]]"
user-codes="[[userCodes]]"
selected-node="[[selectedNode]]"
></zwave-usercodes>
</template>
</ha-config-section>
<!-- Ozw log -->
<ozw-log is-wide="[[isWide]]" hass="[[hass]]"></ozw-log>
</ha-app-layout>
`;
}
static get properties() {
return {
hass: Object,
isWide: Boolean,
nodes: {
type: Array,
computed: "computeNodes(hass)",
},
selectedNode: {
type: Number,
value: -1,
observer: "selectedNodeChanged",
},
nodeFailed: {
type: Boolean,
value: false,
},
config: {
type: Array,
value: () => [],
},
entities: {
type: Array,
computed: "computeEntities(selectedNode)",
},
selectedEntity: {
type: Number,
value: -1,
observer: "selectedEntityChanged",
},
values: {
type: Array,
},
groups: {
type: Array,
},
userCodes: {
type: Array,
value: () => [],
},
hasNodeUserCodes: {
type: Boolean,
value: false,
},
showHelp: {
type: Boolean,
value: false,
},
entityIgnored: Boolean,
entityPollingIntensity: {
type: Number,
value: 0,
},
_protection: {
type: Array,
value: () => [],
},
_protectionNode: {
type: Boolean,
value: false,
},
};
}
ready() {
super.ready();
import("web-animations-js/web-animations-next-lite.min");
this.addEventListener("hass-service-called", (ev) =>
this.serviceCalled(ev)
);
}
attached() {
setCancelSyntheticClickEvents(true);
}
detached() {
setCancelSyntheticClickEvents(false);
}
serviceCalled(ev) {
if (ev.detail.success && ev.detail.service === "set_poll_intensity") {
this._saveEntity();
}
}
computeNodes(hass) {
return Object.keys(hass.states)
.map((key) => hass.states[key])
.filter((ent) => ent.entity_id.match("zwave[.]"))
.sort(sortStatesByName);
}
computeEntities(selectedNode) {
if (!this.nodes || selectedNode === -1) {
return -1;
}
const nodeid = this.nodes[this.selectedNode].attributes.node_id;
const hass = this.hass;
return Object.keys(this.hass.states)
.map((key) => hass.states[key])
.filter((ent) => {
if (ent.attributes.node_id === undefined) {
return false;
}
return (
"node_id" in ent.attributes &&
ent.attributes.node_id === nodeid &&
!ent.entity_id.match("zwave[.]")
);
})
.sort(sortStatesByName);
}
selectedNodeChanged(selectedNode) {
if (selectedNode === -1) {
return;
}
this.selectedEntity = -1;
this.hass
.callApi(
"GET",
`zwave/config/${this.nodes[selectedNode].attributes.node_id}`
)
.then((configs) => {
this.config = this._objToArray(configs);
});
this.hass
.callApi(
"GET",
`zwave/values/${this.nodes[selectedNode].attributes.node_id}`
)
.then((values) => {
this.values = this._objToArray(values);
});
this.hass
.callApi(
"GET",
`zwave/groups/${this.nodes[selectedNode].attributes.node_id}`
)
.then((groups) => {
this.groups = this._objToArray(groups);
});
this.hasNodeUserCodes = false;
this.notifyPath("hasNodeUserCodes");
this.hass
.callApi(
"GET",
`zwave/usercodes/${this.nodes[selectedNode].attributes.node_id}`
)
.then((usercodes) => {
this.userCodes = this._objToArray(usercodes);
this.hasNodeUserCodes = this.userCodes.length > 0;
this.notifyPath("hasNodeUserCodes");
});
this.hass
.callApi(
"GET",
`zwave/protection/${this.nodes[selectedNode].attributes.node_id}`
)
.then((protections) => {
this._protection = this._objToArray(protections);
if (this._protection) {
if (this._protection.length === 0) {
return;
}
this._protectionNode = true;
}
});
this.nodeFailed = this.nodes[selectedNode].attributes.is_failed;
}
selectedEntityChanged(selectedEntity) {
if (selectedEntity === -1) {
return;
}
this.hass
.callApi(
"GET",
`zwave/values/${this.nodes[this.selectedNode].attributes.node_id}`
)
.then((values) => {
this.values = this._objToArray(values);
});
const valueId = this.entities[selectedEntity].attributes.value_id;
const valueData = this.values.find((obj) => obj.key === valueId);
const valueIndex = this.values.indexOf(valueData);
this.hass
.callApi(
"GET",
`config/zwave/device_config/${this.entities[selectedEntity].entity_id}`
)
.then((data) => {
this.setProperties({
entityIgnored: data.ignored || false,
entityPollingIntensity: this.values[valueIndex].value.poll_intensity,
});
})
.catch(() => {
this.setProperties({
entityIgnored: false,
entityPollingIntensity: this.values[valueIndex].value.poll_intensity,
});
});
}
computeSelectCaption(stateObj) {
return (
computeStateName(stateObj) +
" (Node:" +
stateObj.attributes.node_id +
" " +
stateObj.attributes.query_stage +
")"
);
}
computeSelectCaptionEnt(stateObj) {
return computeStateDomain(stateObj) + "." + computeStateName(stateObj);
}
computeIsNodeSelected() {
return this.nodes && this.selectedNode !== -1;
}
computeIsEntitySelected(selectedEntity) {
return selectedEntity === -1;
}
computeNodeServiceData(selectedNode) {
return { node_id: this.nodes[selectedNode].attributes.node_id };
}
computeHealNodeServiceData(selectedNode) {
return {
node_id: this.nodes[selectedNode].attributes.node_id,
return_routes: true,
};
}
computeRefreshEntityServiceData(selectedEntity) {
if (selectedEntity === -1) {
return -1;
}
return { entity_id: this.entities[selectedEntity].entity_id };
}
computePollIntensityServiceData(entityPollingIntensity) {
if (this.selectedNode === -1 || this.selectedEntity === -1) {
return -1;
}
return {
node_id: this.nodes[this.selectedNode].attributes.node_id,
value_id: this.entities[this.selectedEntity].attributes.value_id,
poll_intensity: parseInt(entityPollingIntensity),
};
}
_nodeMoreInfo() {
this.fire("hass-more-info", {
entityId: this.nodes[this.selectedNode].entity_id,
});
}
_entityMoreInfo() {
this.fire("hass-more-info", {
entityId: this.entities[this.selectedEntity].entity_id,
});
}
_saveEntity() {
const data = {
ignored: this.entityIgnored,
polling_intensity: parseInt(this.entityPollingIntensity),
};
return this.hass.callApi(
"POST",
`config/zwave/device_config/${
this.entities[this.selectedEntity].entity_id
}`,
data
);
}
toggleHelp() {
this.showHelp = !this.showHelp;
}
_objToArray(obj) {
const array = [];
Object.keys(obj).forEach((key) => {
array.push({
key,
value: obj[key],
});
});
return array;
}
_backTapped() {
history.back();
}
entityIgnoredChanged(ev) {
this.entityIgnored = ev.target.checked;
}
}
customElements.define("ha-config-zwave", HaConfigZwave);

View File

@ -1,62 +0,0 @@
import { customElement, property } from "lit/decorators";
import { navigate } from "../../../../../common/navigate";
import {
HassRouterPage,
RouterOptions,
} from "../../../../../layouts/hass-router-page";
import { HomeAssistant } from "../../../../../types";
@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.routeTail.prefix}${
this.routeTail.path
}?${searchParams.toString()}`,
{ replace: true }
);
}
}
}
declare global {
interface HTMLElementTagNameMap {
"zwave-config-router": ZWaveConfigRouter;
}
}

View File

@ -1,380 +0,0 @@
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { computeStateName } from "../../../../../common/entity/compute_state_name";
import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card";
import LocalizeMixin from "../../../../../mixins/localize-mixin";
import "../../../../../styles/polymer-ha-style";
class ZwaveGroups extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex ha-style">
.content {
margin-top: 24px;
}
ha-card {
margin: 0 auto;
max-width: 600px;
}
.device-picker {
@apply --layout-horizontal;
@apply --layout-center-center;
padding-left: 24px;
padding-right: 24px;
padding-bottom: 24px;
}
.help-text {
padding-left: 24px;
padding-right: 24px;
padding-bottom: 12px;
}
</style>
<ha-card
class="content"
header="[[localize('ui.panel.config.zwave.node_management.node_group_associations')]]"
>
<!-- TODO make api for getting groups and members -->
<div class="device-picker">
<paper-dropdown-menu
label="[[localize('ui.panel.config.zwave.node_management.group')]]"
dynamic-align=""
class="flex"
>
<paper-listbox
slot="dropdown-content"
selected="{{_selectedGroup}}"
>
<template is="dom-repeat" items="[[groups]]" as="state">
<paper-item>[[_computeSelectCaptionGroup(state)]]</paper-item>
</template>
</paper-listbox>
</paper-dropdown-menu>
</div>
<template is="dom-if" if="[[_computeIsGroupSelected(_selectedGroup)]]">
<div class="device-picker">
<paper-dropdown-menu
label="[[localize('ui.panel.config.zwave.node_management.node_to_control')]]"
dynamic-align=""
class="flex"
>
<paper-listbox
slot="dropdown-content"
selected="{{_selectedTargetNode}}"
>
<template is="dom-repeat" items="[[nodes]]" as="state">
<paper-item>[[_computeSelectCaption(state)]]</paper-item>
</template>
</paper-listbox>
</paper-dropdown-menu>
</div>
<div class="help-text">
<span
>[[localize('ui.panel.config.zwave.node_management.nodes_in_group')]]</span
>
<template is="dom-repeat" items="[[_otherGroupNodes]]" as="state">
<div>[[state]]</div>
</template>
</div>
<div class="help-text">
<span
>[[localize('ui.panel.config.zwave.node_management.max_associations')]]</span
>
<span>[[_maxAssociations]]</span>
</div>
</template>
<template
is="dom-if"
if="[[_computeIsTargetNodeSelected(_selectedTargetNode)]]"
>
<div class="card-actions">
<template is="dom-if" if="[[!_noAssociationsLeft]]">
<ha-call-service-button
hass="[[hass]]"
domain="zwave"
service="change_association"
service-data="[[_addAssocServiceData]]"
>
[[localize('ui.panel.config.zwave.node_management.add_to_group')]]
</ha-call-service-button>
</template>
<template
is="dom-if"
if="[[_computeTargetInGroup(_selectedGroup, _selectedTargetNode)]]"
>
<ha-call-service-button
hass="[[hass]]"
domain="zwave"
service="change_association"
service-data="[[_removeAssocServiceData]]"
>
[[localize('ui.panel.config.zwave.node_management.remove_from_group')]]
</ha-call-service-button>
</template>
<template is="dom-if" if="[[_isBroadcastNodeInGroup]]">
<ha-call-service-button
hass="[[hass]]"
domain="zwave"
service="change_association"
service-data="[[_removeBroadcastNodeServiceData]]"
>
[[localize('ui.panel.config.zwave.node_management.remove_broadcast')]]
</ha-call-service-button>
</template>
</div>
</template>
</ha-card>
`;
}
static get properties() {
return {
hass: Object,
nodes: Array,
groups: Array,
selectedNode: {
type: Number,
observer: "_selectedNodeChanged",
},
_selectedTargetNode: {
type: Number,
value: -1,
observer: "_selectedTargetNodeChanged",
},
_selectedGroup: {
type: Number,
value: -1,
},
_otherGroupNodes: {
type: Array,
value: -1,
computed: "_computeOtherGroupNodes(_selectedGroup)",
},
_maxAssociations: {
type: String,
value: "",
computed: "_computeMaxAssociations(_selectedGroup)",
},
_noAssociationsLeft: {
type: Boolean,
value: true,
computed: "_computeAssociationsLeft(_selectedGroup)",
},
_addAssocServiceData: {
type: String,
value: "",
},
_removeAssocServiceData: {
type: String,
value: "",
},
_removeBroadcastNodeServiceData: {
type: String,
value: "",
},
_isBroadcastNodeInGroup: {
type: Boolean,
value: false,
},
};
}
static get observers() {
return ["_selectedGroupChanged(groups, _selectedGroup)"];
}
ready() {
super.ready();
this.addEventListener("hass-service-called", (ev) =>
this.serviceCalled(ev)
);
}
serviceCalled(ev) {
if (ev.detail.success) {
setTimeout(() => {
this._refreshGroups(this.selectedNode);
}, 5000);
}
}
_computeAssociationsLeft(selectedGroup) {
if (selectedGroup === -1) return true;
return this._maxAssociations === this._otherGroupNodes.length;
}
_computeMaxAssociations(selectedGroup) {
if (selectedGroup === -1) return -1;
const maxAssociations = this.groups[selectedGroup].value.max_associations;
if (!maxAssociations) return "None";
return maxAssociations;
}
_computeOtherGroupNodes(selectedGroup) {
if (selectedGroup === -1) return -1;
this.setProperties({ _isBroadcastNodeInGroup: false });
const associations = Object.values(
this.groups[selectedGroup].value.association_instances
);
if (!associations.length) return ["None"];
return associations.map((assoc) => {
if (!assoc.length || assoc.length !== 2) {
return `Unknown Node: ${assoc}`;
}
const id = assoc[0];
const instance = assoc[1];
const node = this.nodes.find((n) => n.attributes.node_id === id);
if (id === 255) {
this.setProperties({
_isBroadcastNodeInGroup: true,
_removeBroadcastNodeServiceData: {
node_id: this.nodes[this.selectedNode].attributes.node_id,
association: "remove",
target_node_id: 255,
group: this.groups[selectedGroup].key,
},
});
}
if (!node) {
return `Unknown Node (${id}: (${instance} ? ${id}.${instance} : ${id}))`;
}
let caption = this._computeSelectCaption(node);
if (instance) {
caption += `/ Instance: ${instance}`;
}
return caption;
});
}
_computeTargetInGroup(selectedGroup, selectedTargetNode) {
if (selectedGroup === -1 || selectedTargetNode === -1) return false;
const associations = Object.values(
this.groups[selectedGroup].value.associations
);
if (!associations.length) return false;
return (
associations.indexOf(
this.nodes[selectedTargetNode].attributes.node_id
) !== -1
);
}
_computeSelectCaption(stateObj) {
return `${computeStateName(stateObj)}
(Node: ${stateObj.attributes.node_id}
${stateObj.attributes.query_stage})`;
}
_computeSelectCaptionGroup(stateObj) {
return `${stateObj.key}: ${stateObj.value.label}`;
}
_computeIsTargetNodeSelected(selectedTargetNode) {
return this.nodes && selectedTargetNode !== -1;
}
_computeIsGroupSelected(selectedGroup) {
return this.nodes && this.selectedNode !== -1 && selectedGroup !== -1;
}
_computeAssocServiceData(selectedGroup, type) {
if (
!this.groups ||
selectedGroup === -1 ||
this.selectedNode === -1 ||
this._selectedTargetNode === -1
) {
return -1;
}
return {
node_id: this.nodes[this.selectedNode].attributes.node_id,
association: type,
target_node_id: this.nodes[this._selectedTargetNode].attributes.node_id,
group: this.groups[selectedGroup].key,
};
}
async _refreshGroups(selectedNode) {
const groupData = [];
const groups = await this.hass.callApi(
"GET",
`zwave/groups/${this.nodes[selectedNode].attributes.node_id}`
);
Object.keys(groups).forEach((key) => {
groupData.push({
key,
value: groups[key],
});
});
this.setProperties({
groups: groupData,
_maxAssociations: groupData[this._selectedGroup].value.max_associations,
_otherGroupNodes: Object.values(
groupData[this._selectedGroup].value.associations
),
_isBroadcastNodeInGroup: false,
});
const oldGroup = this._selectedGroup;
this.setProperties({ _selectedGroup: -1 });
this.setProperties({ _selectedGroup: oldGroup });
}
_selectedGroupChanged() {
if (this._selectedGroup === -1) return;
this.setProperties({
_maxAssociations: this.groups[this._selectedGroup].value.max_associations,
_otherGroupNodes: Object.values(
this.groups[this._selectedGroup].value.associations
),
});
}
_selectedTargetNodeChanged() {
if (this._selectedGroup === -1) return;
if (
this._computeTargetInGroup(this._selectedGroup, this._selectedTargetNode)
) {
this.setProperties({
_removeAssocServiceData: this._computeAssocServiceData(
this._selectedGroup,
"remove"
),
});
} else {
this.setProperties({
_addAssocServiceData: this._computeAssocServiceData(
this._selectedGroup,
"add"
),
});
}
}
_selectedNodeChanged() {
if (this.selectedNode === -1) return;
this.setProperties({ _selectedTargetNode: -1, _selectedGroup: -1 });
}
}
customElements.define("zwave-groups", ZwaveGroups);

View File

@ -1,83 +0,0 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { EventsMixin } from "../../../../../mixins/events-mixin";
import "../../../../../styles/polymer-ha-style-dialog";
import "../../../../../components/ha-dialog";
class ZwaveLogDialog extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="ha-style-dialog">
pre {
font-family: var(--code-font-family, monospace);
}
</style>
<ha-dialog open="[[_opened]]" heading="OpenZwave internal logfile" on-closed="closeDialog">
<div>
<pre>[[_ozwLog]]</pre>
<div>
</ha-dialog>
`;
}
static get properties() {
return {
hass: Object,
_ozwLog: String,
_dialogClosedCallback: Function,
_opened: {
type: Boolean,
value: false,
},
_intervalId: String,
_numLogLines: {
type: Number,
},
};
}
ready() {
super.ready();
this.addEventListener("iron-overlay-closed", (ev) =>
this._dialogClosed(ev)
);
}
showDialog({ _ozwLog, hass, _tail, _numLogLines, dialogClosedCallback }) {
this.hass = hass;
this._ozwLog = _ozwLog;
this._opened = true;
this._dialogClosedCallback = dialogClosedCallback;
this._numLogLines = _numLogLines;
if (_tail) {
this.setProperties({
_intervalId: setInterval(() => {
this._refreshLog();
}, 1500),
});
}
}
closeDialog() {
clearInterval(this._intervalId);
this._opened = false;
const closedEvent = true;
this._dialogClosedCallback({ closedEvent });
this._dialogClosedCallback = null;
}
async _refreshLog() {
const info = await this.hass.callApi(
"GET",
"zwave/ozwlog?lines=" + this._numLogLines
);
this.setProperties({ _ozwLog: info });
}
}
customElements.define("zwave-log-dialog", ZwaveLogDialog);

View File

@ -1,160 +0,0 @@
import "@material/mwc-button";
import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import isPwa from "../../../../../common/config/is_pwa";
import "../../../../../components/ha-card";
import { EventsMixin } from "../../../../../mixins/events-mixin";
import LocalizeMixin from "../../../../../mixins/localize-mixin";
import "../../../../../styles/polymer-ha-style";
import "../../../ha-config-section";
let registeredDialog = false;
class OzwLog extends LocalizeMixin(EventsMixin(PolymerElement)) {
static get template() {
return html`
<style include="iron-flex ha-style">
.content {
margin-top: 24px;
}
ha-card {
margin: 0 auto;
max-width: 600px;
}
.device-picker {
padding-left: 24px;
padding-right: 24px;
padding-bottom: 24px;
}
</style>
<ha-config-section is-wide="[[isWide]]">
<span slot="header">
[[localize('ui.panel.config.zwave.ozw_log.header')]]
</span>
<span slot="introduction">
[[localize('ui.panel.config.zwave.ozw_log.introduction')]]
</span>
<ha-card class="content">
<div class="device-picker">
<paper-input label="[[localize('ui.panel.config.zwave.ozw_log.last_log_lines')]]" type="number" min="0" max="1000" step="10" value="{{numLogLines}}">
</paper-input>
</div>
<div class="card-actions">
<mwc-button raised="true" on-click="_openLogWindow">[[localize('ui.panel.config.zwave.ozw_log.load')]]</mwc-button>
<mwc-button raised="true" on-click="_tailLog" disabled="{{_completeLog}}">[[localize('ui.panel.config.zwave.ozw_log.tail')]]</mwc-button>
</ha-card>
</ha-config-section>
`;
}
static get properties() {
return {
hass: Object,
isWide: {
type: Boolean,
value: false,
},
_ozwLogs: String,
_completeLog: {
type: Boolean,
value: true,
},
numLogLines: {
type: Number,
value: 0,
observer: "_isCompleteLog",
},
_intervalId: String,
tail: Boolean,
};
}
async _tailLog() {
this.setProperties({ tail: true });
const ozwWindow = await this._openLogWindow();
if (!isPwa()) {
this.setProperties({
_intervalId: setInterval(() => {
this._refreshLog(ozwWindow);
}, 1500),
});
}
}
async _openLogWindow() {
const info = await this.hass.callApi(
"GET",
"zwave/ozwlog?lines=" + this.numLogLines
);
this.setProperties({ _ozwLogs: info });
if (isPwa()) {
this._showOzwlogDialog();
return -1;
}
const ozwWindow = open("", "ozwLog", "toolbar");
ozwWindow.document.body.innerHTML = `<pre>${this._ozwLogs}</pre>`;
return ozwWindow;
}
async _refreshLog(ozwWindow) {
if (ozwWindow.closed === true) {
clearInterval(this._intervalId);
this.setProperties({ _intervalId: null });
} else {
const info = await this.hass.callApi(
"GET",
"zwave/ozwlog?lines=" + this.numLogLines
);
this.setProperties({ _ozwLogs: info });
ozwWindow.document.body.innerHTML = `<pre>${this._ozwLogs}</pre>`;
}
}
_isCompleteLog() {
if (this.numLogLines !== "0") {
this.setProperties({ _completeLog: false });
} else {
this.setProperties({ _completeLog: true });
}
}
connectedCallback() {
super.connectedCallback();
if (!registeredDialog) {
registeredDialog = true;
this.fire("register-dialog", {
dialogShowEvent: "show-ozwlog-dialog",
dialogTag: "zwave-log-dialog",
dialogImport: () => import("./zwave-log-dialog"),
});
}
}
_showOzwlogDialog() {
this.fire("show-ozwlog-dialog", {
hass: this.hass,
_numLogLines: this.numLogLines,
_ozwLog: this._ozwLogs,
_tail: this.tail,
dialogClosedCallback: () => this._dialogClosed(),
});
}
_dialogClosed() {
this.setProperties({
tail: false,
});
}
}
customElements.define("ozw-log", OzwLog);

View File

@ -1,573 +0,0 @@
import "@material/mwc-button/mwc-button";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../../../../common/config/is_component_loaded";
import { computeStateDomain } from "../../../../../common/entity/compute_state_domain";
import { computeStateName } from "../../../../../common/entity/compute_state_name";
import "../../../../../components/buttons/ha-call-api-button";
import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-alert";
import "../../../../../components/ha-card";
import "../../../../../components/ha-circular-progress";
import "../../../../../components/ha-icon";
import "../../../../../components/ha-icon-button";
import {
computeDeviceName,
DeviceRegistryEntry,
fetchDeviceRegistry,
subscribeDeviceRegistry,
} from "../../../../../data/device_registry";
import {
fetchMigrationConfig,
fetchNetworkStatus,
startZwaveJsConfigFlow,
ZWaveMigrationConfig,
ZWaveNetworkStatus,
ZWAVE_NETWORK_STATE_STOPPED,
} from "../../../../../data/zwave";
import {
fetchZwaveNetworkStatus as fetchZwaveJsNetworkStatus,
fetchZwaveNodeStatus,
getZwaveJsIdentifiersFromDevice,
migrateZwave,
subscribeZwaveNodeReady,
ZWaveJsMigrationData,
} from "../../../../../data/zwave_js";
import { showConfigFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-config-flow";
import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
import "../../../../../layouts/hass-subpage";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section";
@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;
@state() private _networkStatus?: ZWaveNetworkStatus;
@state() private _step = 0;
@state() private _stoppingNetwork = false;
@state() private _migrationConfig?: ZWaveMigrationConfig;
@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;
public disconnectedCallback(): void {
this._unsubscribe();
if (this._unsubDevices) {
this._unsubDevices();
this._unsubDevices = undefined;
}
}
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.zwave_js.header"
)}
</div>
<div slot="introduction">
${this.hass.localize(
"ui.panel.config.zwave.migration.zwave_js.introduction"
)}
</div>
${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" &&
!["ready", "sleeping"].includes(entityState.state)
)
.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>
If you have 'zwave' in your configurtion.yaml file, you
should remove it now.
</p>
</div>
<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>
`;
}
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 _setupZwaveJs() {
const zwaveJsConfigFlow = await startZwaveJsConfigFlow(this.hass);
showConfigFlowDialog(this, {
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", "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) =>
fetchZwaveNodeStatus(this.hass, this._zwaveJsEntryId!, nodeId)
);
const nodesNotReady = (await Promise.all(nodeStatePromisses)).filter(
(node) => !node.ready
);
// eslint-disable-next-line no-console
console.log("waiting for nodes to be ready", nodesNotReady);
this._getMigrationData();
if (nodesNotReady.length === 0) {
this._waitingOnDevices = [];
return;
}
this._nodeReadySubscriptions = nodesNotReady.map((node) =>
subscribeZwaveNodeReady(
this.hass,
this._zwaveJsEntryId!,
node.node_id,
() => {
this._getZwaveJSNodesStatus();
}
)
);
const deviceReg: DeviceRegistryEntry[] = await fetchDeviceRegistry(
this.hass.connection
);
this._waitingOnDevices = deviceReg.filter((device) => {
const identifiers = getZwaveJsIdentifiersFromDevice(device);
if (
!identifiers ||
Number(identifiers.home_id) !== networkStatus.controller.home_id
) {
return false;
}
return nodesNotReady.some((node) => identifiers.node_id === node.node_id);
});
}
private async _getMigrationData() {
try {
this._migrationData = await migrateZwave(
this.hass,
this._zwaveJsEntryId!,
true
);
} catch (err: any) {
showAlertDialog(this, {
title: "Failed to get migration data!",
text:
err.code === "unknown_command"
? "Restart Home Assistant and try again."
: err.message,
});
return;
}
this._migratedZwaveEntities = Object.keys(
this._migrationData.migration_entity_map
);
if (Object.keys(this._migrationData.migration_device_map).length) {
this._fetchDevices();
}
}
private _fetchDevices() {
this._unsubDevices = subscribeDeviceRegistry(
this.hass.connection,
(devices) => {
if (!this._migrationData) {
return;
}
const migrationDevices = Object.keys(
this._migrationData.migration_device_map
);
const deviceNameLookup = {};
devices.forEach((device) => {
if (migrationDevices.includes(device.id)) {
deviceNameLookup[device.id] = computeDeviceName(device, this.hass);
}
});
this._deviceNameLookup = deviceNameLookup;
}
);
}
private async _doMigrate() {
const data = await migrateZwave(this.hass, this._zwaveJsEntryId!, false);
if (!data.migrated) {
showAlertDialog(this, { title: "Migration failed!" });
return;
}
this._step = 4;
}
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(): CSSResultGroup {
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

@ -1,302 +0,0 @@
import { mdiCheckboxMarkedCircle, mdiClose, mdiHelpCircle } from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../../components/buttons/ha-call-api-button";
import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card";
import "../../../../../components/ha-circular-progress";
import "../../../../../components/ha-svg-icon";
import "../../../../../components/ha-icon-button";
import "../../../../../components/ha-service-description";
import {
fetchNetworkStatus,
ZWaveNetworkStatus,
ZWAVE_NETWORK_STATE_AWAKED,
ZWAVE_NETWORK_STATE_READY,
ZWAVE_NETWORK_STATE_STARTED,
ZWAVE_NETWORK_STATE_STOPPED,
} from "../../../../../data/zwave";
import { haStyle } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types";
import { documentationUrl } from "../../../../../util/documentation-url";
import "../../../ha-config-section";
@customElement("zwave-network")
export class ZwaveNetwork extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public isWide!: boolean;
@state() private _showHelp = false;
@state() private _networkStatus?: ZWaveNetworkStatus;
@state() private _unsubs: Array<Promise<UnsubscribeFunc>> = [];
public disconnectedCallback(): void {
this._unsubscribe();
}
protected firstUpdated(changedProps): void {
super.firstUpdated(changedProps);
this._getNetworkStatus();
this._subscribe();
}
protected render(): TemplateResult {
return html`
<ha-config-section .isWide=${this.isWide}>
<div class="sectionHeader" slot="header">
<span>
${this.hass!.localize(
"ui.panel.config.zwave.network_management.header"
)}
</span>
<ha-icon-button
class="toggle-help-icon"
@click=${this._onHelpTap}
.path=${mdiHelpCircle}
.label=${this.hass!.localize("ui.common.help")}
></ha-icon-button>
</div>
<div slot="introduction">
${this.hass!.localize(
"ui.panel.config.zwave.network_management.introduction"
)}
<p>
<a
href=${documentationUrl(this.hass, "/docs/z-wave/control-panel/")}
target="_blank"
rel="noreferrer"
>
${this.hass!.localize("ui.panel.config.zwave.learn_more")}
</a>
</p>
</div>
${this._networkStatus
? html`
<ha-card class="content network-status">
<div class="details">
${this._networkStatus.state === ZWAVE_NETWORK_STATE_STOPPED
? html`
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
${this.hass!.localize(
"ui.panel.config.zwave.network_status.network_stopped"
)}
`
: this._networkStatus.state === ZWAVE_NETWORK_STATE_STARTED
? html`
<ha-circular-progress active></ha-circular-progress>
${this.hass!.localize(
"ui.panel.config.zwave.network_status.network_starting"
)}<br />
<small>
${this.hass!.localize(
"ui.panel.config.zwave.network_status.network_starting_note"
)}
</small>
`
: this._networkStatus.state === ZWAVE_NETWORK_STATE_AWAKED
? html`
<ha-svg-icon
.path=${mdiCheckboxMarkedCircle}
></ha-svg-icon>
${this.hass!.localize(
"ui.panel.config.zwave.network_status.network_started"
)}<br />
<small>
${this.hass!.localize(
"ui.panel.config.zwave.network_status.network_started_note_some_queried"
)}
</small>
`
: this._networkStatus.state === ZWAVE_NETWORK_STATE_READY
? html`
${this.hass!.localize(
"ui.panel.config.zwave.network_status.network_started"
)}<br />
<small>
${this.hass!.localize(
"ui.panel.config.zwave.network_status.network_started_note_all_queried"
)}
</small>
`
: ""}
</div>
<div class="card-actions">
${this._networkStatus.state >= ZWAVE_NETWORK_STATE_AWAKED
? html`
${this._generateServiceButton("stop_network")}
${this._generateServiceButton("heal_network")}
${this._generateServiceButton("test_network")}
`
: html` ${this._generateServiceButton("start_network")} `}
</div>
${this._networkStatus.state >= ZWAVE_NETWORK_STATE_AWAKED
? html`
<div class="card-actions">
${this._generateServiceButton("soft_reset")}
<ha-call-api-button
.hass=${this.hass}
path="zwave/saveconfig"
>
${this.hass!.localize(
"ui.panel.config.zwave.services.save_config"
)}
</ha-call-api-button>
</div>
`
: ""}
</ha-card>
${this._networkStatus.state >= ZWAVE_NETWORK_STATE_AWAKED
? html`
<ha-card class="content">
<div class="card-actions">
${this._generateServiceButton("add_node_secure")}
${this._generateServiceButton("add_node")}
${this._generateServiceButton("remove_node")}
</div>
<div class="card-actions">
${this._generateServiceButton("cancel_command")}
</div>
</ha-card>
`
: ""}
`
: ""}
</ha-config-section>
`;
}
private async _getNetworkStatus(): Promise<void> {
this._networkStatus = await fetchNetworkStatus(this.hass!);
}
private _subscribe(): void {
this._unsubs = [
"zwave.network_start",
"zwave.network_stop",
"zwave.network_ready",
"zwave.network_complete",
"zwave.network_complete_some_dead",
].map((e) =>
this.hass!.connection.subscribeEvents(
(event) => this._handleEvent(event),
e
)
);
}
private _unsubscribe(): void {
while (this._unsubs.length) {
this._unsubs.pop()!.then((unsub) => unsub());
}
}
private _handleEvent(event) {
if (event.event_type === "zwave.network_start") {
// Optimistically set the state, wait 1s and poll the backend
// The backend will still report a state of 0 when the 'network_start'
// event is first fired.
if (this._networkStatus) {
this._networkStatus = { ...this._networkStatus, state: 5 };
}
setTimeout(() => this._getNetworkStatus, 1000);
} else {
this._getNetworkStatus();
}
}
private _onHelpTap(): void {
this._showHelp = !this._showHelp;
}
private _generateServiceButton(service: string) {
return html`
<ha-call-service-button
.hass=${this.hass}
domain="zwave"
service=${service}
>
${this.hass!.localize("ui.panel.config.zwave.services." + service)}
</ha-call-service-button>
<ha-service-description
.hass=${this.hass}
domain="zwave"
service=${service}
?hidden=${!this._showHelp}
>
</ha-service-description>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.content {
margin-top: 24px;
}
.sectionHeader {
position: relative;
padding-right: 40px;
}
.network-status {
text-align: center;
}
.network-status div.details {
font-size: 1.5rem;
padding: 24px;
}
.network-status ha-svg-icon {
display: block;
margin: 0px auto 16px;
width: 48px;
height: 48px;
}
.network-status small {
font-size: 1rem;
}
ha-card {
margin: 0 auto;
max-width: 600px;
}
.card-actions.warning ha-call-service-button {
color: var(--error-color);
}
.toggle-help-icon {
position: absolute;
top: -6px;
right: 0;
color: var(--primary-color);
}
ha-service-description {
display: block;
color: grey;
padding: 0 8px 12px;
}
[hidden] {
display: none;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zwave-network": ZwaveNetwork;
}
}

View File

@ -1,388 +0,0 @@
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card";
import {
fetchNodeConfig,
ZWaveConfigItem,
ZWaveConfigServiceData,
ZWaveNode,
} from "../../../../../data/zwave";
import { haStyle } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types";
@customElement("zwave-node-config")
export class ZwaveNodeConfig extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public nodes: ZWaveNode[] = [];
@property() public config: ZWaveConfigItem[] = [];
@property() public selectedNode = -1;
@state() private _configItem?: ZWaveConfigItem;
@state() private _wakeupInput = -1;
@state() private _selectedConfigParameter = -1;
@state() private _selectedConfigValue: number | string = -1;
protected render(): TemplateResult {
return html`
<div class="content">
<ha-card
.header=${this.hass!.localize(
"ui.panel.config.zwave.node_config.header"
)}
>
${"wake_up_interval" in this.nodes[this.selectedNode].attributes
? html`
<div class="card-actions">
<paper-input
.floatLabel=${this.hass!.localize(
"ui.panel.config.zwave.common.wakeup_interval"
)}
type="number"
.value=${this._wakeupInput !== -1
? this._wakeupInput
: this.hass!.localize(
"ui.panel.config.zwave.common.unknown"
)}
@value-changed=${this._onWakeupIntervalChanged}
.placeholder=${this.nodes[this.selectedNode].attributes
.wake_up_interval
? this.nodes[this.selectedNode].attributes
.wake_up_interval
: this.hass!.localize(
"ui.panel.config.zwave.common.unknown"
)}
>
<div suffix>
${this.hass!.localize(
"ui.panel.config.zwave.node_config.seconds"
)}
</div>
</paper-input>
<ha-call-service-button
.hass=${this.hass}
domain="zwave"
service="set_wakeup"
.serviceData=${this._computeWakeupServiceData(
this._wakeupInput
)}
>
${this.hass!.localize(
"ui.panel.config.zwave.node_config.set_wakeup"
)}
</ha-call-service-button>
</div>
`
: ""}
<div class="device-picker">
<paper-dropdown-menu
.label=${this.hass!.localize(
"ui.panel.config.zwave.node_config.config_parameter"
)}
dynamic-align
class="flex"
>
<paper-listbox
slot="dropdown-content"
.selected=${this._selectedConfigParameter}
@iron-select=${this._selectedConfigParameterChanged}
>
${this.config.map(
(entityState) => html`
<paper-item>
${entityState.key}: ${entityState.value.label}
</paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu>
</div>
${this._configItem
? html`
${this._configItem.value.type === "List"
? html`
<div class="device-picker">
<paper-dropdown-menu
.label=${this.hass!.localize(
"ui.panel.config.zwave.node_config.config_value"
)}
dynamic-align
class="flex"
.placeholder=${this._configItem.value.data}
>
<paper-listbox
slot="dropdown-content"
.selected=${this._configItem.value.data}
@iron-select=${this._configValueSelectChanged}
>
${this._configItem.value.data_items.map(
(entityState) => html`
<paper-item>${entityState}</paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu>
</div>
`
: ""}
${["Byte", "Short", "Int"].includes(this._configItem.value.type)
? html`
<div class="card-actions">
<paper-input
.label=${this._configItem.value.data_items}
type="number"
.value=${this._configItem.value.data}
.max=${this._configItem.value.max}
.min=${this._configItem.value.min}
@value-changed=${this._configValueInputChanged}
>
</paper-input>
</div>
`
: ""}
${["Bool", "Button"].includes(this._configItem.value.type)
? html`
<div class="device-picker">
<paper-dropdown-menu
.label=${this.hass!.localize(
"ui.panel.config.zwave.node_config.config_value"
)}
class="flex"
dynamic-align
.placeholder=${this._configItem.value.data}
>
<paper-listbox
slot="dropdown-content"
.selected=${this._configItem.value.data}
@iron-select=${this._configValueSelectChanged}
>
<paper-item>
${this.hass!.localize(
"ui.panel.config.zwave.node_config.true"
)}
</paper-item>
<paper-item>
${this.hass!.localize(
"ui.panel.config.zwave.node_config.false"
)}
</paper-item>
</paper-listbox>
</paper-dropdown-menu>
</div>
`
: ""}
<div class="help-text">
<span>${this._configItem.value.help}</span>
</div>
${["Bool", "Button", "Byte", "Short", "Int", "List"].includes(
this._configItem.value.type
)
? html`
<div class="card-actions">
<ha-call-service-button
.hass=${this.hass}
domain="zwave"
service="set_config_parameter"
.serviceData=${this._computeSetConfigParameterServiceData()}
>
${this.hass!.localize(
"ui.panel.config.zwave.node_config.set_config_parameter"
)}
</ha-call-service-button>
</div>
`
: ""}
`
: ""}
</ha-card>
</div>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.content {
margin-top: 24px;
}
ha-card {
margin: 0 auto;
max-width: 600px;
}
.device-picker {
@apply --layout-horizontal;
@apply --layout-center-center;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-ms-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
padding-left: 24px;
padding-right: 24px;
padding-bottom: 24px;
}
.help-text {
padding-left: 24px;
padding-right: 24px;
}
.flex {
-ms-flex: 1 1 0.000000001px;
-webkit-flex: 1;
flex: 1;
-webkit-flex-basis: 0.000000001px;
flex-basis: 0.000000001px;
}
`,
];
}
protected firstUpdated(changedProps: PropertyValues): void {
super.firstUpdated(changedProps);
this.addEventListener("hass-service-called", (ev) =>
this.serviceCalled(ev)
);
}
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (changedProps.has("selectedNode")) {
this._nodesChanged();
}
}
private serviceCalled(ev): void {
if (ev.detail.success) {
setTimeout(() => {
this._refreshConfig(this.selectedNode);
}, 5000);
}
}
private _nodesChanged(): void {
if (!this.nodes) {
return;
}
this._configItem = undefined;
this._wakeupInput =
this.nodes[this.selectedNode].attributes.wake_up_interval || -1;
}
private _onWakeupIntervalChanged(value: ChangeEvent): void {
this._wakeupInput = value.detail!.value;
}
private _computeWakeupServiceData(wakeupInput: number) {
return {
node_id: this.nodes[this.selectedNode].attributes.node_id,
value: wakeupInput,
};
}
private _computeSetConfigParameterServiceData():
| ZWaveConfigServiceData
| boolean {
if (this.selectedNode === -1 || typeof this._configItem === "undefined") {
return false;
}
let valueData: number | string = "";
if (["Short", "Byte", "Int"].includes(this._configItem!.value.type)) {
valueData =
typeof this._selectedConfigValue === "string"
? parseInt(this._selectedConfigValue, 10)
: this._selectedConfigValue;
}
if (["Bool", "Button", "List"].includes(this._configItem!.value.type)) {
valueData = this._selectedConfigValue;
}
return {
node_id: this.nodes[this.selectedNode].attributes.node_id,
parameter: this._configItem.key,
value: valueData,
};
}
private _selectedConfigParameterChanged(event: ItemSelectedEvent): void {
if (event.target!.selected === -1) {
return;
}
this._selectedConfigParameter = event.target!.selected;
this._configItem = this.config[event.target!.selected];
}
private _configValueSelectChanged(event: ItemSelectedEvent): void {
if (event.target!.selected === -1) {
return;
}
this._selectedConfigValue = event.target!.selectedItem.textContent;
}
private _configValueInputChanged(value: ChangeEvent): void {
this._selectedConfigValue = value.detail!.value;
}
private async _refreshConfig(selectedNode): Promise<void> {
const configData: ZWaveConfigItem[] = [];
const config = await fetchNodeConfig(
this.hass,
this.nodes[selectedNode].attributes.node_id
);
Object.keys(config).forEach((key) => {
configData.push({
key: parseInt(key, 10),
value: config[key],
});
});
this.config = configData;
this._configItem = this.config[this._selectedConfigParameter];
}
}
export interface ChangeEvent {
detail?: {
value?: any;
};
target?: EventTarget;
}
export interface PickerTarget extends EventTarget {
selected: number;
selectedItem?: any;
}
export interface ItemSelectedEvent {
target?: PickerTarget;
}
declare global {
interface HTMLElementTagNameMap {
"zwave-node-config": ZwaveNodeConfig;
}
}

View File

@ -1,179 +0,0 @@
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../../../components/buttons/ha-call-api-button";
import "../../../../../components/ha-card";
import LocalizeMixin from "../../../../../mixins/localize-mixin";
import "../../../../../styles/polymer-ha-style";
class ZwaveNodeProtection extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex ha-style">
.card-actions.warning ha-call-api-button {
color: var(--error-color);
}
.content {
margin-top: 24px;
}
ha-card {
margin: 0 auto;
max-width: 600px;
}
.device-picker {
@apply --layout-horizontal;
@apply --layout-center-center;
padding: 0 24px 24px 24px;
}
</style>
<div class="content">
<ha-card header="[[localize('ui.panel.config.zwave.node_management.node_protection')]]">
<div class="device-picker">
<paper-dropdown-menu label="[[localize('ui.panel.config.zwave.node_management.protection')]]" dynamic-align class="flex" placeholder="{{_loadedProtectionValue}}">
<paper-listbox slot="dropdown-content" selected="{{_selectedProtectionParameter}}">
<template is="dom-repeat" items="[[_protectionOptions]]" as="state">
<paper-item>[[state]]</paper-item>
</template>
</paper-listbox>
</paper-dropdown-menu>
</div>
<div class="card-actions">
<ha-call-api-button
hass="[[hass]]"
path="[[_nodePath]]"
data="[[_protectionData]]">
[[localize('ui.panel.config.zwave.node_management.set_protection')]]
</ha-call-service-button>
</div>
</ha-card>
</div>
`;
}
static get properties() {
return {
hass: Object,
nodes: Array,
selectedNode: {
type: Number,
value: -1,
},
protectionNode: {
type: Boolean,
value: false,
},
_protectionValueID: {
type: Number,
value: -1,
},
_selectedProtectionParameter: {
type: Number,
value: -1,
observer: "_computeProtectionData",
},
_protectionOptions: Array,
_protection: {
type: Array,
value: () => [],
},
_loadedProtectionValue: {
type: String,
value: "",
},
_protectionData: {
type: Object,
value: {},
},
_nodePath: String,
};
}
static get observers() {
return ["_nodesChanged(nodes, selectedNode)"];
}
ready() {
super.ready();
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
}
apiCalled(ev) {
if (ev.detail.success) {
setTimeout(() => {
this._refreshProtection(this.selectedNode);
}, 5000);
}
}
_nodesChanged() {
if (!this.nodes) return;
if (this.protection) {
if (this.protection.length === 0) {
return;
}
let options = [];
let value_id = -1;
let selected = -1;
this.protection.forEach((item) => {
if (item.key === "options") options = item.value;
else if (item.key === "value_id") value_id = item.value;
else if (item.key === "selected") selected = item.value;
});
this.setProperties({
protectionNode: true,
_protectionOptions: options,
_loadedProtectionValue: selected,
_protectionValueID: value_id,
});
}
}
async _refreshProtection(selectedNode) {
const protectionValues = [];
const protections = await this.hass.callApi(
"GET",
`zwave/protection/${this.nodes[selectedNode].attributes.node_id}`
);
Object.keys(protections).forEach((key) => {
protectionValues.push({
key,
value: protections[key],
});
});
this.setProperties({
_protection: protectionValues,
_selectedProtectionParameter: -1,
_loadedProtectionValue: this.protection[1].value,
});
}
_computeProtectionData(selectedProtectionParameter) {
if (this.selectedNode === -1 || selectedProtectionParameter === -1) return;
this._protectionData = {
selection: this._protectionOptions[selectedProtectionParameter],
value_id: this._protectionValueID,
};
this._nodePath = `zwave/protection/${
this.nodes[this.selectedNode].attributes.node_id
}`;
}
}
customElements.define("zwave-node-protection", ZwaveNodeProtection);

View File

@ -1,226 +0,0 @@
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card";
import "../../../../../styles/polymer-ha-style";
class ZwaveUsercodes extends PolymerElement {
static get template() {
return html`
<style include="iron-flex ha-style">
.content {
margin-top: 24px;
}
ha-card {
margin: 0 auto;
max-width: 600px;
}
.device-picker {
@apply --layout-horizontal;
@apply --layout-center-center;
padding-left: 24px;
padding-right: 24px;
padding-bottom: 24px;
}
pre {
font-family: var(--code-font-family, monospace);
}
</style>
<div class="content">
<ha-card header="Node user codes">
<div class="device-picker">
<paper-dropdown-menu
label="Code slot"
dynamic-align=""
class="flex"
>
<paper-listbox
slot="dropdown-content"
selected="{{_selectedUserCode}}"
>
<template is="dom-repeat" items="[[userCodes]]" as="state">
<paper-item
>[[_computeSelectCaptionUserCodes(state)]]</paper-item
>
</template>
</paper-listbox>
</paper-dropdown-menu>
</div>
<template is="dom-if" if="[[_isUserCodeSelected(_selectedUserCode)]]">
<div class="card-actions">
<paper-input
label="User code"
type="text"
allowed-pattern="[0-9,a-f,x,\\\\]"
maxlength="40"
minlength="16"
value="{{_selectedUserCodeValue}}"
>
</paper-input>
<pre>Ascii: [[_computedCodeOutput]]</pre>
</div>
<div class="card-actions">
<ha-call-service-button
hass="[[hass]]"
domain="lock"
service="set_usercode"
service-data='[[_computeUserCodeServiceData(_selectedUserCodeValue, "Add")]]'
>
Set Usercode
</ha-call-service-button>
<ha-call-service-button
hass="[[hass]]"
domain="lock"
service="clear_usercode"
service-data='[[_computeUserCodeServiceData(_selectedUserCode, "Delete")]]'
>
Delete Usercode
</ha-call-service-button>
</div>
</template>
</ha-card>
</div>
`;
}
static get properties() {
return {
hass: Object,
nodes: Array,
selectedNode: {
type: Number,
observer: "_selectedNodeChanged",
},
userCodes: Object,
_selectedUserCode: {
type: Number,
value: -1,
observer: "_selectedUserCodeChanged",
},
_selectedUserCodeValue: String,
_computedCodeOutput: {
type: String,
value: "",
},
};
}
ready() {
super.ready();
this.addEventListener("hass-service-called", (ev) =>
this.serviceCalled(ev)
);
}
serviceCalled(ev) {
if (ev.detail.success) {
setTimeout(() => {
this._refreshUserCodes(this.selectedNode);
}, 5000);
}
}
_isUserCodeSelected(selectedUserCode) {
if (selectedUserCode === -1) return false;
return true;
}
_computeSelectCaptionUserCodes(stateObj) {
return `${stateObj.key}: ${stateObj.value.label}`;
}
_selectedUserCodeChanged(selectedUserCode) {
if (this._selectedUserCode === -1 || selectedUserCode === -1) return;
const value = this.userCodes[selectedUserCode].value.code;
this.setProperties({
_selectedUserCodeValue: this._a2hex(value),
_computedCodeOutput: `[${this._hex2a(this._a2hex(value))}]`,
});
}
_computeUserCodeServiceData(selectedUserCodeValue, type) {
if (this.selectedNode === -1 || !selectedUserCodeValue) return -1;
let serviceData = null;
let valueData = null;
if (type === "Add") {
valueData = this._hex2a(selectedUserCodeValue);
this._computedCodeOutput = `[${valueData}]`;
serviceData = {
node_id: this.nodes[this.selectedNode].attributes.node_id,
code_slot: this._selectedUserCode,
usercode: valueData,
};
}
if (type === "Delete") {
serviceData = {
node_id: this.nodes[this.selectedNode].attributes.node_id,
code_slot: this._selectedUserCode,
};
}
return serviceData;
}
async _refreshUserCodes(selectedNode) {
this.setProperties({ _selectedUserCodeValue: "" });
const userCodes = [];
const userCodeData = await this.hass.callApi(
"GET",
`zwave/usercodes/${this.nodes[selectedNode].attributes.node_id}`
);
Object.keys(userCodeData).forEach((key) => {
userCodes.push({
key,
value: userCodeData[key],
});
});
this.setProperties({ userCodes: userCodes });
this._selectedUserCodeChanged(this._selectedUserCode);
}
_a2hex(str) {
const arr = [];
let output = "";
for (let i = 0, l = str.length; i < l; i++) {
const hex = Number(str.charCodeAt(i)).toString(16);
if (hex === "0") {
output = "00";
} else {
output = hex;
}
arr.push("\\x" + output);
}
return arr.join("");
}
_hex2a(hexx) {
const hex = hexx.toString();
const hexMod = hex.replace(/\\x/g, "");
let str = "";
for (let i = 0; i < hexMod.length; i += 2) {
str += String.fromCharCode(parseInt(hexMod.substr(i, 2), 16));
}
return str;
}
_selectedNodeChanged() {
if (this.selectedNode === -1) return;
this.setProperties({ _selecteduserCode: -1 });
}
}
customElements.define("zwave-usercodes", ZwaveUsercodes);

View File

@ -1,109 +0,0 @@
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../../components/buttons/ha-call-service-button";
import "../../../../../components/ha-card";
import { ZWaveValue } from "../../../../../data/zwave";
import { haStyle } from "../../../../../resources/styles";
import { HomeAssistant } from "../../../../../types";
@customElement("zwave-values")
export class ZwaveValues extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public values: ZWaveValue[] = [];
@state() private _selectedValue = -1;
protected render(): TemplateResult {
return html`
<div class="content">
<ha-card
.header=${this.hass.localize("ui.panel.config.zwave.values.header")}
>
<div class="device-picker">
<paper-dropdown-menu
.label=${this.hass.localize("ui.panel.config.zwave.common.value")}
dynamic-align
class="flex"
>
<paper-listbox
slot="dropdown-content"
.selected=${this._selectedValue}
>
${this.values.map(
(item) => html`
<paper-item> ${this._computeCaption(item)} </paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu>
</div>
</ha-card>
</div>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.content {
margin-top: 24px;
}
ha-card {
margin: 0 auto;
max-width: 600px;
}
.device-picker {
@apply --layout-horizontal;
@apply --layout-center-center;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-ms-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
padding-left: 24px;
padding-right: 24px;
padding-bottom: 24px;
}
.flex {
-ms-flex: 1 1 0.000000001px;
-webkit-flex: 1;
flex: 1;
-webkit-flex-basis: 0.000000001px;
flex-basis: 0.000000001px;
}
.help-text {
padding-left: 24px;
padding-right: 24px;
}
`,
];
}
private _computeCaption(item) {
let out = `${item.value.label}`;
out += ` (${this.hass.localize("ui.panel.config.zwave.common.instance")}:`;
out += ` ${item.value.instance},`;
out += ` ${this.hass.localize("ui.panel.config.zwave.common.index")}:`;
out += ` ${item.value.index})`;
return out;
}
}
declare global {
interface HTMLElementTagNameMap {
"zwave-values": ZwaveValues;
}
}

View File

@ -2692,120 +2692,6 @@
"stop_listening": "Stop listening",
"message_received": "Message {id} received on {topic} at {time}:"
},
"ozw": {
"common": {
"zwave": "Z-Wave",
"node_id": "Node ID",
"ozw_instance": "OpenZWave Instance",
"instance": "Instance",
"controller": "Controller",
"network": "Network",
"wakeup_instructions": "Wake-up Instructions",
"query_stage": "Query Stage"
},
"device_info": {
"zwave_info": "Z-Wave Info",
"stage": "Stage",
"node_failed": "Node Failed"
},
"node_query_stages": {
"protocolinfo": "Obtaining basic Z-Wave capabilities of this node from the controller",
"probe": "Checking if the node is awake/alive",
"wakeup": "Setting up support for wake-up queues and messages",
"manufacturerspecific1": "Obtaining manufacturer and product ID codes from the node",
"nodeinfo": "Obtaining supported command classes from the node",
"nodeplusinfo": "Obtaining Z-Wave+ information from the node",
"manufacturerspecific2": "Obtaining additional manufacturer and product ID codes from the node",
"versions": "Obtaining information about firmware and command class versions",
"instances": "Obtaining details about what instances or channels a device supports",
"static": "Obtaining static values from the device",
"cacheload": "Loading information from the OpenZWave cache file. Battery nodes will stay at this stage until the node wakes up.",
"associations": "Refreshing association groups and memberships",
"neighbors": "Obtaining a list of the node's neighbors",
"session": "Obtaining infrequently changing values from the node",
"dynamic": "Obtaining frequently changing values from the node",
"configuration": "Obtaining configuration values from the node",
"complete": "Interview process is complete"
},
"refresh_node": {
"button": "Refresh Node",
"title": "Refresh Node Information",
"complete": "Node Refresh Complete",
"description": "This will tell OpenZWave to re-interview a node and update the node's command classes, capabilities, and values.",
"battery_note": "If the node is battery powered, be sure to wake it before proceeding",
"wakeup_header": "Wake-up Instructions for",
"wakeup_instructions_source": "Wake-up instructions are sourced from the OpenZWave community device database.",
"start_refresh_button": "Start Refresh",
"refreshing_description": "Refreshing node information…",
"node_status": "Node Status",
"step": "Step"
},
"network_status": {
"online": "Online",
"offline": "Offline",
"starting": "Starting",
"unknown": "Unknown",
"details": {
"driverallnodesqueried": "All nodes have been queried",
"driverallnodesqueriedsomedead": "All nodes have been queried. Some nodes were found dead",
"driverawakenodesqueries": "All awake nodes have been queried",
"driverremoved": "The driver has been removed",
"driverreset": "The driver has been reset",
"driverfailed": "Failed to connect to Z-Wave controller",
"driverready": "Initializing the Z-Wave controller",
"ready": "Ready to connect",
"stopped": "OpenZWave stopped",
"started": "Connected to MQTT",
"starting": "Connecting to MQTT",
"offline": "OZWDaemon offline"
}
},
"navigation": {
"select_instance": "Select Instance",
"network": "Network",
"nodes": "Nodes",
"node": {
"dashboard": "Dashboard",
"config": "Config"
}
},
"select_instance": {
"header": "Select an OpenZWave Instance",
"introduction": "You have more than one OpenZWave instance running. Which instance would you like to manage?",
"none_found": "We couldn't find an OpenZWave instance. If you believe this is incorrect, check your OpenZWave and MQTT setups and ensure that Home Assistant can communicate with your MQTT broker."
},
"network": {
"header": "Network Management",
"introduction": "Manage network-wide functions.",
"node_count": "{count} nodes"
},
"nodes_table": {
"id": "ID",
"manufacturer": "Manufacturer",
"model": "Model",
"query_stage": "Query Stage",
"zwave_plus": "Z-Wave Plus",
"failed": "Failed"
},
"node": {
"button": "Node Details",
"not_found": "Node not found"
},
"node_config": {
"header": "Node Configuration",
"introduction": "Manage the different configuration parameters for a Z-Wave node.",
"help_source": "Config parameter descriptions and help text are provided by the OpenZWave project.",
"wakeup_help": "Battery powered nodes must be awake to change their configuration. If the node is not awake, OpenZWave will attempt to update the node's configuration the next time it wakes up, which could be multiple hours (or days) later. Follow these steps to wake up your device:"
},
"node_metadata": {
"product_manual": "Product Manual"
},
"services": {
"add_node": "Add Node",
"remove_node": "Remove Node",
"cancel_command": "Cancel Command"
}
},
"zha": {
"common": {
"clusters": "Clusters",
@ -2899,96 +2785,6 @@
"unbind_button_help": "Unbind the selected group from the selected device clusters."
}
},
"zwave": {
"description": "Manage your Z-Wave network",
"learn_more": "Learn more about Z-Wave",
"common": {
"value": "Value",
"instance": "Instance",
"index": "Index",
"unknown": "unknown",
"wakeup_interval": "Wake-up Interval"
},
"migration": {
"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": {
"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."
},
"node_management": {
"header": "Z-Wave Node Management",
"introduction": "Run Z-Wave commands that affect a single node. Pick a node to see a list of available commands.",
"nodes": "Nodes",
"nodes_hint": "Select node to view per-node options",
"entities": "Entities of this node",
"entity_info": "Entity Information",
"exclude_entity": "Exclude this entity from Home Assistant",
"pooling_intensity": "Polling intensity",
"node_protection": "Node protection",
"protection": "Protection",
"set_protection": "Set Protection",
"node_group_associations": "Node group associations",
"group": "Group",
"node_to_control": "Node to control",
"nodes_in_group": "Other nodes in this group:",
"max_associations": "Max Associations:",
"add_to_group": "Add to Group",
"remove_from_group": "Remove from Group",
"remove_broadcast": "Remove Broadcast"
},
"ozw_log": {
"header": "OZW Log",
"introduction": "View the log. 0 is the minimum (loads entire log) and 1000 is the maximum. Load will show a static log and tail will auto update with the last specified number of lines of the log.",
"last_log_lines": "Number of last log lines",
"load": "Load",
"tail": "Tail"
},
"network_status": {
"network_stopped": "Z-Wave Network Stopped",
"network_starting": "Starting Z-Wave Network…",
"network_starting_note": "This may take a while depending on the size of your network.",
"network_started": "Z-Wave Network Started",
"network_started_note_some_queried": "Awake nodes have been queried. Sleeping nodes will be queried when they wake.",
"network_started_note_all_queried": "All nodes have been queried."
},
"node_config": {
"header": "Node Configuration Options",
"seconds": "seconds",
"set_wakeup": "Set Wake-up Interval",
"config_parameter": "Configuration Parameter",
"config_value": "Configuration Value",
"true": "True",
"false": "False",
"set_config_parameter": "Set Configuration Parameter"
},
"values": {
"header": "Node Values"
},
"services": {
"start_network": "Start Network",
"stop_network": "Stop Network",
"heal_network": "Heal Network",
"test_network": "Test Network",
"soft_reset": "Soft Reset",
"save_config": "Save Configuration",
"add_node_secure": "Add Node Secure",
"add_node": "Add Node",
"remove_node": "Remove Node",
"cancel_command": "Cancel Command",
"refresh_node": "Refresh Node",
"remove_failed_node": "Remove Failed Node",
"replace_failed_node": "Replace Failed Node",
"print_node": "Print Node",
"heal_node": "Heal Node",
"test_node": "Test Node",
"node_info": "Node Information",
"refresh_entity": "Refresh Entity"
}
},
"zwave_js": {
"navigation": {
"network": "Network",

View File

@ -3136,19 +3136,7 @@ __metadata:
languageName: node
linkType: hard
"@polymer/iron-dropdown@npm:^3.0.0-pre.26":
version: 3.0.1
resolution: "@polymer/iron-dropdown@npm:3.0.1"
dependencies:
"@polymer/iron-behaviors": ^3.0.0-pre.26
"@polymer/iron-overlay-behavior": ^3.0.0-pre.27
"@polymer/neon-animation": ^3.0.0-pre.26
"@polymer/polymer": ^3.0.0
checksum: 2c1ba429c8f5553f8493f256691efa8a338e8c038c1102f482ecb612b61c079b5019f6c362aefb31b44d3429661152c1b6912408a69c67e9d6fff62914ad801f
languageName: node
linkType: hard
"@polymer/iron-fit-behavior@npm:^3.0.0-pre.26, @polymer/iron-fit-behavior@npm:^3.1.0":
"@polymer/iron-fit-behavior@npm:^3.0.0-pre.26":
version: 3.1.0
resolution: "@polymer/iron-fit-behavior@npm:3.1.0"
dependencies:
@ -3295,17 +3283,6 @@ __metadata:
languageName: node
linkType: hard
"@polymer/neon-animation@npm:^3.0.0-pre.26":
version: 3.0.1
resolution: "@polymer/neon-animation@npm:3.0.1"
dependencies:
"@polymer/iron-resizable-behavior": ^3.0.0-pre.26
"@polymer/iron-selector": ^3.0.0-pre.26
"@polymer/polymer": ^3.0.0
checksum: c5ea5e1ef9f2017faaa5799ea108b26634dd7d986fe469369e629075efe382a5e5d4f9c537bacc77f9852453a2758c9f67e491d6ea5a1c4457f772bfdf06c707
languageName: node
linkType: hard
"@polymer/paper-behaviors@npm:^3.0.0-pre.27":
version: 3.0.1
resolution: "@polymer/paper-behaviors@npm:3.0.1"
@ -3318,25 +3295,6 @@ __metadata:
languageName: node
linkType: hard
"@polymer/paper-dropdown-menu@npm:^3.2.0":
version: 3.2.0
resolution: "@polymer/paper-dropdown-menu@npm:3.2.0"
dependencies:
"@polymer/iron-a11y-keys-behavior": ^3.0.0-pre.26
"@polymer/iron-form-element-behavior": ^3.0.0-pre.26
"@polymer/iron-icon": ^3.0.0-pre.26
"@polymer/iron-iconset-svg": ^3.0.0-pre.26
"@polymer/iron-validatable-behavior": ^3.0.0-pre.26
"@polymer/paper-behaviors": ^3.0.0-pre.27
"@polymer/paper-input": ^3.1.0
"@polymer/paper-menu-button": ^3.1.0
"@polymer/paper-ripple": ^3.0.0-pre.26
"@polymer/paper-styles": ^3.0.0-pre.26
"@polymer/polymer": ^3.3.1
checksum: dc7f6a8e3d449f37068ad5ee1d1c6d9037c9abd855ccc1d4e433d743c6378bb25af165ea86174edd1414887cf56a011ad0adda6d24dd55765e9458d49dc3e5e3
languageName: node
linkType: hard
"@polymer/paper-icon-button@npm:^3.0.0-pre.26":
version: 3.0.2
resolution: "@polymer/paper-icon-button@npm:3.0.2"
@ -3349,7 +3307,7 @@ __metadata:
languageName: node
linkType: hard
"@polymer/paper-input@npm:^3.0.0-pre.26, @polymer/paper-input@npm:^3.1.0, @polymer/paper-input@npm:^3.2.1":
"@polymer/paper-input@npm:^3.0.0-pre.26, @polymer/paper-input@npm:^3.2.1":
version: 3.2.1
resolution: "@polymer/paper-input@npm:3.2.1"
dependencies:
@ -3388,21 +3346,6 @@ __metadata:
languageName: node
linkType: hard
"@polymer/paper-menu-button@npm:^3.1.0":
version: 3.1.0
resolution: "@polymer/paper-menu-button@npm:3.1.0"
dependencies:
"@polymer/iron-a11y-keys-behavior": ^3.0.0-pre.26
"@polymer/iron-behaviors": ^3.0.0-pre.26
"@polymer/iron-dropdown": ^3.0.0-pre.26
"@polymer/iron-fit-behavior": ^3.1.0
"@polymer/neon-animation": ^3.0.0-pre.26
"@polymer/paper-styles": ^3.0.0-pre.26
"@polymer/polymer": ^3.0.0
checksum: 9243e104bac583189c6221f2df8dffeb331868cbf8084dd488cf2ddaba25987bfb3d4d2a9bd3168e6b49f28ba6b1b07ef7163fbfcf3af97978d34608e91cc605
languageName: node
linkType: hard
"@polymer/paper-progress@npm:^3.0.0-pre.26":
version: 3.0.1
resolution: "@polymer/paper-progress@npm:3.0.1"
@ -9113,7 +9056,6 @@ fsevents@^1.2.7:
"@polymer/iron-icon": ^3.0.1
"@polymer/iron-input": ^3.0.1
"@polymer/iron-resizable-behavior": ^3.0.1
"@polymer/paper-dropdown-menu": ^3.2.0
"@polymer/paper-input": ^3.2.1
"@polymer/paper-item": ^3.0.1
"@polymer/paper-listbox": ^3.0.1
@ -9240,7 +9182,6 @@ fsevents@^1.2.7:
vis-network: ^8.5.4
vue: ^2.6.12
vue2-daterange-picker: ^0.5.1
web-animations-js: ^2.3.2
webpack: ^5.55.1
webpack-cli: ^4.8.0
webpack-dev-server: ^4.3.0
@ -15703,13 +15644,6 @@ typescript@^4.4.3:
languageName: node
linkType: hard
"web-animations-js@npm:^2.3.2":
version: 2.3.2
resolution: "web-animations-js@npm:2.3.2"
checksum: 194db111bb2f92c15100c33b63af320ccdc26066748e358a945b947c510216c78e0a1e2ae22fefbaacb585c8a0b41b62a1417d8b549636ee32e16f059bb488f2
languageName: node
linkType: hard
"web-component-analyzer@npm:~1.1.1":
version: 1.1.6
resolution: "web-component-analyzer@npm:1.1.6"