diff --git a/src/data/zwave_js.ts b/src/data/zwave_js.ts index a3b98a1ded..91284dba81 100644 --- a/src/data/zwave_js.ts +++ b/src/data/zwave_js.ts @@ -1,5 +1,6 @@ -import type { UnsubscribeFunc } from "home-assistant-js-websocket"; +import type { Connection, UnsubscribeFunc } from "home-assistant-js-websocket"; import type { HomeAssistant } from "../types"; +import { callWS } from "../util/ws-message"; export enum InclusionState { /** The controller isn't doing anything regarding inclusion. */ @@ -325,7 +326,10 @@ export interface ZWaveJSRefreshNodeStatusMessage { export interface ZWaveJSRebuildRoutesStatusMessage { event: string; - rebuild_routes_status: Record; + rebuild_routes_status: Record< + number, + "pending" | "skipped" | "done" | "failed" + >; } export interface ZWaveJSControllerStatisticsUpdatedMessage { @@ -475,7 +479,7 @@ export const invokeZWaveCCApi = ( }); export const fetchZwaveNetworkStatus = ( - hass: HomeAssistant, + connection: Connection, device_or_entry_id: { device_id?: string; entry_id?: string; @@ -487,7 +491,7 @@ export const fetchZwaveNetworkStatus = ( if (!device_or_entry_id.device_id && !device_or_entry_id.entry_id) { throw new Error("Either device or entry ID should be supplied."); } - return hass.callWS({ + return callWS(connection, { type: "zwave_js/network_status", device_id: device_or_entry_id.device_id, entry_id: device_or_entry_id.entry_id, @@ -814,35 +818,32 @@ export const removeFailedZwaveNode = ( ); export const rebuildZwaveNetworkRoutes = ( - hass: HomeAssistant, + connection: Connection, entry_id: string ): Promise => - hass.callWS({ + callWS(connection, { type: "zwave_js/begin_rebuilding_routes", entry_id, }); export const stopRebuildingZwaveNetworkRoutes = ( - hass: HomeAssistant, + connection: Connection, entry_id: string ): Promise => - hass.callWS({ + callWS(connection, { type: "zwave_js/stop_rebuilding_routes", entry_id, }); export const subscribeRebuildZwaveNetworkRoutesProgress = ( - hass: HomeAssistant, + connection: Connection, entry_id: string, callbackFunction: (message: ZWaveJSRebuildRoutesStatusMessage) => void ): Promise => - hass.connection.subscribeMessage( - (message: any) => callbackFunction(message), - { - type: "zwave_js/subscribe_rebuild_routes_progress", - entry_id, - } - ); + connection.subscribeMessage((message: any) => callbackFunction(message), { + type: "zwave_js/subscribe_rebuild_routes_progress", + entry_id, + }); export const subscribeZwaveControllerStatistics = ( hass: HomeAssistant, diff --git a/src/panels/config/devices/device-detail/integration-elements/zwave_js/device-actions.ts b/src/panels/config/devices/device-detail/integration-elements/zwave_js/device-actions.ts index a7423723c5..f28074fcce 100644 --- a/src/panels/config/devices/device-detail/integration-elements/zwave_js/device-actions.ts +++ b/src/panels/config/devices/device-detail/integration-elements/zwave_js/device-actions.ts @@ -189,7 +189,7 @@ export const getZwaveDeviceActions = async ( } if (nodeStatus.is_controller_node) { - const networkStatus = await fetchZwaveNetworkStatus(hass, { + const networkStatus = await fetchZwaveNetworkStatus(hass.connection, { entry_id: entryId, }); actions.unshift({ diff --git a/src/panels/config/integrations/integration-panels/zwave_js/add-node/dialog-zwave_js-add-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/add-node/dialog-zwave_js-add-node.ts index a64cbff91d..452e2cbbc7 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/add-node/dialog-zwave_js-add-node.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/add-node/dialog-zwave_js-add-node.ts @@ -508,9 +508,12 @@ class DialogZWaveJSAddNode extends LitElement { if (this._controllerSupportsLongRange === undefined) { try { - const zwaveNetwork = await fetchZwaveNetworkStatus(this.hass, { - entry_id: this._entryId, - }); + const zwaveNetwork = await fetchZwaveNetworkStatus( + this.hass.connection, + { + entry_id: this._entryId, + } + ); this._controllerSupportsLongRange = zwaveNetwork?.controller?.supports_long_range; } catch (err) { diff --git a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-rebuild-network-routes/dialog-zwave_js-rebuild-network-routes-detail.ts b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-rebuild-network-routes/dialog-zwave_js-rebuild-network-routes-detail.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-rebuild-network-routes.ts b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-rebuild-network-routes/dialog-zwave_js-rebuild-network-routes.ts similarity index 64% rename from src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-rebuild-network-routes.ts rename to src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-rebuild-network-routes/dialog-zwave_js-rebuild-network-routes.ts index db195049e9..df9956d1e2 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-rebuild-network-routes.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-rebuild-network-routes/dialog-zwave_js-rebuild-network-routes.ts @@ -1,79 +1,72 @@ +import { consume, type ContextType } from "@lit/context"; import "@material/mwc-linear-progress/mwc-linear-progress"; import { mdiCheckCircle, mdiCloseCircle, mdiStethoscope } from "@mdi/js"; import type { UnsubscribeFunc } from "home-assistant-js-websocket"; import type { CSSResultGroup } from "lit"; import { css, html, LitElement, nothing } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import { fireEvent } from "../../../../../common/dom/fire_event"; -import "../../../../../components/ha-button"; -import "../../../../../components/ha-dialog-footer"; -import "../../../../../components/ha-dialog"; +import { customElement, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import "../../../../../../components/ha-button"; +import "../../../../../../components/ha-dialog"; +import "../../../../../../components/ha-dialog-footer"; +import { + connectionContext, + localizeContext, +} from "../../../../../../data/context"; import type { ZWaveJSNetwork, ZWaveJSRebuildRoutesStatusMessage, -} from "../../../../../data/zwave_js"; +} from "../../../../../../data/zwave_js"; import { fetchZwaveNetworkStatus, rebuildZwaveNetworkRoutes, stopRebuildingZwaveNetworkRoutes, subscribeRebuildZwaveNetworkRoutesProgress, -} from "../../../../../data/zwave_js"; -import { haStyleDialog } from "../../../../../resources/styles"; -import type { HomeAssistant } from "../../../../../types"; +} from "../../../../../../data/zwave_js"; +import { DialogMixin } from "../../../../../../dialogs/dialog-mixin"; import type { ZWaveJSRebuildNetworkRoutesDialogParams } from "./show-dialog-zwave_js-rebuild-network-routes"; @customElement("dialog-zwave_js-rebuild-network-routes") -class DialogZWaveJSRebuildNetworkRoutes extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @state() private entry_id?: string; - +class DialogZWaveJSRebuildNetworkRoutes extends DialogMixin( + LitElement +) { @state() private _status?: string; - @state() private _progress_total = 0; - - @state() private _progress_finished = 0; - - @state() private _progress_in_progress = 0; + @state() private _progress?: { + pending: number[]; + skipped: number[]; + done: number[]; + failed: number[]; + }; private _subscribed?: Promise; - @state() private _open = false; + @state() + @consume({ context: localizeContext, subscribe: true }) + private _localize!: ContextType; - public showDialog(params: ZWaveJSRebuildNetworkRoutesDialogParams): void { - this._progress_total = 0; - this.entry_id = params.entry_id; - this._open = true; - this._fetchData(); - } + @state() + @consume({ context: connectionContext, subscribe: true }) + private _connection!: ContextType; - public closeDialog(): void { - this._open = false; - } - - private _dialogClosed(): void { - this.entry_id = undefined; - this._status = undefined; - this._progress_total = 0; - - this._unsubscribe(); - - fireEvent(this, "dialog-closed", { dialog: this.localName }); + connectedCallback() { + super.connectedCallback(); + if (this.params?.entry_id) { + this._fetchData(); + } } protected render() { - if (!this.entry_id) { + if (!this.params || !this.params.entry_id) { return nothing; } return html` ${!this._status ? html` @@ -84,7 +77,7 @@ class DialogZWaveJSRebuildNetworkRoutes extends LitElement { >

- ${this.hass.localize( + ${this._localize( "ui.panel.config.zwave_js.rebuild_network_routes.introduction" )}

@@ -92,36 +85,36 @@ class DialogZWaveJSRebuildNetworkRoutes extends LitElement {

- ${this.hass.localize( + ${this._localize( "ui.panel.config.zwave_js.rebuild_network_routes.traffic_warning" )}

` - : ``} + : nothing} ${this._status === "started" ? html`

- ${this.hass.localize( + ${this._localize( "ui.panel.config.zwave_js.rebuild_network_routes.in_progress" )}

- ${this.hass.localize( + ${this._localize( "ui.panel.config.zwave_js.rebuild_network_routes.run_in_background" )}

- ${!this._progress_total + ${!this._progress ? html` ` - : ""} + : nothing} ` - : ``} + : nothing} ${this._status === "failed" ? html`
@@ -131,14 +124,14 @@ class DialogZWaveJSRebuildNetworkRoutes extends LitElement { >

- ${this.hass.localize( + ${this._localize( "ui.panel.config.zwave_js.rebuild_network_routes.rebuilding_routes_failed" )}

` - : ``} + : nothing} ${this._status === "finished" ? html`
@@ -148,14 +141,14 @@ class DialogZWaveJSRebuildNetworkRoutes extends LitElement { >

- ${this.hass.localize( + ${this._localize( "ui.panel.config.zwave_js.rebuild_network_routes.rebuilding_routes_complete" )}

` - : ``} + : nothing} ${this._status === "cancelled" ? html`
@@ -165,24 +158,31 @@ class DialogZWaveJSRebuildNetworkRoutes extends LitElement { >

- ${this.hass.localize( + ${this._localize( "ui.panel.config.zwave_js.rebuild_network_routes.rebuilding_routes_cancelled" )}

` - : ``} - ${this._progress_total && this._status !== "finished" + : nothing} + ${this._progress && this._status !== "finished" ? html` + +
    +
  • Done: ${this._progress.done.length}
  • +
  • Skipped: ${this._progress.skipped.length}
  • +
  • Pending: ${this._progress.pending.length}
  • +
  • Failed: ${this._progress.failed.length}
  • +
` - : ""} + : nothing} ${!this._status ? html` @@ -190,7 +190,7 @@ class DialogZWaveJSRebuildNetworkRoutes extends LitElement { slot="primaryAction" @click=${this._startRebuildingRoutes} > - ${this.hass.localize( + ${this._localize( "ui.panel.config.zwave_js.rebuild_network_routes.start_rebuilding_routes" )} @@ -203,17 +203,17 @@ class DialogZWaveJSRebuildNetworkRoutes extends LitElement { @click=${this._stopRebuildingRoutes} variant="danger" > - ${this.hass.localize( + ${this._localize( "ui.panel.config.zwave_js.rebuild_network_routes.stop_rebuilding_routes" )} - ${this.hass.localize("ui.common.close")} + ${this._localize("ui.common.close")} ` : html` - ${this.hass.localize("ui.common.close")} + ${this._localize("ui.common.close")} `} @@ -222,65 +222,70 @@ class DialogZWaveJSRebuildNetworkRoutes extends LitElement { } private async _fetchData(): Promise { - if (!this.hass) { - return; - } - const network: ZWaveJSNetwork = await fetchZwaveNetworkStatus(this.hass!, { - entry_id: this.entry_id!, - }); + const network: ZWaveJSNetwork = await fetchZwaveNetworkStatus( + this._connection, + { + entry_id: this.params!.entry_id, + } + ); if (network.controller.is_rebuilding_routes) { this._status = "started"; - this._subscribed = subscribeRebuildZwaveNetworkRoutesProgress( - this.hass, - this.entry_id!, - this._handleMessage.bind(this) - ); + this._startSubscribingToProgress(); } } private _startRebuildingRoutes(): void { - if (!this.hass) { - return; - } - rebuildZwaveNetworkRoutes(this.hass, this.entry_id!); + rebuildZwaveNetworkRoutes(this._connection, this.params!.entry_id); this._status = "started"; + this._startSubscribingToProgress(); + } + + private _startSubscribingToProgress() { + // TODO initial message isn't handled this._subscribed = subscribeRebuildZwaveNetworkRoutesProgress( - this.hass, - this.entry_id!, - this._handleMessage.bind(this) + this._connection, + this.params!.entry_id, + this._handleMessage ); } private _stopRebuildingRoutes(): void { - if (!this.hass) { - return; - } - stopRebuildingZwaveNetworkRoutes(this.hass, this.entry_id!); + stopRebuildingZwaveNetworkRoutes(this._connection, this.params!.entry_id); this._unsubscribe(); this._status = "cancelled"; } - private _handleMessage(message: ZWaveJSRebuildRoutesStatusMessage): void { + private _handleMessage = (message: ZWaveJSRebuildRoutesStatusMessage) => { if (message.event === "rebuild routes progress") { - let finished = 0; - let in_progress = 0; - for (const status of Object.values(message.rebuild_routes_status)) { - if (status === "pending") { - in_progress++; - } - if (["skipped", "failed", "done"].includes(status)) { - finished++; - } + const progressNumbers: typeof this._progress = { + pending: [], + skipped: [], + done: [], + failed: [], + }; + for (const [nodeId, status] of Object.entries( + message.rebuild_routes_status + )) { + progressNumbers[status].push(Number(nodeId)); } - this._progress_total = Object.keys(message.rebuild_routes_status).length; - this._progress_finished = finished / this._progress_total; - this._progress_in_progress = in_progress / this._progress_total; + this._progress = progressNumbers; } if (message.event === "rebuild routes done") { this._unsubscribe(); this._status = "finished"; } - } + }; + + private _progressPercent = memoizeOne( + (progress: typeof this._progress) => + (progress!.done.length + + progress!.skipped.length + + progress!.failed.length) / + (progress!.done.length + + progress!.skipped.length + + progress!.failed.length + + progress!.pending.length) + ); private _unsubscribe(): void { if (this._subscribed) { @@ -289,9 +294,13 @@ class DialogZWaveJSRebuildNetworkRoutes extends LitElement { } } + disconnectedCallback(): void { + super.disconnectedCallback(); + this._unsubscribe(); + } + static get styles(): CSSResultGroup { return [ - haStyleDialog, css` .success { color: var(--success-color); diff --git a/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-rebuild-network-routes.ts b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-rebuild-network-routes/show-dialog-zwave_js-rebuild-network-routes.ts similarity index 89% rename from src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-rebuild-network-routes.ts rename to src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-rebuild-network-routes/show-dialog-zwave_js-rebuild-network-routes.ts index 56d7791b7f..141b3b3c72 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-rebuild-network-routes.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-rebuild-network-routes/show-dialog-zwave_js-rebuild-network-routes.ts @@ -1,4 +1,4 @@ -import { fireEvent } from "../../../../../common/dom/fire_event"; +import { fireEvent } from "../../../../../../common/dom/fire_event"; export interface ZWaveJSRebuildNetworkRoutesDialogParams { entry_id: string; diff --git a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-rebuild-node-routes.ts b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-rebuild-node-routes.ts index 97f64d3963..889bd040d3 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-rebuild-node-routes.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-rebuild-node-routes.ts @@ -209,9 +209,12 @@ class DialogZWaveJSRebuildNodeRoutes extends LitElement { if (!this.hass) { return; } - const network: ZWaveJSNetwork = await fetchZwaveNetworkStatus(this.hass!, { - device_id: this.device!.id, - }); + const network: ZWaveJSNetwork = await fetchZwaveNetworkStatus( + this.hass!.connection, + { + device_id: this.device!.id, + } + ); if (network.controller.is_rebuilding_routes) { this._status = "rebuilding-routes"; } diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts index 7f7a4f5d8d..c438f28648 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts @@ -636,7 +636,9 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) { } const [network, provisioningEntries] = await Promise.all([ - fetchZwaveNetworkStatus(this.hass!, { entry_id: this.configEntryId }), + fetchZwaveNetworkStatus(this.hass!.connection, { + entry_id: this.configEntryId, + }), fetchZwaveProvisioningEntries(this.hass!, this.configEntryId), ]); diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-network-info-page.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-network-info-page.ts index 5b3c637356..96bb0b9763 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-network-info-page.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-network-info-page.ts @@ -27,7 +27,7 @@ class ZWaveJSNetworkInfoPage extends LitElement { protected async firstUpdated() { if (this.hass && this.configEntryId) { - this._network = await fetchZwaveNetworkStatus(this.hass, { + this._network = await fetchZwaveNetworkStatus(this.hass.connection, { entry_id: this.configEntryId, }); } diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-network-visualization.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-network-visualization.ts index 2930760357..848798cf63 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-network-visualization.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-network-visualization.ts @@ -114,7 +114,7 @@ export class ZWaveJSNetworkVisualization extends SubscribeMixin(LitElement) { } private async _fetchNetworkStatus() { - const network = await fetchZwaveNetworkStatus(this.hass!, { + const network = await fetchZwaveNetworkStatus(this.hass!.connection, { entry_id: this.configEntryId, }); const nodeStatuses: Record = {}; diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-options-page.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-options-page.ts index b8178f178c..af5c25a952 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-options-page.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-options-page.ts @@ -16,7 +16,7 @@ import { import "../../../../../layouts/hass-subpage"; import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant, Route } from "../../../../../types"; -import { showZWaveJSRebuildNetworkRoutesDialog } from "./show-dialog-zwave_js-rebuild-network-routes"; +import { showZWaveJSRebuildNetworkRoutesDialog } from "./dialog-zwave_js-rebuild-network-routes/show-dialog-zwave_js-rebuild-network-routes"; import { showZWaveJSRemoveNodeDialog } from "./show-dialog-zwave_js-remove-node"; @customElement("zwave_js-options-page") @@ -37,7 +37,7 @@ class ZWaveJSOptionsPage extends LitElement { protected async firstUpdated() { if (this.hass && this.configEntryId) { - const network = await fetchZwaveNetworkStatus(this.hass, { + const network = await fetchZwaveNetworkStatus(this.hass.connection, { entry_id: this.configEntryId, }); this._network = network; diff --git a/src/state/connection-mixin.ts b/src/state/connection-mixin.ts index 9b41ffba29..ffd5e7837f 100644 --- a/src/state/connection-mixin.ts +++ b/src/state/connection-mixin.ts @@ -30,16 +30,17 @@ import { subscribeEntityRegistryDisplay } from "../data/ws-entity_registry_displ import { subscribeFloorRegistry } from "../data/ws-floor_registry"; import { subscribePanels } from "../data/ws-panels"; import { translationMetadata } from "../resources/translations-metadata"; +import type { Constructor, HomeAssistant, ServiceCallResponse } from "../types"; import { addBrandsAuth, clearBrandsTokenRefresh, fetchAndScheduleBrandsAccessToken, } from "../util/brands-url"; -import type { Constructor, HomeAssistant, ServiceCallResponse } from "../types"; import { getLocalLanguage } from "../util/common-translation"; import { fetchWithAuth } from "../util/fetch-with-auth"; import { getState } from "../util/ha-pref-storage"; import hassCallApi, { hassCallApiRaw } from "../util/hass-call-api"; +import { callWS } from "../util/ws-message"; import type { HassBaseEl } from "./hass-base-mixin"; export const connectionMixin = >( @@ -187,24 +188,7 @@ export const connectionMixin = >( conn.sendMessage(msg); }, // For messages that expect a response - callWS: (msg) => { - if (this.hass?.debugConnection) { - // eslint-disable-next-line no-console - console.log("Sending", msg); - } - - const resp = conn.sendMessagePromise(msg); - - if (this.hass?.debugConnection) { - resp.then( - // eslint-disable-next-line no-console - (result) => console.log("Received", result), - // eslint-disable-next-line no-console - (err) => console.error("Error", err) - ); - } - return resp; - }, + callWS: (msg) => callWS(conn, msg), loadBackendTranslation: (category, integration?, configFlow?) => // @ts-ignore this._loadHassTranslations( diff --git a/src/util/ws-message.ts b/src/util/ws-message.ts new file mode 100644 index 0000000000..6d82ae5248 --- /dev/null +++ b/src/util/ws-message.ts @@ -0,0 +1,23 @@ +import type { Connection, MessageBase } from "home-assistant-js-websocket"; + +export const callWS = ( + connection: Connection, + msg: MessageBase +): Promise => { + if (__DEV__) { + // eslint-disable-next-line no-console + console.log("Sending", msg); + } + + const response = connection.sendMessagePromise(msg); + + if (__DEV__) { + response.then( + // eslint-disable-next-line no-console + (result) => console.log("Received", result), + // eslint-disable-next-line no-console + (err) => console.error("Error", err) + ); + } + return response; +};