diff --git a/src/data/ozw.ts b/src/data/ozw.ts index 80ad9882b7..0e5d73565a 100644 --- a/src/data/ozw.ts +++ b/src/data/ozw.ts @@ -63,6 +63,16 @@ export interface OZWNetworkStatistics { 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", @@ -180,6 +190,17 @@ export const fetchOZWNodeMetadata = ( node_id: node_id, }); +export const fetchOZWNodeConfig = ( + hass: HomeAssistant, + ozw_instance: number, + node_id: number +): Promise => + hass.callWS({ + type: "ozw/get_config_parameters", + ozw_instance: ozw_instance, + node_id: node_id, + }); + export const refreshNodeInfo = ( hass: HomeAssistant, ozw_instance: number, diff --git a/src/panels/config/integrations/integration-panels/ozw/ozw-node-config.ts b/src/panels/config/integrations/integration-panels/ozw/ozw-node-config.ts new file mode 100644 index 0000000000..b1017aaaa8 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/ozw/ozw-node-config.ts @@ -0,0 +1,274 @@ +import "@material/mwc-button/mwc-button"; +import "@material/mwc-fab"; +import { + css, + CSSResultArray, + customElement, + html, + LitElement, + internalProperty, + property, + TemplateResult, +} from "lit-element"; +import { navigate } from "../../../../../common/navigate"; +import "../../../../../components/buttons/ha-call-service-button"; +import "../../../../../components/ha-card"; +import "../../../../../components/ha-icon-next"; +import "../../../../../layouts/hass-tabs-subpage"; +import { haStyle } from "../../../../../resources/styles"; +import type { HomeAssistant, Route } from "../../../../../types"; +import "../../../ha-config-section"; +import { + fetchOZWNodeStatus, + fetchOZWNodeMetadata, + fetchOZWNodeConfig, + OZWDevice, + OZWDeviceMetaDataResponse, + OZWDeviceConfig, +} from "../../../../../data/ozw"; +import { ERR_NOT_FOUND } from "../../../../../data/websocket_api"; +import { showOZWRefreshNodeDialog } from "./show-dialog-ozw-refresh-node"; +import { ozwNodeTabs } from "./ozw-node-router"; + +@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?; + + @internalProperty() private _node?: OZWDevice; + + @internalProperty() private _metadata?: OZWDeviceMetaDataResponse; + + @internalProperty() private _config?: OZWDeviceConfig[]; + + @internalProperty() private _error?: string; + + protected firstUpdated() { + if (!this.ozwInstance) { + navigate(this, "/config/ozw/dashboard", true); + } else if (!this.nodeId) { + navigate(this, `/config/ozw/network/${this.ozwInstance}/nodes`, true); + } else { + this._fetchData(); + } + } + + protected render(): TemplateResult { + if (this._error) { + return html` + + `; + } + + return html` + + +
+ ${this.hass.localize("ui.panel.config.ozw.node_config.header")} +
+ +
+ ${this.hass.localize( + "ui.panel.config.ozw.node_config.introduction" + )} +

+ + ${this.hass.localize( + "ui.panel.config.ozw.node_config.help_source" + )} + +

+

+ Note: This panel is currently read-only. The ability to change + values will come in a later update. +

+
+ ${this._node + ? html` + +
+ + ${this._node.node_manufacturer_name} + ${this._node.node_product_name}
+ ${this.hass.localize("ui.panel.config.ozw.common.node_id")}: + ${this._node.node_id}
+ ${this.hass.localize( + "ui.panel.config.ozw.common.query_stage" + )}: + ${this._node.node_query_stage} + ${this._metadata?.metadata.ProductManualURL + ? html` +

+ ${this.hass.localize( + "ui.panel.config.ozw.node_metadata.product_manual" + )} +

+
` + : ``} +
+
+ + ${this.hass.localize( + "ui.panel.config.ozw.refresh_node.button" + )} + +
+
+ + ${this._metadata?.metadata.WakeupHelp + ? html` + +
+ + ${this.hass.localize( + "ui.panel.config.ozw.node_config.wakeup_help" + )} + +

+ ${this._metadata.metadata.WakeupHelp} +

+
+
+ ` + : ``} + ${this._config + ? html` + ${this._config.map( + (item) => html` + +
+ ${item.label}
+ ${item.help} +

${item.value}

+
+
+ ` + )} + ` + : ``} + ` + : ``} +
+
+ `; + } + + 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) { + 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(): CSSResultArray { + 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; + } +} diff --git a/src/panels/config/integrations/integration-panels/ozw/ozw-node-dashboard.ts b/src/panels/config/integrations/integration-panels/ozw/ozw-node-dashboard.ts index 0b7283e71a..c860027551 100644 --- a/src/panels/config/integrations/integration-panels/ozw/ozw-node-dashboard.ts +++ b/src/panels/config/integrations/integration-panels/ozw/ozw-node-dashboard.ts @@ -26,7 +26,7 @@ import { } from "../../../../../data/ozw"; import { ERR_NOT_FOUND } from "../../../../../data/websocket_api"; import { showOZWRefreshNodeDialog } from "./show-dialog-ozw-refresh-node"; -import { ozwNetworkTabs } from "./ozw-network-router"; +import { ozwNodeTabs } from "./ozw-node-router"; @customElement("ozw-node-dashboard") class OZWNodeDashboard extends LitElement { @@ -74,7 +74,7 @@ class OZWNodeDashboard extends LitElement { .hass=${this.hass} .narrow=${this.narrow} .route=${this.route} - .tabs=${ozwNetworkTabs(this.ozwInstance)} + .tabs=${ozwNodeTabs(this.ozwInstance, this.nodeId)} >
diff --git a/src/panels/config/integrations/integration-panels/ozw/ozw-node-router.ts b/src/panels/config/integrations/integration-panels/ozw/ozw-node-router.ts index baf68ddc96..ea30bf78ff 100644 --- a/src/panels/config/integrations/integration-panels/ozw/ozw-node-router.ts +++ b/src/panels/config/integrations/integration-panels/ozw/ozw-node-router.ts @@ -1,11 +1,31 @@ +import { mdiNetwork, mdiWrench } from "@mdi/js"; import { customElement, property } from "lit-element"; 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[] => { + return [ + { + 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; @@ -33,6 +53,11 @@ class OZWNodeRouter extends HassRouterPage { /* webpackChunkName: "ozw-node-dashboard" */ "./ozw-node-dashboard" ), }, + config: { + tag: "ozw-node-config", + load: () => + import(/* webpackChunkName: "ozw-node-config" */ "./ozw-node-config"), + }, }, }; diff --git a/src/translations/en.json b/src/translations/en.json index fc62990841..a206807ee7 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1899,7 +1899,9 @@ "ozw_instance": "OpenZWave Instance", "instance": "Instance", "controller": "Controller", - "network": "Network" + "network": "Network", + "wakeup_instructions": "Wake-up Instructions", + "query_stage": "Query Stage" }, "device_info": { "zwave_info": "Z-Wave Info", @@ -1961,7 +1963,11 @@ "navigation": { "select_instance": "Select Instance", "network": "Network", - "nodes": "Nodes" + "nodes": "Nodes", + "node": { + "dashboard": "Dashboard", + "config": "Config" + } }, "select_instance": { "header": "Select an OpenZWave Instance", @@ -1985,6 +1991,15 @@ "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"