Beginning pages for OZW Config Panel (#6670)

This commit is contained in:
Charles Garwood 2020-08-24 13:21:25 -04:00 committed by GitHub
parent 6599351d45
commit dc5607f554
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 671 additions and 1 deletions

View File

@ -39,6 +39,28 @@ export interface OZWDeviceMetaData {
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 const nodeQueryStages = [
"ProtocolInfo",
"Probe",
@ -59,6 +81,26 @@ export const nodeQueryStages = [
"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 {
@ -80,6 +122,31 @@ export const getIdentifiersFromDevice = function (
};
};
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: ozw_instance,
});
export const fetchOZWNetworkStatistics = (
hass: HomeAssistant,
ozw_instance: number
): Promise<OZWNetworkStatistics> =>
hass.callWS({
type: "ozw/network_statistics",
ozw_instance: ozw_instance,
});
export const fetchOZWNodeStatus = (
hass: HomeAssistant,
ozw_instance: number,

View File

@ -352,6 +352,13 @@ class HaPanelConfig extends HassRouterPage {
/* webpackChunkName: "panel-config-mqtt" */ "./integrations/integration-panels/mqtt/mqtt-config-panel"
),
},
ozw: {
tag: "ozw-config-router",
load: () =>
import(
/* webpackChunkName: "panel-config-ozw" */ "./integrations/integration-panels/ozw/ozw-config-router"
),
},
},
};

View File

@ -55,6 +55,10 @@ const integrationsWithPanel = {
buttonLocalizeKey: "ui.panel.config.zha.button",
path: "/config/zha/dashboard",
},
ozw: {
buttonLocalizeKey: "ui.panel.config.ozw.button",
path: "/config/ozw/dashboard",
},
zwave: {
buttonLocalizeKey: "ui.panel.config.zwave.button",
path: "/config/zwave",

View File

@ -0,0 +1,227 @@
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body";
import "@material/mwc-fab";
import {
css,
CSSResultArray,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { navigate } from "../../../../../common/navigate";
import "../../../../../components/ha-card";
import "../../../../../components/ha-icon-next";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section";
import { mdiCircle, mdiCheckCircle, mdiCloseCircle, mdiZWave } from "@mdi/js";
import "../../../../../layouts/hass-tabs-subpage";
import type { PageNavigation } from "../../../../../layouts/hass-tabs-subpage";
import "@material/mwc-button/mwc-button";
import {
OZWInstance,
fetchOZWInstances,
networkOnlineStatuses,
networkOfflineStatuses,
networkStartingStatuses,
} from "../../../../../data/ozw";
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;
@internalProperty() private _instances: OZWInstance[] = [];
public connectedCallback(): void {
super.connectedCallback();
if (this.hass) {
this._fetchData();
}
}
private async _fetchData() {
this._instances = await fetchOZWInstances(this.hass!);
if (this._instances.length === 1) {
navigate(
this,
`/config/ozw/network/${this._instances[0].ozw_instance}`,
true
);
}
}
protected render(): TemplateResult {
return html`
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
.tabs=${ozwTabs}
back-path="/config/integrations"
>
<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}"
aria-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>
`;
}
static get styles(): CSSResultArray {
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-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

@ -0,0 +1,253 @@
import "@material/mwc-fab";
import {
css,
CSSResultArray,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { navigate } from "../../../../../common/navigate";
import "../../../../../components/ha-card";
import "../../../../../components/ha-icon-next";
import "../../../../../components/buttons/ha-call-service-button";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import "../../../ha-config-section";
import { mdiCircle, mdiCheckCircle, mdiCloseCircle } from "@mdi/js";
import "../../../../../layouts/hass-tabs-subpage";
import type { PageNavigation } from "../../../../../layouts/hass-tabs-subpage";
import "@material/mwc-button/mwc-button";
import {
OZWInstance,
fetchOZWNetworkStatus,
fetchOZWNetworkStatistics,
networkOnlineStatuses,
networkOfflineStatuses,
networkStartingStatuses,
OZWNetworkStatistics,
} from "../../../../../data/ozw";
export const ozwTabs: PageNavigation[] = [];
@customElement("ozw-config-network")
class OZWConfigNetwork 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 ozw_instance = 0;
@internalProperty() private _network?: OZWInstance;
@internalProperty() private _statistics?: OZWNetworkStatistics;
@internalProperty() private _status = "unknown";
@internalProperty() private _icon = mdiCircle;
public connectedCallback(): void {
super.connectedCallback();
if (this.ozw_instance <= 0) {
navigate(this, "/config/ozw/dashboard", true);
}
if (this.hass) {
this._fetchData();
}
}
private async _fetchData() {
this._network = await fetchOZWNetworkStatus(this.hass!, this.ozw_instance);
this._statistics = await fetchOZWNetworkStatistics(
this.hass!,
this.ozw_instance
);
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) {
return html`
<ha-call-service-button
.hass=${this.hass}
domain="ozw"
service="${service}"
>
${this.hass!.localize("ui.panel.config.ozw.services." + service)}
</ha-call-service-button>
`;
}
protected render(): TemplateResult {
return html`
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
.tabs=${ozwTabs}
>
<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 ${this._status}"
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")}
</div>
</ha-card>
`
: ``}
</ha-config-section>
</hass-tabs-subpage>
`;
}
static get styles(): CSSResultArray {
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-config-network": OZWConfigNetwork;
}
}

View File

@ -0,0 +1,70 @@
import { customElement, property } from "lit-element";
import {
HassRouterPage,
RouterOptions,
} from "../../../../../layouts/hass-router-page";
import { HomeAssistant } from "../../../../../types";
import { navigate } from "../../../../../common/navigate";
@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(
/* webpackChunkName: "ozw-config-dashboard" */ "./ozw-config-dashboard"
),
},
network: {
tag: "ozw-config-network",
load: () =>
import(
/* webpackChunkName: "ozw-config-network" */ "./ozw-config-network"
),
},
},
};
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") {
el.ozw_instance = this.routeTail.path.substr(1);
}
const searchParams = new URLSearchParams(window.location.search);
if (this._configEntry && !searchParams.has("config_entry")) {
searchParams.append("config_entry", this._configEntry);
navigate(
this,
`${this.routeTail.prefix}${
this.routeTail.path
}?${searchParams.toString()}`,
true
);
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ozw-config-router": OZWConfigRouter;
}
}

View File

@ -1684,10 +1684,14 @@
"message_received": "Message {id} received on {topic} at {time}:"
},
"ozw": {
"button": "Configure",
"common": {
"zwave": "Z-Wave",
"node_id": "Node ID",
"ozw_instance": "OpenZWave Instance"
"ozw_instance": "OpenZWave Instance",
"instance": "Instance",
"controller": "Controller",
"network": "Network"
},
"device_info": {
"zwave_info": "Z-Wave Info",
@ -1724,6 +1728,44 @@
"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"
},
"select_instance": {
"header": "Select an OpenZWave Instance",
"introduction": "You have more than one OpenZWave instance running. Which instance would you like to manage?"
},
"network": {
"header": "Network Management",
"introduction": "Manage network-wide functions.",
"node_count": "{count} nodes"
},
"services": {
"add_node": "Add Node",
"remove_node": "Remove Node"
}
},
"zha": {